diff --git a/.gitignore b/.gitignore index 9f2cba3bf..3975c4521 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,55 @@ -build -*~ -Doxygen -prebuilt -apps/openmw/config.hpp -Docs/mainpage.hpp +## make CMakeFiles */CMakeFiles CMakeCache.txt -moc_*.cxx cmake_install.cmake -*.[ao] Makefile makefile -data +build +prebuilt + +## doxygen +Doxygen + +## ides/editors +*~ *.kdev4 -CMakeLists.txt.user *.swp *.swo *.kate-swp .cproject .project -.settings/ +.settings .directory +## qt-creator +CMakeLists.txt.user* + +## resources +data +resources +/*.cfg +/*.desktop +/*.install + +## binaries +/esmtool +/mwiniimport +/omwlauncher +/openmw +/opencs + +## generated objects +apps/openmw/config.hpp +components/version/version.hpp +Docs/mainpage.hpp +moc_*.cxx +*.cxx_parameters +*qrc_launcher.cxx +*qrc_resources.cxx +*__* +*ui_datafilespage.h +*ui_graphicspage.h +*ui_mainwindow.h +*ui_playpage.h +*.[ao] +*.so diff --git a/.travis.yml b/.travis.yml index caf2e3389..39a02de63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,18 @@ compiler: branches: only: - master - - next + - /openmw-.*$/ before_install: - pwd - - git submodule update --init --recursive + - git fetch --tags - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - - echo "yes" | sudo apt-add-repository ppa:openmw/deps + - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq - - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev - - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev + - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock + - sudo apt-get install -qq libqt4-dev + - sudo apt-get install -qq libopenal-dev + - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev + - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 @@ -26,7 +26,7 @@ before_script: - cd - - mkdir build - cd build - - cmake .. -DOGRE_STATIC=1 -DMYGUI_STATIC=1 -DBOOST_STATIC=1 -DSDL2_STATIC=1 -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 + - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1 script: - make -j4 after_script: @@ -37,3 +37,9 @@ notifications: email: on_success: change on_failure: always + irc: + channels: + - "chat.freenode.net#openmw" + on_success: change + on_failure: always + diff --git a/CMakeLists.txt b/CMakeLists.txt index d7b621924..eba5f02b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,15 +14,30 @@ endif (APPLE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) -include (OpenMWMacros) +include(OpenMWMacros) # Version -set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 26) -set (OPENMW_VERSION_RELEASE 0) +include(GetGitRevisionDescription) -set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") +get_git_tag_revision(TAGHASH --tags --max-count=1 "HEAD...") +get_git_head_revision(REFSPEC COMMITHASH) +git_describe(VERSION --tags ${TAGHASH}) + +string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") +if (MATCH) + string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") + + set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + + message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...") +else (MATCH) + message(FATAL_ERROR "Failed to get valid version information from Git") +endif (MATCH) # doxygen main page @@ -50,7 +65,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") +if(UNIX AND NOT APPLE) + option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) + if(BUILD_WITH_DPKG) + find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") + endif(BUILD_WITH_DPKG) +endif(UNIX AND NOT APPLE) # Location of morrowind data files if (APPLE) @@ -79,7 +99,6 @@ set(OENGINE_OGRE ${LIBDIR}/openengine/ogre/renderer.cpp ${LIBDIR}/openengine/ogre/fader.cpp ${LIBDIR}/openengine/ogre/lights.cpp - ${LIBDIR}/openengine/ogre/particles.cpp ${LIBDIR}/openengine/ogre/selectionbuffer.cpp ${LIBDIR}/openengine/ogre/imagerotate.cpp ) @@ -89,14 +108,10 @@ set(OENGINE_GUI ) set(OENGINE_BULLET - ${LIBDIR}/openengine/bullet/btKinematicCharacterController.cpp - ${LIBDIR}/openengine/bullet/btKinematicCharacterController.h ${LIBDIR}/openengine/bullet/BtOgre.cpp ${LIBDIR}/openengine/bullet/BtOgreExtras.h ${LIBDIR}/openengine/bullet/BtOgreGP.h ${LIBDIR}/openengine/bullet/BtOgrePG.h - ${LIBDIR}/openengine/bullet/CMotionState.cpp - ${LIBDIR}/openengine/bullet/CMotionState.h ${LIBDIR}/openengine/bullet/physic.cpp ${LIBDIR}/openengine/bullet/physic.hpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp @@ -181,10 +196,11 @@ if (WIN32) set(Boost_USE_STATIC_LIBS ON) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) + + # Suppress WinMain(), provided by SDL + add_definitions(-DSDL_MAIN_HANDLED) else (WIN32) set(PLATFORM_INCLUDE_DIR "") - find_path (UUID_INCLUDE_DIR uuid/uuid.h) - include_directories(${UUID_INCLUDE_DIR}) endif (WIN32) if (MSVC10) set(PLATFORM_INCLUDE_DIR "") @@ -208,7 +224,7 @@ if (HAVE_UNORDERED_MAP) endif () -set(BOOST_COMPONENTS system filesystem program_options thread date_time wave) +set(BOOST_COMPONENTS system filesystem program_options) IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) @@ -236,7 +252,6 @@ include_directories("." ${MYGUI_INCLUDE_DIRS} ${MYGUI_PLATFORM_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} - ${UUID_INCLUDE_DIR} ${LIBDIR} ) @@ -318,6 +333,9 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg "${OpenMW_BINARY_DIR}/opencs.cfg") +configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters + "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) + if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") @@ -378,7 +396,6 @@ IF(NOT WIN32 AND NOT APPLE) # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) ENDIF (DPKG_PROGRAM) @@ -402,46 +419,6 @@ IF(NOT WIN32 AND NOT APPLE) # Install resources INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") - - IF (DPKG_PROGRAM) - ## Debian Specific - IF(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.git") - EXEC_PROGRAM("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe" OUTPUT_VARIABLE GIT_VERSION ) - STRING(REGEX REPLACE "openmw-" "" VERSION_STRING "${GIT_VERSION}") - EXEC_PROGRAM("git" ARGS "config --get user.name" OUTPUT_VARIABLE GIT_NAME ) - EXEC_PROGRAM("git" ARGS "config --get user.email" OUTPUT_VARIABLE GIT_EMAIL) - SET(PACKAGE_MAINTAINER "${GIT_NAME} <${GIT_EMAIL}>") - ELSE() - SET(VERSION_STRING "${OPENMW_VERSION}") - SET(PACKAGE_MAINTAINER "unknown") - ENDIF() - - SET(CPACK_GENERATOR "DEB") - SET(CPACK_PACKAGE_NAME "openmw") - SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://openmw.org") - SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") - SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PACKAGE_MAINTAINER}") - SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A reimplementation of The Elder Scrolls III: Morrowind - OpenMW is a reimplementation of the Bethesda Game Studios game The Elder Scrolls III: Morrowind. - 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 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), 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") - - STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE) - EXECUTE_PROCESS( - COMMAND ${DPKG_PROGRAM} --print-architecture - OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") - - - INCLUDE(CPack) - ENDIF(DPKG_PROGRAM) ENDIF(NOT WIN32 AND NOT APPLE) if(WIN32) @@ -453,13 +430,22 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/OFL.txt" "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" - "${OpenMW_SOURCE_DIR}/Daedric Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" - "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" - "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" "${OpenMW_BINARY_DIR}/Release/openmw.exe" DESTINATION ".") + + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" DESTINATION ".") + ENDIF(BUILD_LAUNCHER) + IF(BUILD_MWINIIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" DESTINATION ".") + ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_OPENCS) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION ".") + ENDIF(BUILD_OPENCS) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") @@ -469,7 +455,13 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;omwlauncher;OpenMW Launcher") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW") + IF(BUILD_LAUNCHER) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};omwlauncher;OpenMW Launcher") + ENDIF(BUILD_LAUNCHER) + IF(BUILD_OPENCS) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};opencs;OpenMW Construction Set") + ENDIF(BUILD_OPENCS) SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_NSIS_DELETE_ICONS_EXTRA " !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP @@ -666,8 +658,11 @@ if (APPLE) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - set(APPS "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + + set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/OpenCS.app") + set(PLUGINS "") set(ABSOLUTE_PLUGINS "") foreach (PLUGIN ${USED_OGRE_PLUGINS}) @@ -725,7 +720,8 @@ if (APPLE) cmake_policy(SET CMP0009 OLD) set(BU_CHMOD_BUNDLE_ITEMS ON) include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") + fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\") " COMPONENT Runtime) include(CPack) endif (APPLE) diff --git a/Daedric Font License.txt b/Daedric Font License.txt deleted file mode 100644 index a1553d0b0..000000000 --- a/Daedric Font License.txt +++ /dev/null @@ -1,10 +0,0 @@ -Dongle's Oblivion Daedric font set -http://www.uesp.net/wiki/Lore:Daedric_Alphabet#Daedric_Font - ---------------------------------------------------- - -This was done entirely as a personal project. Bethesda Softworks graciously granted me the permission for it. I am not connected with them in any way. -You may freely use these fonts to create anything you'd like. You may re-distribute the fonts freely, over the Internet, or by any other means. Always keep the .zip file intact, and this read me included. -Please do not modify and redistribute the fonts without my permission. -You may NOT sell any of these fonts under any circumstances. This includes putting them on compilation font CDs for sale, putting them in a "members only" pay-area of a website, or any other means of financial gain connected in ANY way with the redistribution of any of these fonts. -You have my permission to create and sell any artwork made with these fonts, however you may need to contact Bethesda Softworks before doing so. diff --git a/README_Mac.md b/README_Mac.md deleted file mode 100644 index dc3918368..000000000 --- a/README_Mac.md +++ /dev/null @@ -1,160 +0,0 @@ -#Getting OpenMW Working on OS X - -## Initial setup -First of all, clone OpenMW repo. - - $ git clone github.com/zinnschlag/openmw - -Or use your github url if you forked. - -About dependencies: I prefer not to install them globally (i. e. in /usr/local/), so I'm installing them in directory in my home directory. If OpenMW sources is in $HOME/path/openmw, I'm using $HOME/path/libs/root as prefix for boost and other libs. - -It's useful to create env var for lib install prefix: - - $ export OMW_LIB_PREFIX=$HOME/path/libs/root` - -Most of libs can be installed from [Homebrew][homebrew]. Only mpg123 needs to be installed from source (due to lack of universal compilation support). I think that some of libs can be installed from MacPorts or Fink too. - -As OpenMW currently only supports i386 architecture on OS X, denendencies also should support it. Set some env vars in current terminal: - - $ export CFLAGS="-arch i386" - $ export CXXFLAGS="-arch i386" - $ export LDFLAGS="-arch i386" - -If you close your terminal, you should set env vars again before pcoceeding to next steps! - -## Boost -Download [boost][boost] and install it with the following command: - - $ cd /path/to/boost/source - $ ./bootstrap.sh --prefix=$OMW_LIB_PREFIX - $ ./bjam --build-dir=build --layout=versioned \ - --toolset=darwin architecture=x86 address-model=32 \ - --link-shared,static --prefix=$OMW_LIB_PREFIX install - - -Alternatively you can install boost with homebrew: - - $ brew install boost --universal - -I think MacPorts also should support universal build for boost. - -## Ogre -Download [Ogre][] SDK (tested with 1.7.3), unpack it somewhere and move -`lib/Release/Ogre.framework` into `/Library/Frameworks`. - -## OIS -Download patched [OIS][] and use the XCode project provided. Be sure to set your build architecture to - `i386`. Once it built, locate built OIS.framework with Xcode and move it to `/Library/Frameworks`. - -## mpg123 -Download [MPG 123][mpg123] and build it: - - $ cd /path/to/mpg123/source - $ ./configure --prefix=$OMW_LIB_PREFIX --disable-debug \ - --disable-dependency-tracking \ - --with-optimization=4 \ - --with-audio=dummy \ - --with-default-audio=dummy \ - --with-cpu=sse_alone \ - $ make install - -## libsndfile -Download [libsndfile][] and build it: - - $ cd /path/to/libsndfile/source - $ ./configure --prefix=$OMW_LIB_PREFIX \ - --disable-dependency-tracking - $ make install - -or install with homebrew: - - $ brew install libsndfile --universal - -## Bullet -Download [Bullet][] and build it: - - $ cd /path/to/bullet/source - $ mkdir build - $ cd build - $ cmake -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=$OMW_LIB_PREFIX \ - -DBUILD_EXTRAS=OFF \ - -DBUILD_DEMOS=OFF \ - -DCMAKE_OSX_ARCHITECTURES=i386 \ - -DCMAKE_INSTALL_NAME_DIR=$OMW_LIB_RPEFIX/lib \ - -G"Unix Makefiles" ../ - $ make install - -or install with homebrew: - - $ brew install bullet --HEAD --universal - -I prefer head because 2.79 has some issue which causes OpenMW to lag. Also you can edit formula and install 2.77, which is stable and haven't mentioned issue. - -## Qt -Install [Qt][qt]. Qt SDK distributed by Nokia is not an option because it's 64 bit only, and OpenMW currently doesn't build for 64 bit on OS X. I'm installing it from Homebrew: - - $ brew install qt --universal - -## Run CMake -Generate the Makefile for OpenMW as follows and build OpenMW: - - $ mkdir /path/to/openmw/build/dir - $ cd /path/to/open/build/dir - $ cmake \ - -D CMAKE_OSX_ARCHITECTURES=i386 \ - -D OGRE_SDK=/path/to/ogre/sdk \ - -D BOOST_INCLUDEDIR=$OMW_LIB_PREFIX/include/boost-1_45 \ - -D BOOST_LIBRARYDIR=$OMW_LIB_PREFIX/lib \ - -D SNDFILE_INCLUDE_DIR=$OMW_LIB_PREFIX/include \ - -D SNDFILE_LIBRARY=$OMW_LIB_PREFIX/lib/libsndfile.a \ - -D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \ - -D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \ - -D BULLET_DYNAMICS_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletDynamics.a \ - -D BULLET_COLLISION_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletCollision.a \ - -D BULLET_MATH_LIBRARY=$OMW_LIB_PREFIX/lib/libLinearMath.a \ - -D BULLET_SOFTBODY_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletSoftBody.a \ - -D BULLET_INCLUDE_DIR=$OMW_LIB_PREFIX/include/bullet/ \ - -G "Unix Makefiles" /path/to/openmw/source/dir - $ make - -You can use `-G"Xcode"` if you prefer Xcode, or -G"Eclipse CDT4 - Unix Makefiles" -if you prefer Eclipse. You also can specify `-D CMAKE_BUILD_TYPE=Debug` for debug -build. As for CMake 2.8.7 and Xcode 4.3, Xcode generator is broken. Sadly Eclipse CDT also cannot import generated project at least on my machine. - -If all libs installed via homebrew (excluding mpg123), then command would be even simplier: - - $ cmake \ - -D CMAKE_OSX_ARCHITECTURES="i386" \ - -D OGRE_SDK=/path/to/ogre/sdk \ - -D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \ - -D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \ - -G "Unix Makefiles" /path/to/openmw/source/dir - $ make - -Note for users with recent Xcode versions: you must explicitly specify what set of compilers do you use! If not, gcc will be used for C and Clang for C++. Just add this two -D's to command: `-D CMAKE_C_COMPILER=/usr/bin/clang` and `-D CMAKE_CXX_COMPILER=/usr/bin/clang` - -Note for Xcode 4.3 users: you should specify full path to used SDK, because current CMake (2.8.7) couldn't find SDKs inside Xcode app bundle: - - -D CMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk" - -# Run -From your build directory run: - - $ OpenMW.app/Contents/MacOS/openmw -or: - - $ open OpenMW.app -Enjoy! - -[homebrew]: https://github.com/mxcl/homebrew -[boost]: http://www.boost.org -[Ogre]: http://www.ogre3d.org -[Bullet]: http://bulletphysics.org -[OIS]: https://github.com/corristo/ois-fork -[mpg123]: http://www.mpg123.de -[libsndfile]: http://www.mega-nerd.com/libsndfile -[official website]: http://openmw.com -[Will Thimbleby's Ogre Framework]: http://www.thimbleby.net/ogre/ -[qt]: http://qt.nokia.com/ \ No newline at end of file diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index e6fcc2567..3781dd066 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -184,7 +184,7 @@ int list(Bsa::BSAFile& bsa, Arguments& info) { // List all files const Bsa::BSAFile::FileList &files = bsa.getList(); - for(int i=0; i(ref.mReferenceBlocked) << "'" << std::endl; + std::cout << " Deleted: " << deleted << std::endl; } } @@ -305,14 +308,14 @@ int load(Arguments& info) info.data.author = esm.getAuthor(); info.data.description = esm.getDesc(); - info.data.masters = esm.getMasters(); + info.data.masters = esm.getGameFiles(); if (!quiet) { std::cout << "Author: " << esm.getAuthor() << std::endl << "Description: " << esm.getDesc() << std::endl << "File format version: " << esm.getFVer() << std::endl; - std::vector m = esm.getMasters(); + std::vector m = esm.getGameFiles(); if (!m.empty()) { std::cout << "Masters:" << std::endl; @@ -339,6 +342,8 @@ int load(Arguments& info) } std::string id = esm.getHNOString("NAME"); + if (id.empty()) + id = esm.getHNOString("INAM"); if(!quiet && interested) std::cout << "\nRecord: " << n.toString() diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 7b1fc7fb2..7a42e6900 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -680,7 +680,7 @@ std::string creatureFlags(int flags) if (flags & ESM::Creature::Walks) properties += "Walks "; if (flags & ESM::Creature::Swims) properties += "Swims "; if (flags & ESM::Creature::Flies) properties += "Flies "; - if (flags & ESM::Creature::Biped) properties += "Biped "; + if (flags & ESM::Creature::Bipedal) properties += "Bipedal "; if (flags & ESM::Creature::Respawn) properties += "Respawn "; if (flags & ESM::Creature::Weapon) properties += "Weapon "; if (flags & ESM::Creature::Skeleton) properties += "Skeleton "; @@ -691,7 +691,7 @@ std::string creatureFlags(int flags) ESM::Creature::Walks| ESM::Creature::Swims| ESM::Creature::Flies| - ESM::Creature::Biped| + ESM::Creature::Bipedal| ESM::Creature::Respawn| ESM::Creature::Weapon| ESM::Creature::Skeleton| @@ -717,16 +717,26 @@ std::string landFlags(int flags) return properties; } -std::string leveledListFlags(int flags) +std::string itemListFlags(int flags) { std::string properties = ""; if (flags == 0) properties += "[None] "; - if (flags & ESM::LeveledListBase::AllLevels) properties += "AllLevels "; - // This flag apparently not present on creature lists... - if (flags & ESM::LeveledListBase::Each) properties += "Each "; + if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels "; + if (flags & ESM::ItemLevList::Each) properties += "Each "; int unused = (0xFFFFFFFF ^ - (ESM::LeveledListBase::AllLevels| - ESM::LeveledListBase::Each)); + (ESM::ItemLevList::AllLevels| + ESM::ItemLevList::Each)); + if (flags & unused) properties += "Invalid "; + properties += str(boost::format("(0x%08X)") % flags); + return properties; +} + +std::string creatureListFlags(int flags) +{ + std::string properties = ""; + if (flags == 0) properties += "[None] "; + if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels "; + int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels); if (flags & unused) properties += "Invalid "; properties += str(boost::format("(0x%08X)") % flags); return properties; @@ -764,34 +774,19 @@ std::string magicEffectFlags(int flags) { std::string properties = ""; if (flags == 0) properties += "[None] "; - // Enchanting & SpellMaking occur on the same list of effects. - // "EXTRA SPELL" appears in the construction set under both the - // spell making and enchanting tabs as an allowed effect. Since - // most of the effects without this flags are defective in various - // ways, it's still very unclear what these flag bits are. - if (flags & ESM::MagicEffect::SpellMaking) properties += "SpellMaking "; - if (flags & ESM::MagicEffect::Enchanting) properties += "Enchanting "; - if (flags & 0x00000040) properties += "RangeNoSelf "; - if (flags & 0x00000080) properties += "RangeTouch "; - if (flags & 0x00000100) properties += "RangeTarget "; - if (flags & 0x00001000) properties += "Unknown2 "; - if (flags & 0x00000001) properties += "AffectSkill "; - if (flags & 0x00000002) properties += "AffectAttribute "; + if (flags & ESM::MagicEffect::TargetAttribute) properties += "TargetAttribute "; + if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill "; if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration "; - if (flags & 0x00000008) properties += "NoMagnitude "; - if (flags & 0x00000010) properties += "Negative "; - if (flags & 0x00000020) properties += "Unknown1 "; - // ESM componet says 0x800 is negative, but none of the magic - // effects have this flags set. - if (flags & ESM::MagicEffect::Negative) properties += "Unused "; - // Since only Chameleon has this flag it could be anything - // that uniquely distinguishes Chameleon. - if (flags & 0x00002000) properties += "Chameleon "; - if (flags & 0x00004000) properties += "Bound "; - if (flags & 0x00008000) properties += "Summon "; - // Calm, Demoralize, Frenzy, Lock, Open, Rally, Soultrap, Turn Unded - if (flags & 0x00010000) properties += "Unknown3 "; - if (flags & 0x00020000) properties += "Absorb "; + if (flags & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude "; + if (flags & ESM::MagicEffect::Harmful) properties += "Harmful "; + if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX "; + if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf "; + if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch "; + if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget "; + if (flags & ESM::MagicEffect::UncappedDamage) properties += "UncappedDamage "; + if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable "; + if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable "; + if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked "; if (flags & 0xFFFC0000) properties += "Invalid "; properties += str(boost::format("(0x%08X)") % flags); return properties; diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index 48d7b249b..007f93316 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -50,7 +50,8 @@ std::string cellFlags(int flags); std::string containerFlags(int flags); std::string creatureFlags(int flags); std::string landFlags(int flags); -std::string leveledListFlags(int flags); +std::string creatureListFlags(int flags); +std::string itemListFlags(int flags); std::string lightFlags(int flags); std::string magicEffectFlags(int flags); std::string npcFlags(int flags); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 68e0dcc09..184d11bb4 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -13,8 +13,8 @@ void printAIPackage(ESM::AIPackage p) std::cout << " Distance: " << p.mWander.mDistance << std::endl; std::cout << " Duration: " << p.mWander.mDuration << std::endl; std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl; - if (p.mWander.mUnk != 1) - std::cout << " Unknown: " << (int)p.mWander.mUnk << std::endl; + if (p.mWander.mShouldRepeat != 1) + std::cout << " Should repeat: " << (bool)p.mWander.mShouldRepeat << std::endl; std::cout << " Idle: "; for (int i = 0; i != 8; i++) @@ -740,8 +740,8 @@ void Record::print() if (mData.mClass != "") std::cout << " Class: " << mData.mClass << std::endl; std::cout << " Factionless: " << mData.mFactionLess << std::endl; - if (mData.mNpcFaction != "") - std::cout << " NPC Faction: " << mData.mNpcFaction << std::endl; + if (mData.mFaction != "") + std::cout << " NPC Faction: " << mData.mFaction << std::endl; if (mData.mData.mRank != -1) std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl; if (mData.mPcFaction != "") @@ -834,7 +834,7 @@ template<> void Record::print() { std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; - std::cout << " Flags: " << leveledListFlags(mData.mFlags) << std::endl; + std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; std::vector::iterator iit; for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++) @@ -846,11 +846,11 @@ template<> void Record::print() { std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; - std::cout << " Flags: " << leveledListFlags(mData.mFlags) << std::endl; + std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; std::vector::iterator iit; for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++) - std::cout << " Inventory: Count: " << iit->mLevel + std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; } @@ -958,7 +958,7 @@ void Record::print() std::cout << " RGB Color: " << "(" << mData.mData.mRed << "," << mData.mData.mGreen << "," - << mData.mData.mGreen << ")" << std::endl; + << mData.mData.mBlue << ")" << std::endl; } template<> @@ -989,8 +989,7 @@ void Record::print() std::cout << " Faction: " << mData.mFaction << std::endl; std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl; - // Seriously? - if (mData.mNpdt52.mGold == -10) + if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; @@ -1022,7 +1021,7 @@ void Record::print() std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl; std::cout << " Skills:" << std::endl; - for (int i = 0; i != 27; i++) + for (int i = 0; i != ESM::Skill::Length; i++) std::cout << " " << skillLabel(i) << ": " << (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl; diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 78cf5d436..45b6d0426 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -24,7 +24,12 @@ namespace EsmTool bool mPrintPlain; public: - RecordBase () { mPrintPlain = false; } + RecordBase () + : mFlags(0) + , mPrintPlain(false) + { + } + virtual ~RecordBase() {} const std::string &getId() const { diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 92cabffff..e4638c31b 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -11,7 +11,9 @@ set(LAUNCHER settings/launchersettings.cpp utils/checkablemessagebox.cpp + utils/profilescombobox.cpp utils/textinputdialog.cpp + utils/lineedit.cpp ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ) @@ -32,8 +34,9 @@ set(LAUNCHER_HEADER settings/settingsbase.hpp utils/checkablemessagebox.hpp + utils/profilescombobox.hpp utils/textinputdialog.hpp - + utils/lineedit.hpp ) if(NOT WIN32) LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) @@ -48,8 +51,11 @@ set(LAUNCHER_HEADER_MOC playpage.hpp textslotmsgbox.hpp - utils/checkablemessagebox.hpp utils/textinputdialog.hpp + utils/checkablemessagebox.hpp + utils/profilescombobox.hpp + utils/lineedit.hpp + ) if(NOT WIN32) @@ -62,6 +68,7 @@ set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) @@ -130,8 +137,3 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(omwlauncher gcov) endif() -# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream -if (UNIX AND NOT APPLE) -target_link_libraries(omwlauncher dl Xt) -endif() - diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index add3dea40..362d7562c 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -4,548 +4,300 @@ #include #include #include +#include #include -#include -#include -#include +#include -#include -#include -#include +#include + +#include "utils/textinputdialog.hpp" +#include "utils/profilescombobox.hpp" #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" -#include "utils/textinputdialog.hpp" +#include "components/contentselector/view/contentselector.hpp" -DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) +Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) , QWidget(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); - mastersTable->horizontalHeader()->hide(); - - // 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->horizontalHeader()->hide(); - - pluginsTable->verticalHeader()->setDefaultSectionSize(height); - pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - - // Adjust the tableview widths inside the splitter - QList sizeList; - sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt(); - sizeList << mLauncherSettings.value(QString("General/PluginTable/width"), QString("340")).toInt(); - - splitter->setSizes(sizeList); - - // 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(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - - 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))); + ui.setupUi (this); + setObjectName ("DataFilesPage"); + mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); - connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter())); - - createActions(); + buildView(); setupDataFiles(); } -void DataFilesPage::createActions() -{ - - // Add the actions to the toolbuttons - newProfileButton->setDefaultAction(newProfileAction); - deleteProfileButton->setDefaultAction(deleteProfileAction); - - // Context menu actions - mContextMenu = new QMenu(this); - mContextMenu->addAction(checkAction); - mContextMenu->addAction(uncheckAction); -} - -void DataFilesPage::setupDataFiles() +void Launcher::DataFilesPage::loadSettings() { - // Set the encoding to the one found in openmw.cfg or the default - mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); - QStringList paths = mGameSettings.getDataDirs(); + paths.insert (0, mDataLocal); + PathIterator pathIterator (paths); - foreach (const QString &path, paths) { - mDataFilesModel->addFiles(path); - } - - QString dataLocal = mGameSettings.getDataLocal(); - if (!dataLocal.isEmpty()) - mDataFilesModel->addFiles(dataLocal); - - // Sort by date accessed for now - mDataFilesModel->sort(3); - - QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); - - if (!profiles.isEmpty()) - profilesComboBox->addItems(profiles); - - // Add the current profile if empty - if (profilesComboBox->findText(profile) == -1 && !profile.isEmpty()) - profilesComboBox->addItem(profile); - - if (profilesComboBox->findText(QString("Default")) == -1) - profilesComboBox->addItem(QString("Default")); - - if (profile.isEmpty() || profile == QLatin1String("Default")) { - deleteProfileAction->setEnabled(false); - profilesComboBox->setEditEnabled(false); - profilesComboBox->setCurrentIndex(profilesComboBox->findText(QString("Default"))); - } else { - profilesComboBox->setEditEnabled(true); - profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); - } - - // 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(); - -} - -void DataFilesPage::loadSettings() -{ - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); + QString profileName = ui.profilesComboBox->currentText(); - if (profile.isEmpty()) - return; + QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName, Qt::MatchExactly); - mDataFilesModel->uncheckAll(); + QStringList filepaths; - 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 &file, files) + { + QString filepath = pathIterator.findFirstPath (file); - foreach (const QString &master, masters) { - QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master)); - if (index.isValid()) - mDataFilesModel->setCheckState(index, Qt::Checked); + if (!filepath.isEmpty()) + filepaths << filepath; } - foreach (const QString &plugin, plugins) { - QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin)); - if (index.isValid()) - mDataFilesModel->setCheckState(index, Qt::Checked); - } + mSelector->setProfileContent (filepaths); } -void DataFilesPage::saveSettings() +void Launcher::DataFilesPage::saveSettings(const QString &profile) { - if (mDataFilesModel->rowCount() < 1) - return; + QString profileName = profile; - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); - - if (profile.isEmpty()) { - profile = profilesComboBox->currentText(); - mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); - } + if (profileName.isEmpty()) + profileName = ui.profilesComboBox->currentText(); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); + //retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); - mGameSettings.remove(QString("master")); - mGameSettings.remove(QString("plugin")); + removeProfile (profileName); - QStringList items = mDataFilesModel->checkedItems(); + mGameSettings.remove(QString("content")); - foreach(const QString &item, items) { + //set the value of the current profile (not necessarily the profile being saved!) + mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); - 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); - } + foreach(const ContentSelectorModel::EsmFile *item, items) { + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } } -void DataFilesPage::updateOkButton(const QString &text) +void Launcher::DataFilesPage::buildView() { - // We do this here because we need the profiles combobox text - if (text.isEmpty()) { - mNewProfileDialog->setOkButtonEnabled(false); - return; - } + ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); - (profilesComboBox->findText(text) == -1) - ? mNewProfileDialog->setOkButtonEnabled(true) - : mNewProfileDialog->setOkButtonEnabled(false); -} + //tool buttons + ui.newProfileButton->setToolTip ("Create a new profile"); + ui.deleteProfileButton->setToolTip ("Delete an existing profile"); -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(); + //combo box + ui.profilesComboBox->addItem ("Default"); + ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); - mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0))); - mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1))); -} + // Add the actions to the toolbuttons + ui.newProfileButton->setDefaultAction (ui.newProfileAction); + ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); -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); -} + //establish connections + connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), + this, SLOT (slotProfileChanged(int))); -void DataFilesPage::setProfilesComboBoxIndex(int index) -{ - profilesComboBox->setCurrentIndex(index); + connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), + this, SLOT (slotProfileRenamed(QString, QString))); + + connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), + this, SLOT (slotProfileChangedByUser(QString, QString))); } -void DataFilesPage::slotCurrentIndexChanged(int index) +void Launcher::DataFilesPage::removeProfile(const QString &profile) { - emit profileChanged(index); + mLauncherSettings.remove(QString("Profiles/") + profile); } -QAbstractItemModel* DataFilesPage::profilesComboBoxModel() +QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const { - return profilesComboBox->model(); + return ui.profilesComboBox->model(); } -int DataFilesPage::profilesComboBoxIndex() +int Launcher::DataFilesPage::profilesIndex() const { - return profilesComboBox->currentIndex(); + return ui.profilesComboBox->currentIndex(); } -void DataFilesPage::on_newProfileAction_triggered() +void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) { - if (mNewProfileDialog->exec() == QDialog::Accepted) { - QString profile = mNewProfileDialog->lineEdit()->text(); - profilesComboBox->addItem(profile); - profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); + if (index >= -1 && index < ui.profilesComboBox->count()) + { + QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); + QString current = ui.profilesComboBox->itemText(index); + + setProfile (previous, current, savePrevious); } } -void DataFilesPage::on_deleteProfileAction_triggered() +void Launcher::DataFilesPage::setProfile (const QString &previous, const QString ¤t, bool savePrevious) { - QString profile = profilesComboBox->currentText(); - - if (profile.isEmpty()) - return; - - QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Delete Profile")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); + //abort if no change (poss. duplicate signal) + if (previous == current) + return; - QAbstractButton *deleteButton = - msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); + if (!previous.isEmpty() && savePrevious) + saveSettings (previous); - msgBox.exec(); + ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current)); - if (msgBox.clickedButton() == deleteButton) { - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); + loadSettings(); - // Remove the profile from the combobox - profilesComboBox->removeItem(profilesComboBox->findText(profile)); - } + checkForDefaultProfile(); } -void DataFilesPage::on_checkAction_triggered() +void Launcher::DataFilesPage::slotProfileDeleted (const QString &item) { - if (pluginsTable->hasFocus()) - setPluginsCheckstates(Qt::Checked); - - if (mastersTable->hasFocus()) - setMastersCheckstates(Qt::Checked); - + removeProfile (item); } -void DataFilesPage::on_uncheckAction_triggered() +void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) { - if (pluginsTable->hasFocus()) - setPluginsCheckstates(Qt::Unchecked); - - if (mastersTable->hasFocus()) - setMastersCheckstates(Qt::Unchecked); + setProfile(previous, current, true); + emit signalProfileChanged (ui.profilesComboBox->findText(current)); } -void DataFilesPage::setMastersCheckstates(Qt::CheckState state) +void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) { - if (!mastersTable->selectionModel()->hasSelection()) { + if (previous.isEmpty()) return; - } - - QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); - - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; - QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); + // Save the new profile name + saveSettings(); - if (!sourceIndex.isValid()) - return; + // Remove the old one + removeProfile (previous); - mDataFilesModel->setCheckState(sourceIndex, state); - } + loadSettings(); } -void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) +void Launcher::DataFilesPage::slotProfileChanged(int index) { - 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); - } + setProfile (index, true); } -void DataFilesPage::setCheckState(QModelIndex index) +void Launcher::DataFilesPage::setupDataFiles() { - if (!index.isValid()) - return; + QStringList paths = mGameSettings.getDataDirs(); - QObject *object = QObject::sender(); + foreach (const QString &path, paths) + mSelector->addFiles(path); - // Not a signal-slot call - if (!object) - return; + mDataLocal = mGameSettings.getDataLocal(); + if (!mDataLocal.isEmpty()) + mSelector->addFiles(mDataLocal); - if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); + QStringList profiles; + QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + foreach (QString key, mLauncherSettings.getSettings().keys()) + { + if (key.contains("Profiles/")) + { + QString profile = key.mid (9); + if (profile != "currentprofile") + { + if (!profiles.contains(profile)) + profiles << profile; + } } } - 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); - } - } + foreach (const QString &item, profiles) + addProfile (item, false); - return; -} + setProfile (ui.profilesComboBox->findText(currentProfile), false); -void DataFilesPage::filterChanged(const QString filter) -{ - QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString); - mFilterProxyModel->setFilterRegExp(regExp); + loadSettings(); } -void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) +void Launcher::DataFilesPage::on_newProfileAction_triggered() { - // Prevent the deletion of the default profile - if (current == QLatin1String("Default")) { - deleteProfileAction->setEnabled(false); - profilesComboBox->setEditEnabled(false); - } else { - deleteProfileAction->setEnabled(true); - profilesComboBox->setEditEnabled(true); - } + TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); - if (previous.isEmpty()) + if (newDialog.exec() != QDialog::Accepted) return; - 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(); -} + QString profile = newDialog.getText(); -void DataFilesPage::profileRenamed(const QString &previous, const QString ¤t) -{ - if (previous.isEmpty()) - return; + if (profile.isEmpty()) + return; - // Save the new profile name - mLauncherSettings.setValue(QString("Profiles/currentprofile"), current); saveSettings(); - // Remove the old one - mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin")); + mSelector->clearCheckStates(); - // Remove the profile from the combobox - profilesComboBox->removeItem(profilesComboBox->findText(previous)); + addProfile(profile, true); - loadSettings(); + mSelector->setGameFile(); + + saveSettings(); + emit signalProfileChanged (ui.profilesComboBox->findText(profile)); } -void DataFilesPage::showContextMenu(const QPoint &point) +void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) { - QObject *object = QObject::sender(); + if (profile.isEmpty()) + return; - // Not a signal-slot call - if (!object) + if (ui.profilesComboBox->findText (profile) != -1) return; - if (object->objectName() == QLatin1String("PluginsTable")) { - if (!pluginsTable->selectionModel()->hasSelection()) - return; + ui.profilesComboBox->addItem (profile); - QPoint globalPos = pluginsTable->mapToGlobal(point); - QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes(); + if (setAsCurrent) + setProfile (ui.profilesComboBox->findText (profile), false); +} - // Show the check/uncheck actions depending on the state of the selected items - uncheckAction->setEnabled(false); - checkAction->setEnabled(false); +void Launcher::DataFilesPage::on_deleteProfileAction_triggered() +{ + QString profile = ui.profilesComboBox->currentText(); - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; + if (profile.isEmpty()) + return; - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); + if (!showDeleteMessageBox (profile)) + return; - if (!sourceIndex.isValid()) - return; + // Remove the profile from the combobox + ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile)); - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? uncheckAction->setEnabled(true) - : checkAction->setEnabled(true); - } + removeProfile(profile); - // Show menu - mContextMenu->exec(globalPos); - } + saveSettings(); - if (object->objectName() == QLatin1String("MastersTable")) { - if (!mastersTable->selectionModel()->hasSelection()) - return; + loadSettings(); - QPoint globalPos = mastersTable->mapToGlobal(point); - QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); + checkForDefaultProfile(); +} - // Show the check/uncheck actions depending on the state of the selected items - uncheckAction->setEnabled(false); - checkAction->setEnabled(false); +void Launcher::DataFilesPage::checkForDefaultProfile() +{ + //don't allow deleting "Default" profile + bool success = (ui.profilesComboBox->currentText() != "Default"); - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; + ui.deleteProfileAction->setEnabled (success); + ui.profilesComboBox->setEditEnabled (success); +} - QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); +bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) +{ + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Delete Profile")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("Are you sure you want to delete %0?").arg(text)); - if (!sourceIndex.isValid()) - return; + QAbstractButton *deleteButton = + msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? uncheckAction->setEnabled(true) - : checkAction->setEnabled(true); - } + msgBox.exec(); - mContextMenu->exec(globalPos); - } + return (msgBox.clickedButton() == deleteButton); } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index a0b029330..37603a210 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -1,88 +1,136 @@ #ifndef DATAFILESPAGE_H #define DATAFILESPAGE_H +#include "ui_datafilespage.h" #include -#include -#include "ui_datafilespage.h" + +#include +#include class QSortFilterProxyModel; class QAbstractItemModel; -class QAction; class QMenu; -class DataFilesModel; -class TextInputDialog; -class GameSettings; -class LauncherSettings; -class PluginsProxyModel; - namespace Files { struct ConfigurationManager; } +namespace ContentSelectorView { class ContentSelector; } -class DataFilesPage : public QWidget, private Ui::DataFilesPage +namespace Launcher { - Q_OBJECT + class TextInputDialog; + class GameSettings; + class LauncherSettings; + class ProfilesComboBox; + + class DataFilesPage : public QWidget + { + Q_OBJECT + + ContentSelectorView::ContentSelector *mSelector; + Ui::DataFilesPage ui; + + public: + explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings, + LauncherSettings &launcherSettings, QWidget *parent = 0); + + QAbstractItemModel* profilesModel() const; + + int profilesIndex() const; + + //void writeConfig(QString profile = QString()); + void saveSettings(const QString &profile = ""); + void loadSettings(); + + signals: + void signalProfileChanged (int index); + + public slots: + void slotProfileChanged (int index); + + private slots: + + void slotProfileChangedByUser(const QString &previous, const QString ¤t); + void slotProfileRenamed(const QString &previous, const QString ¤t); + void slotProfileDeleted(const QString &item); -public: - DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); + void on_newProfileAction_triggered(); + void on_deleteProfileAction_triggered(); - QAbstractItemModel* profilesComboBoxModel(); - int profilesComboBoxIndex(); + private: - void writeConfig(QString profile = QString()); - void saveSettings(); + QMenu *mContextMenu; -signals: - void profileChanged(int index); + Files::ConfigurationManager &mCfgMgr; -public slots: - void setCheckState(QModelIndex index); - void setProfilesComboBoxIndex(int index); + GameSettings &mGameSettings; + LauncherSettings &mLauncherSettings; - 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(); + QString mDataLocal; - // Action slots - void on_newProfileAction_triggered(); - void on_deleteProfileAction_triggered(); - void on_checkAction_triggered(); - void on_uncheckAction_triggered(); + void setPluginsCheckstates(Qt::CheckState state); -private slots: - void slotCurrentIndexChanged(int index); + void buildView(); + void setupDataFiles(); + void setupConfig(); + void readConfig(); + void setProfile (int index, bool savePrevious); + void setProfile (const QString &previous, const QString ¤t, bool savePrevious); + void removeProfile (const QString &profile); + bool showDeleteMessageBox (const QString &text); + void addProfile (const QString &profile, bool setAsCurrent); + void checkForDefaultProfile(); -private: - DataFilesModel *mDataFilesModel; + class PathIterator + { + QStringList::ConstIterator mCitEnd; + QStringList::ConstIterator mCitCurrent; + QStringList::ConstIterator mCitBegin; + QString mFile; + QString mFilePath; - PluginsProxyModel *mPluginsProxyModel; - QSortFilterProxyModel *mMastersProxyModel; + public: + PathIterator (const QStringList &list) + { + mCitBegin = list.constBegin(); + mCitCurrent = mCitBegin; + mCitEnd = list.constEnd(); + } - QSortFilterProxyModel *mFilterProxyModel; + QString findFirstPath (const QString &file) + { + mCitCurrent = mCitBegin; + mFile = file; + return path(); + } - QMenu *mContextMenu; + QString findNextPath () { return path(); } - Files::ConfigurationManager &mCfgMgr; + private: - GameSettings &mGameSettings; - LauncherSettings &mLauncherSettings; + QString path () + { + bool success = false; + QDir dir; + QFileInfo file; - TextInputDialog *mNewProfileDialog; + while (!success) + { + if (mCitCurrent == mCitEnd) + break; - void setMastersCheckstates(Qt::CheckState state); - void setPluginsCheckstates(Qt::CheckState state); + dir.setPath (*(mCitCurrent++)); + file.setFile (dir.absoluteFilePath (mFile)); - void createActions(); - void setupDataFiles(); - void setupConfig(); - void readConfig(); + success = file.exists(); + } - void loadSettings(); + if (success) + return file.absoluteFilePath(); -}; + return ""; + } + }; + }; +} #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 9308c1d57..8abdf0019 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -4,20 +4,19 @@ #include #include -#ifdef __APPLE__ +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#endif -#include +#endif // MAC_OS_X_VERSION_MIN_REQUIRED -#include +#include #include #include -#include -#include +#include #include "settings/graphicssettings.hpp" @@ -33,11 +32,12 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) : mCfgMgr(cfg) , mGraphicsSettings(graphicsSetting) , QWidget(parent) { + setObjectName ("GraphicsPage"); setupUi(this); // Set the maximum res we can set in windowed mode @@ -52,15 +52,11 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &g } -bool GraphicsPage::setupOgre() +bool Launcher::GraphicsPage::setupOgre() { - // Create a log manager so we can surpress debug text to stdout/stderr - Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager; - logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false); - try { - mOgre = new Ogre::Root("", "", "./launcherOgre.log"); + mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log"); } catch(Ogre::Exception &ex) { @@ -78,40 +74,6 @@ bool GraphicsPage::setupOgre() return false; } - - std::string pluginDir; - const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); - if (pluginEnv) - pluginDir = pluginEnv; - else - { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - pluginDir = ".\\"; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - pluginDir = OGRE_PLUGIN_DIR; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR_REL; -#endif - } - - 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 - mGLPlugin = new Ogre::GLPlugin(); - mOgre->installPlugin(mGLPlugin); -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 - mD3D9Plugin = new Ogre::D3D9Plugin(); - mOgre->installPlugin(mD3D9Plugin); -#endif - // Get the available renderers and put them in the combobox const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers(); @@ -133,7 +95,7 @@ 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 Ogre plugins were installed correctly.
")); msgBox.exec(); return false; } @@ -156,7 +118,7 @@ bool GraphicsPage::setupOgre() return true; } -bool GraphicsPage::setupSDL() +bool Launcher::GraphicsPage::setupSDL() { int displays = SDL_GetNumVideoDisplays(); @@ -179,7 +141,7 @@ bool GraphicsPage::setupSDL() return true; } -bool GraphicsPage::loadSettings() +bool Launcher::GraphicsPage::loadSettings() { if (!setupSDL()) return false; @@ -218,7 +180,7 @@ bool GraphicsPage::loadSettings() return true; } -void GraphicsPage::saveSettings() +void Launcher::GraphicsPage::saveSettings() { vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); @@ -245,7 +207,7 @@ void GraphicsPage::saveSettings() mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex())); } -QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) +QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) { QStringList result; @@ -278,7 +240,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy return result; } -QStringList GraphicsPage::getAvailableResolutions(int screen) +QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) { QStringList result; SDL_DisplayMode mode; @@ -325,7 +287,7 @@ QStringList GraphicsPage::getAvailableResolutions(int screen) return result; } -QRect GraphicsPage::getMaximumResolution() +QRect Launcher::GraphicsPage::getMaximumResolution() { QRect max; int screens = QApplication::desktop()->screenCount(); @@ -340,7 +302,7 @@ QRect GraphicsPage::getMaximumResolution() return max; } -void GraphicsPage::rendererChanged(const QString &renderer) +void Launcher::GraphicsPage::rendererChanged(const QString &renderer) { mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString()); @@ -349,7 +311,7 @@ void GraphicsPage::rendererChanged(const QString &renderer) antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); } -void GraphicsPage::screenChanged(int screen) +void Launcher::GraphicsPage::screenChanged(int screen) { if (screen >= 0) { resolutionComboBox->clear(); @@ -357,7 +319,7 @@ void GraphicsPage::screenChanged(int screen) } } -void GraphicsPage::slotFullScreenChanged(int state) +void Launcher::GraphicsPage::slotFullScreenChanged(int state) { if (state == Qt::Checked) { standardRadioButton->toggle(); @@ -371,7 +333,7 @@ void GraphicsPage::slotFullScreenChanged(int state) } } -void GraphicsPage::slotStandardToggled(bool checked) +void Launcher::GraphicsPage::slotStandardToggled(bool checked) { if (checked) { resolutionComboBox->setEnabled(true); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index d233ea12e..da4cb9fb3 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -5,62 +5,53 @@ #include #include -//#include -//#include -// Static plugin headers -#ifdef ENABLE_PLUGIN_GL -# include "OgreGLPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 -# include "OgreD3D9Plugin.h" -#endif +#include + #include "ui_graphicspage.h" -class GraphicsSettings; namespace Files { struct ConfigurationManager; } -class GraphicsPage : public QWidget, private Ui::GraphicsPage +namespace Launcher { - Q_OBJECT + class GraphicsSettings; -public: - GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); + class GraphicsPage : public QWidget, private Ui::GraphicsPage + { + Q_OBJECT - void saveSettings(); - bool loadSettings(); + public: + GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); -public slots: - void rendererChanged(const QString &renderer); - void screenChanged(int screen); + void saveSettings(); + bool loadSettings(); -private slots: - void slotFullScreenChanged(int state); - void slotStandardToggled(bool checked); + public slots: + void rendererChanged(const QString &renderer); + void screenChanged(int screen); -private: - Ogre::Root *mOgre; - Ogre::RenderSystem *mSelectedRenderSystem; - Ogre::RenderSystem *mOpenGLRenderSystem; - Ogre::RenderSystem *mDirect3DRenderSystem; - #ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; - #endif + private slots: + void slotFullScreenChanged(int state); + void slotStandardToggled(bool checked); - Files::ConfigurationManager &mCfgMgr; - GraphicsSettings &mGraphicsSettings; + private: + OgreInit::OgreInit mOgreInit; + Ogre::Root *mOgre; + Ogre::RenderSystem *mSelectedRenderSystem; + Ogre::RenderSystem *mOpenGLRenderSystem; + Ogre::RenderSystem *mDirect3DRenderSystem; - QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); - QStringList getAvailableResolutions(int screen); - QRect getMaximumResolution(); + Files::ConfigurationManager &mCfgMgr; + GraphicsSettings &mGraphicsSettings; - bool setupOgre(); - bool setupSDL(); -}; + QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); + QStringList getAvailableResolutions(int screen); + QRect getMaximumResolution(); + bool setupOgre(); + bool setupSDL(); + }; +} #endif diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index f67f5edcf..fabf77d90 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -3,19 +3,20 @@ #include #include -#ifdef __APPLE__ +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + #include #include "maindialog.hpp" -// SDL workaround -#include "graphicspage.hpp" int main(int argc, char *argv[]) { SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + SDL_SetMainReady(); if (SDL_Init(SDL_INIT_VIDEO) != 0) { qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError()); @@ -49,7 +50,7 @@ int main(int argc, char *argv[]) // Support non-latin characters QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - MainDialog mainWin; + Launcher::MainDialog mainWin; if (mainWin.setup()) { mainWin.show(); @@ -61,4 +62,3 @@ int main(int argc, char *argv[]) SDL_Quit(); return returnValue; } - diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 032f70916..56b3186ff 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,5 +1,11 @@ #include "maindialog.hpp" +#include + +#include +#include +#include +#include #include #include #include @@ -23,8 +29,8 @@ #include "graphicspage.hpp" #include "datafilespage.hpp" -MainDialog::MainDialog() - : mGameSettings(mCfgMgr) +Launcher::MainDialog::MainDialog(QWidget *parent) + : mGameSettings(mCfgMgr), QMainWindow (parent) { // Install the stylesheet font QFile file; @@ -66,10 +72,26 @@ MainDialog::MainDialog() // Remove what's this? button setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + // Add version information to bottom of the window + QString revision(OPENMW_VERSION_COMMITHASH); + QString tag(OPENMW_VERSION_TAGHASH); + + if (revision == tag) { + versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); + } else { + versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); + } + + // Add the compile date and time + versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), + QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), + QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); + createIcons(); } -void MainDialog::createIcons() +void Launcher::MainDialog::createIcons() { if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); @@ -101,15 +123,15 @@ void MainDialog::createIcons() } -void MainDialog::createPages() +void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(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->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel()); - mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex()); + mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); + mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex()); // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); @@ -121,12 +143,12 @@ void MainDialog::createPages() connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play())); - connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int))); - connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int))); + connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); + connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); } -bool MainDialog::showFirstRunDialog() +bool Launcher::MainDialog::showFirstRunDialog() { QStringList iniPaths; @@ -218,7 +240,7 @@ bool MainDialog::showFirstRunDialog() } // Create the file if it doesn't already exist, else the importer will fail - QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + QString("openmw.cfg"); + QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg"); QFile file(path); if (!file.exists()) { @@ -261,19 +283,11 @@ bool MainDialog::showFirstRunDialog() // Add a new profile if (msgBox.isChecked()) { mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); + mLauncherSettings.remove(QString("Profiles/Imported/content")); - 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); + QStringList contents = mGameSettings.values(QString("content")); + foreach (const QString &content, contents) { + mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content); } } @@ -282,7 +296,7 @@ bool MainDialog::showFirstRunDialog() return true; } -bool MainDialog::setup() +bool Launcher::MainDialog::setup() { if (!setupLauncherSettings()) return false; @@ -311,19 +325,37 @@ bool MainDialog::setup() return true; } -void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) +void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) current = previous; - pagesWidget->setCurrentIndex(iconWidget->row(current)); + int currentIndex = iconWidget->row(current); + int previousIndex = iconWidget->row(previous); + + pagesWidget->setCurrentIndex(currentIndex); + + DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); + DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); + + //special call to update/save data files page list view when it's displayed/hidden. + if (previousPage) + { + if (previousPage->objectName() == "DataFilesPage") + previousPage->saveSettings(); + } + else if (currentPage) + { + if (currentPage->objectName() == "DataFilesPage") + currentPage->loadSettings(); + } } -bool MainDialog::setupLauncherSettings() +bool Launcher::MainDialog::setupLauncherSettings() { mLauncherSettings.setMultiValueEnabled(true); - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QStringList paths; paths.append(QString("launcher.cfg")); @@ -356,7 +388,7 @@ bool MainDialog::setupLauncherSettings() } #ifndef WIN32 -bool expansions(UnshieldThread& cd) +bool Launcher::expansions(Launcher::UnshieldThread& cd) { if(cd.BloodmoonDone()) { @@ -367,7 +399,7 @@ bool expansions(UnshieldThread& cd) QMessageBox expansionsBox; expansionsBox.setText(QObject::tr("
Would you like to install expansions now ? (make sure you have the disc)
\ If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.
")); - + QAbstractButton* tribunalButton = NULL; if(!cd.TribunalDone()) tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); @@ -386,7 +418,7 @@ bool expansions(UnshieldThread& cd) { TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); + cdbox.setStandardButtons(QMessageBox::Cancel); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); @@ -405,7 +437,7 @@ bool expansions(UnshieldThread& cd) { TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); + cdbox.setStandardButtons(QMessageBox::Cancel); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); @@ -427,11 +459,37 @@ bool expansions(UnshieldThread& cd) } #endif // WIN32 -bool MainDialog::setupGameSettings() +bool Launcher::MainDialog::setupGameSettings() { - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + // Load the user config file first, separately + // So we can write it properly, uncontaminated + QString path = userPath + QLatin1String("openmw.cfg"); + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(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.readUserFile(stream); + } + + // Now the rest QStringList paths; paths.append(userPath + QString("openmw.cfg")); paths.append(QString("openmw.cfg")); @@ -467,7 +525,7 @@ bool MainDialog::setupGameSettings() foreach (const QString path, mGameSettings.getDataDirs()) { QDir dir(path); QStringList filters; - filters << "*.esp" << "*.esm"; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) dataDirs.append(path); @@ -485,12 +543,12 @@ bool MainDialog::setupGameSettings() QAbstractButton *dirSelectButton = msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); - + #ifndef WIN32 - QAbstractButton *cdSelectButton = + QAbstractButton *cdSelectButton = msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); #endif - + msgBox.exec(); @@ -505,14 +563,14 @@ bool MainDialog::setupGameSettings() #ifndef WIN32 else if(msgBox.clickedButton() == cdSelectButton) { UnshieldThread cd; - + { TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); + cdbox.setStandardButtons(QMessageBox::Cancel); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - + cd.SetMorrowindPath( QFileDialog::getOpenFileName( NULL, @@ -526,11 +584,11 @@ bool MainDialog::setupGameSettings() QObject::tr("Select where to extract files to"), QDir::currentPath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); - + cd.start(); cdbox.exec(); } - + while(expansions(cd)); selectedFile = QString::fromStdString(cd.GetMWEsmPath()); @@ -550,11 +608,11 @@ bool MainDialog::setupGameSettings() return true; } -bool MainDialog::setupGraphicsSettings() +bool Launcher::MainDialog::setupGraphicsSettings() { mGraphicsSettings.setMultiValueEnabled(false); - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); QFile localDefault(QString("settings-default.cfg")); @@ -604,7 +662,7 @@ bool MainDialog::setupGraphicsSettings() return true; } -void MainDialog::loadSettings() +void Launcher::MainDialog::loadSettings() { int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt(); int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt(); @@ -616,7 +674,7 @@ void MainDialog::loadSettings() move(posX, posY); } -void MainDialog::saveSettings() +void Launcher::MainDialog::saveSettings() { QString width = QString::number(this->width()); QString height = QString::number(this->height()); @@ -634,14 +692,14 @@ void MainDialog::saveSettings() } -bool MainDialog::writeSettings() +bool Launcher::MainDialog::writeSettings() { // Now write all config files saveSettings(); mGraphicsPage->saveSettings(); mDataFilesPage->saveSettings(); - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QDir dir(userPath); if (!dir.exists()) { @@ -727,13 +785,13 @@ bool MainDialog::writeSettings() return true; } -void MainDialog::closeEvent(QCloseEvent *event) +void Launcher::MainDialog::closeEvent(QCloseEvent *event) { writeSettings(); event->accept(); } -void MainDialog::play() +void Launcher::MainDialog::play() { if (!writeSettings()) { qApp->quit(); @@ -742,11 +800,11 @@ void MainDialog::play() if(!mGameSettings.hasMaster()) { QMessageBox msgBox; - msgBox.setWindowTitle(tr("No master file selected")); + msgBox.setWindowTitle(tr("No game file selected")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
You do not have any master files selected.

\ - OpenMW will not start without a master file selected.
")); + msgBox.setText(tr("
You do not have no game file selected.

\ + OpenMW will not start without a game file selected.
")); msgBox.exec(); return; } @@ -756,7 +814,7 @@ void MainDialog::play() qApp->quit(); } -bool MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) +bool Launcher::MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) { QString path = name; #ifdef Q_OS_WIN diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 824dff6e8..5b8e4908e 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -11,57 +11,59 @@ #include "ui_mainwindow.h" -class QListWidget; class QListWidgetItem; -class QStackedWidget; -class QStringList; -class QStringListModel; -class QString; -class PlayPage; -class GraphicsPage; -class DataFilesPage; - -class MainDialog : public QMainWindow, private Ui::MainWindow +namespace Launcher { - Q_OBJECT + class PlayPage; + class GraphicsPage; + class DataFilesPage; + class UnshieldThread; + +#ifndef WIN32 + bool expansions(Launcher::UnshieldThread& cd); +#endif -public: - MainDialog(); - bool setup(); - bool showFirstRunDialog(); + class MainDialog : public QMainWindow, private Ui::MainWindow + { + Q_OBJECT -public slots: - void changePage(QListWidgetItem *current, QListWidgetItem *previous); - void play(); + public: + explicit MainDialog(QWidget *parent = 0); + bool setup(); + bool showFirstRunDialog(); -private: - void createIcons(); - void createPages(); + public slots: + void changePage(QListWidgetItem *current, QListWidgetItem *previous); + void play(); - bool setupLauncherSettings(); - bool setupGameSettings(); - bool setupGraphicsSettings(); + private: + void createIcons(); + void createPages(); - void loadSettings(); - void saveSettings(); - bool writeSettings(); + bool setupLauncherSettings(); + bool setupGameSettings(); + bool setupGraphicsSettings(); - 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 loadSettings(); + void saveSettings(); + bool writeSettings(); - void closeEvent(QCloseEvent *event); + 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); - PlayPage *mPlayPage; - GraphicsPage *mGraphicsPage; - DataFilesPage *mDataFilesPage; + void closeEvent(QCloseEvent *event); - Files::ConfigurationManager mCfgMgr; + PlayPage *mPlayPage; + GraphicsPage *mGraphicsPage; + DataFilesPage *mDataFilesPage; - GameSettings mGameSettings; - GraphicsSettings mGraphicsSettings; - LauncherSettings mLauncherSettings; + Files::ConfigurationManager mCfgMgr; -}; + GameSettings mGameSettings; + GraphicsSettings mGraphicsSettings; + LauncherSettings mLauncherSettings; + }; +} #endif diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp index 46900c595..6cfb9686f 100644 --- a/apps/launcher/playpage.cpp +++ b/apps/launcher/playpage.cpp @@ -6,8 +6,9 @@ #include #endif -PlayPage::PlayPage(QWidget *parent) : QWidget(parent) +Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { + setObjectName ("PlayPage"); setupUi(this); // Hacks to get the stylesheet look properly @@ -17,27 +18,22 @@ PlayPage::PlayPage(QWidget *parent) : QWidget(parent) #endif profilesComboBox->setView(new QListView()); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int))); + connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int))); connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked())); } -void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model) +void Launcher::PlayPage::setProfilesModel(QAbstractItemModel *model) { profilesComboBox->setModel(model); } -void PlayPage::setProfilesComboBoxIndex(int index) +void Launcher::PlayPage::setProfilesIndex(int index) { profilesComboBox->setCurrentIndex(index); } -void PlayPage::slotCurrentIndexChanged(int index) -{ - emit profileChanged(index); -} - -void PlayPage::slotPlayClicked() +void Launcher::PlayPage::slotPlayClicked() { emit playButtonClicked(); } diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp index 4306396bd..1dc5bb0fe 100644 --- a/apps/launcher/playpage.hpp +++ b/apps/launcher/playpage.hpp @@ -9,27 +9,28 @@ class QComboBox; class QPushButton; class QAbstractItemModel; -class PlayPage : public QWidget, private Ui::PlayPage +namespace Launcher { - Q_OBJECT + class PlayPage : public QWidget, private Ui::PlayPage + { + Q_OBJECT -public: - PlayPage(QWidget *parent = 0); - void setProfilesComboBoxModel(QAbstractItemModel *model); + public: + PlayPage(QWidget *parent = 0); + void setProfilesModel(QAbstractItemModel *model); -signals: - void profileChanged(int index); - void playButtonClicked(); + signals: + void signalProfileChanged(int index); + void playButtonClicked(); -public slots: - void setProfilesComboBoxIndex(int index); + public slots: + void setProfilesIndex(int index); -private slots: - void slotCurrentIndexChanged(int index); - void slotPlayClicked(); + private slots: + void slotPlayClicked(); -}; - + }; +} #endif diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 205879bc3..e7e5cf1ea 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -9,6 +9,7 @@ #include #include + /** * Workaround for problems with whitespaces in paths in older versions of Boost library */ @@ -26,16 +27,16 @@ namespace boost #endif /* (BOOST_VERSION <= 104600) */ -GameSettings::GameSettings(Files::ConfigurationManager &cfg) +Launcher::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) { } -GameSettings::~GameSettings() +Launcher::GameSettings::~GameSettings() { } -void GameSettings::validatePaths() +void Launcher::GameSettings::validatePaths() { if (mSettings.isEmpty() || !mDataDirs.isEmpty()) return; // Don't re-validate paths if they are already parsed @@ -81,14 +82,24 @@ void GameSettings::validatePaths() } } -QStringList GameSettings::values(const QString &key, const QStringList &defaultValues) +QStringList Launcher::GameSettings::values(const QString &key, const QStringList &defaultValues) { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool GameSettings::readFile(QTextStream &stream) +bool Launcher::GameSettings::readFile(QTextStream &stream) +{ + return readFile(stream, mSettings); +} + +bool Launcher::GameSettings::readUserFile(QTextStream &stream) +{ + return readFile(stream, mUserSettings); +} + +bool Launcher::GameSettings::readFile(QTextStream &stream, QMap &settings) { QMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -106,10 +117,10 @@ bool GameSettings::readFile(QTextStream &stream) // Don't remove existing data entries if (key != QLatin1String("data")) - mSettings.remove(key); + settings.remove(key); QStringList values = cache.values(key); - values.append(mSettings.values(key)); + values.append(settings.values(key)); if (!values.contains(value)) { cache.insertMulti(key, value); @@ -117,35 +128,36 @@ bool GameSettings::readFile(QTextStream &stream) } } - if (mSettings.isEmpty()) { - mSettings = cache; // This is the first time we read a file + if (settings.isEmpty()) { + settings = 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); + settings.unite(cache); validatePaths(); return true; } -bool GameSettings::writeFile(QTextStream &stream) + +bool Launcher::GameSettings::writeFile(QTextStream &stream) { // Iterate in reverse order to preserve insertion order - QMapIterator i(mSettings); + QMapIterator i(mUserSettings); i.toBack(); while (i.hasPrevious()) { i.previous(); - if (i.key() == QLatin1String("master") || i.key() == QLatin1String("plugin")) + if (i.key() == QLatin1String("content")) continue; // Quote paths with spaces if (i.key() == QLatin1String("data") - || i.key() == QLatin1String("data-local") - || i.key() == QLatin1String("resources")) + || i.key() == QLatin1String("data-local") + || i.key() == QLatin1String("resources")) { if (i.value().contains(QChar(' '))) { @@ -161,15 +173,24 @@ bool GameSettings::writeFile(QTextStream &stream) } - QStringList masters = mSettings.values(QString("master")); - for (int i = masters.count(); i--;) { - stream << "master=" << masters.at(i) << "\n"; + QStringList content = mUserSettings.values(QString("content")); + for (int i = content.count(); i--;) { + stream << "content=" << content.at(i) << "\n"; } - QStringList plugins = mSettings.values(QString("plugin")); - for (int i = plugins.count(); i--;) { - stream << "plugin=" << plugins.at(i) << "\n"; + return true; +} + +bool Launcher::GameSettings::hasMaster() +{ + bool result = false; + QStringList content = mSettings.values(QString("content")); + for (int i = 0; i < content.count(); ++i) { + if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { + result = true; + break; + } } - return true; + return result; } diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp index 55b2107e2..df8215074 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/apps/launcher/settings/gamesettings.hpp @@ -8,55 +8,72 @@ #include -namespace Files { typedef std::vector PathContainer; - struct ConfigurationManager;} - -class GameSettings +namespace Files { -public: - GameSettings(Files::ConfigurationManager &cfg); - ~GameSettings(); + typedef std::vector PathContainer; + struct ConfigurationManager; +} - inline QString value(const QString &key, const QString &defaultValue = QString()) +namespace Launcher +{ + class GameSettings { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); - } + 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 setValue(const QString &key, const QString &value) + { + mSettings.insert(key, value); + mUserSettings.insert(key, value); + } - inline void remove(const QString &key) - { - mSettings.remove(key); - } + inline void setMultiValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insertMulti(key, value); + + values = mUserSettings.values(key); + if (!values.contains(value)) + mUserSettings.insertMulti(key, value); + } + + inline void remove(const QString &key) + { + mSettings.remove(key); + mUserSettings.remove(key); + } + + inline QStringList getDataDirs() { return mDataDirs; } + inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } + inline QString getDataLocal() {return mDataLocal; } + + bool hasMaster(); - inline QStringList getDataDirs() { return mDataDirs; } - inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } - inline QString getDataLocal() {return mDataLocal; } - inline bool hasMaster() { return mSettings.count(QString("master")) > 0; } + QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); - QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); - bool readFile(QTextStream &stream); - bool writeFile(QTextStream &stream); + bool readFile(QTextStream &stream); + bool readFile(QTextStream &stream, QMap &settings); + bool readUserFile(QTextStream &stream); -private: - Files::ConfigurationManager &mCfgMgr; + bool writeFile(QTextStream &stream); - void validatePaths(); - QMap mSettings; + private: + Files::ConfigurationManager &mCfgMgr; - QStringList mDataDirs; - QString mDataLocal; -}; + void validatePaths(); + QMap mSettings; + QMap mUserSettings; + QStringList mDataDirs; + QString mDataLocal; + }; +} #endif // GAMESETTINGS_HPP diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp index 0c5580091..9dad3dee6 100644 --- a/apps/launcher/settings/graphicssettings.cpp +++ b/apps/launcher/settings/graphicssettings.cpp @@ -5,15 +5,15 @@ #include #include -GraphicsSettings::GraphicsSettings() +Launcher::GraphicsSettings::GraphicsSettings() { } -GraphicsSettings::~GraphicsSettings() +Launcher::GraphicsSettings::~GraphicsSettings() { } -bool GraphicsSettings::writeFile(QTextStream &stream) +bool Launcher::GraphicsSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp index 3e8617849..6f7c13547 100644 --- a/apps/launcher/settings/graphicssettings.hpp +++ b/apps/launcher/settings/graphicssettings.hpp @@ -3,14 +3,16 @@ #include "settingsbase.hpp" -class GraphicsSettings : public SettingsBase > +namespace Launcher { -public: - GraphicsSettings(); - ~GraphicsSettings(); + class GraphicsSettings : public SettingsBase > + { + public: + GraphicsSettings(); + ~GraphicsSettings(); - bool writeFile(QTextStream &stream); - -}; + bool writeFile(QTextStream &stream); + }; +} #endif // GRAPHICSSETTINGS_HPP diff --git a/apps/launcher/settings/launchersettings.cpp b/apps/launcher/settings/launchersettings.cpp index 5d298e814..705453555 100644 --- a/apps/launcher/settings/launchersettings.cpp +++ b/apps/launcher/settings/launchersettings.cpp @@ -5,15 +5,17 @@ #include #include -LauncherSettings::LauncherSettings() +#include + +Launcher::LauncherSettings::LauncherSettings() { } -LauncherSettings::~LauncherSettings() +Launcher::LauncherSettings::~LauncherSettings() { } -QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags) +QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) { QMap settings = SettingsBase::getSettings(); @@ -34,7 +36,7 @@ QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags) return result; } -QStringList LauncherSettings::subKeys(const QString &key) +QStringList Launcher::LauncherSettings::subKeys(const QString &key) { QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); @@ -44,12 +46,9 @@ QStringList LauncherSettings::subKeys(const QString &key) 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); @@ -61,7 +60,7 @@ QStringList LauncherSettings::subKeys(const QString &key) return result; } -bool LauncherSettings::writeFile(QTextStream &stream) +bool Launcher::LauncherSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); diff --git a/apps/launcher/settings/launchersettings.hpp b/apps/launcher/settings/launchersettings.hpp index 60c6f86bc..8acc389a9 100644 --- a/apps/launcher/settings/launchersettings.hpp +++ b/apps/launcher/settings/launchersettings.hpp @@ -3,17 +3,19 @@ #include "settingsbase.hpp" -class LauncherSettings : public SettingsBase > +namespace Launcher { -public: - LauncherSettings(); - ~LauncherSettings(); + class LauncherSettings : public SettingsBase > + { + public: + LauncherSettings(); + ~LauncherSettings(); - QStringList subKeys(const QString &key); - QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); + QStringList subKeys(const QString &key); + QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); - bool writeFile(QTextStream &stream); - -}; + bool writeFile(QTextStream &stream); + }; +} #endif // LAUNCHERSETTINGS_HPP diff --git a/apps/launcher/settings/settingsbase.hpp b/apps/launcher/settings/settingsbase.hpp index ed8ada56c..3a1cf8e30 100644 --- a/apps/launcher/settings/settingsbase.hpp +++ b/apps/launcher/settings/settingsbase.hpp @@ -7,103 +7,105 @@ #include #include -template -class SettingsBase +namespace Launcher { + template + class SettingsBase + { -public: - SettingsBase() { mMultiValue = false; } - ~SettingsBase() {} + public: + SettingsBase() { mMultiValue = false; } + ~SettingsBase() {} - inline QString value(const QString &key, const QString &defaultValue = QString()) - { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); - } + 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 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 setMultiValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insertMulti(key, value); + } - inline void setMultiValueEnabled(bool enable) - { - mMultiValue = enable; - } + inline void setMultiValueEnabled(bool enable) + { + mMultiValue = enable; + } - inline void remove(const QString &key) - { - mSettings.remove(key); - } + inline void remove(const QString &key) + { + mSettings.remove(key); + } - Map getSettings() {return mSettings;} + Map getSettings() {return mSettings;} - bool readFile(QTextStream &stream) - { - mCache.clear(); + bool readFile(QTextStream &stream) + { + mCache.clear(); - QString sectionPrefix; + QString sectionPrefix; - QRegExp sectionRe("^\\[([^]]+)\\]"); - QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + QRegExp sectionRe("^\\[([^]]+)\\]"); + QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); - while (!stream.atEnd()) { - QString line = stream.readLine(); + while (!stream.atEnd()) { + QString line = stream.readLine(); - if (line.isEmpty() || line.startsWith("#")) - continue; + if (line.isEmpty() || line.startsWith("#")) + continue; - if (sectionRe.exactMatch(line)) { - sectionPrefix = sectionRe.cap(1); - sectionPrefix.append("/"); - continue; - } + if (sectionRe.exactMatch(line)) { + sectionPrefix = sectionRe.cap(1); + sectionPrefix.append("/"); + continue; + } - if (keyRe.indexIn(line) != -1) { + if (keyRe.indexIn(line) != -1) { - QString key = keyRe.cap(1).trimmed(); - QString value = keyRe.cap(2).trimmed(); + QString key = keyRe.cap(1).trimmed(); + QString value = keyRe.cap(2).trimmed(); - if (!sectionPrefix.isEmpty()) - key.prepend(sectionPrefix); + if (!sectionPrefix.isEmpty()) + key.prepend(sectionPrefix); - mSettings.remove(key); + mSettings.remove(key); - QStringList values = mCache.values(key); + QStringList values = mCache.values(key); - if (!values.contains(value)) { - if (mMultiValue) { - mCache.insertMulti(key, value); - } else { - mCache.insert(key, value); + if (!values.contains(value)) { + if (mMultiValue) { + mCache.insertMulti(key, value); + } else { + mCache.insert(key, value); + } } } } - } - if (mSettings.isEmpty()) { - mSettings = mCache; // This is the first time we read a file + 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; } - // Merge the changed keys with those which didn't - mSettings.unite(mCache); - return true; - } - -private: - Map mSettings; - Map mCache; - - bool mMultiValue; -}; + private: + Map mSettings; + Map mCache; + bool mMultiValue; + }; +} #endif // SETTINGSBASE_HPP diff --git a/apps/launcher/textslotmsgbox.cpp b/apps/launcher/textslotmsgbox.cpp index 0607d1cc6..62d9cf576 100644 --- a/apps/launcher/textslotmsgbox.cpp +++ b/apps/launcher/textslotmsgbox.cpp @@ -1,6 +1,6 @@ #include "textslotmsgbox.hpp" -void TextSlotMsgBox::setTextSlot(const QString& string) +void Launcher::TextSlotMsgBox::setTextSlot(const QString& string) { setText(string); } diff --git a/apps/launcher/textslotmsgbox.hpp b/apps/launcher/textslotmsgbox.hpp index a29e2c354..a0fefaa25 100644 --- a/apps/launcher/textslotmsgbox.hpp +++ b/apps/launcher/textslotmsgbox.hpp @@ -3,11 +3,13 @@ #include -class TextSlotMsgBox : public QMessageBox +namespace Launcher { -Q_OBJECT - public slots: - void setTextSlot(const QString& string); -}; - + class TextSlotMsgBox : public QMessageBox + { + Q_OBJECT + public slots: + void setTextSlot(const QString& string); + }; +} #endif diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 69b241365..52f935710 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -235,7 +235,7 @@ namespace { for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) return dir->path(); } } @@ -243,7 +243,7 @@ namespace { for ( bfs::directory_iterator end, dir(in); dir != end; ++dir ) { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) return dir->path(); } } @@ -255,7 +255,7 @@ namespace { for(bfs::directory_iterator end, dir(in); dir != end; ++dir) { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) return true; } @@ -292,30 +292,30 @@ namespace } -bool UnshieldThread::SetMorrowindPath(const std::string& path) +bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path) { mMorrowindPath = path; return true; } -bool UnshieldThread::SetTribunalPath(const std::string& path) +bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path) { mTribunalPath = path; return true; } -bool UnshieldThread::SetBloodmoonPath(const std::string& path) +bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path) { mBloodmoonPath = path; return true; } -void UnshieldThread::SetOutputPath(const std::string& path) +void Launcher::UnshieldThread::SetOutputPath(const std::string& path) { mOutputPath = path; } -bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) +bool Launcher::UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) { bool success; bfs::path dirname; @@ -349,7 +349,7 @@ bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, cons return success; } -void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) +void Launcher::UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) { Unshield * unshield; unshield = unshield_open(cab.c_str()); @@ -369,7 +369,7 @@ void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_d } -bool UnshieldThread::extract() +bool Launcher::UnshieldThread::extract() { bfs::path outputDataFilesDir = mOutputPath; outputDataFilesDir /= "Data Files"; @@ -475,7 +475,7 @@ bool UnshieldThread::extract() return true; } -void UnshieldThread::Done() +void Launcher::UnshieldThread::Done() { // Get rid of unnecessary files bfs::remove_all(mOutputPath / "extract-temp"); @@ -491,28 +491,28 @@ void UnshieldThread::Done() bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); } -std::string UnshieldThread::GetMWEsmPath() +std::string Launcher::UnshieldThread::GetMWEsmPath() { return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); } -bool UnshieldThread::TribunalDone() +bool Launcher::UnshieldThread::TribunalDone() { return mTribunalDone; } -bool UnshieldThread::BloodmoonDone() +bool Launcher::UnshieldThread::BloodmoonDone() { return mBloodmoonDone; } -void UnshieldThread::run() +void Launcher::UnshieldThread::run() { extract(); emit close(); } -UnshieldThread::UnshieldThread() +Launcher::UnshieldThread::UnshieldThread() { unshield_set_log_level(0); mMorrowindDone = false; diff --git a/apps/launcher/unshieldthread.hpp b/apps/launcher/unshieldthread.hpp index 655cb5b53..de6a32b44 100644 --- a/apps/launcher/unshieldthread.hpp +++ b/apps/launcher/unshieldthread.hpp @@ -7,50 +7,52 @@ #include -class UnshieldThread : public QThread +namespace Launcher { - Q_OBJECT + class UnshieldThread : public QThread + { + Q_OBJECT - public: - bool SetMorrowindPath(const std::string& path); - bool SetTribunalPath(const std::string& path); - bool SetBloodmoonPath(const std::string& path); + public: + bool SetMorrowindPath(const std::string& path); + bool SetTribunalPath(const std::string& path); + bool SetBloodmoonPath(const std::string& path); - void SetOutputPath(const std::string& path); - - bool extract(); + void SetOutputPath(const std::string& path); - bool TribunalDone(); - bool BloodmoonDone(); + bool extract(); - void Done(); + bool TribunalDone(); + bool BloodmoonDone(); - std::string GetMWEsmPath(); + void Done(); - UnshieldThread(); + std::string GetMWEsmPath(); - private: + UnshieldThread(); - void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); - bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); - - boost::filesystem::path mMorrowindPath; - boost::filesystem::path mTribunalPath; - boost::filesystem::path mBloodmoonPath; + private: - bool mMorrowindDone; - bool mTribunalDone; - bool mBloodmoonDone; + void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); + bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); - boost::filesystem::path mOutputPath; + boost::filesystem::path mMorrowindPath; + boost::filesystem::path mTribunalPath; + boost::filesystem::path mBloodmoonPath; + bool mMorrowindDone; + bool mTribunalDone; + bool mBloodmoonDone; - protected: - virtual void run(); + boost::filesystem::path mOutputPath; - signals: - void signalGUI(QString); - void close(); -}; + protected: + virtual void run(); + + signals: + void signalGUI(QString); + void close(); + }; +} #endif diff --git a/apps/launcher/utils/checkablemessagebox.cpp b/apps/launcher/utils/checkablemessagebox.cpp index 41207a8de..2f775af57 100644 --- a/apps/launcher/utils/checkablemessagebox.cpp +++ b/apps/launcher/utils/checkablemessagebox.cpp @@ -54,72 +54,61 @@ Emulates the QMessageBox API with static conveniences. The message label can open external URLs. */ - -class CheckableMessageBoxPrivate -{ -public: - CheckableMessageBoxPrivate(QDialog *q) +Launcher::CheckableMessageBoxPrivate::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; -}; +{ + 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(Launcher::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); +} -CheckableMessageBox::CheckableMessageBox(QWidget *parent) : +Launcher::CheckableMessageBox::CheckableMessageBox(QWidget *parent) : QDialog(parent), - d(new CheckableMessageBoxPrivate(this)) + d(new Launcher::CheckableMessageBoxPrivate(this)) { setModal(true); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -129,102 +118,102 @@ CheckableMessageBox::CheckableMessageBox(QWidget *parent) : SLOT(slotClicked(QAbstractButton*))); } -CheckableMessageBox::~CheckableMessageBox() +Launcher::CheckableMessageBox::~CheckableMessageBox() { delete d; } -void CheckableMessageBox::slotClicked(QAbstractButton *b) +void Launcher::CheckableMessageBox::slotClicked(QAbstractButton *b) { d->clickedButton = b; } -QAbstractButton *CheckableMessageBox::clickedButton() const +QAbstractButton *Launcher::CheckableMessageBox::clickedButton() const { return d->clickedButton; } -QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const +QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::clickedStandardButton() const { if (d->clickedButton) return d->buttonBox->standardButton(d->clickedButton); return QDialogButtonBox::NoButton; } -QString CheckableMessageBox::text() const +QString Launcher::CheckableMessageBox::text() const { return d->messageLabel->text(); } -void CheckableMessageBox::setText(const QString &t) +void Launcher::CheckableMessageBox::setText(const QString &t) { d->messageLabel->setText(t); } -QPixmap CheckableMessageBox::iconPixmap() const +QPixmap Launcher::CheckableMessageBox::iconPixmap() const { if (const QPixmap *p = d->pixmapLabel->pixmap()) return QPixmap(*p); return QPixmap(); } -void CheckableMessageBox::setIconPixmap(const QPixmap &p) +void Launcher::CheckableMessageBox::setIconPixmap(const QPixmap &p) { d->pixmapLabel->setPixmap(p); d->pixmapLabel->setVisible(!p.isNull()); } -bool CheckableMessageBox::isChecked() const +bool Launcher::CheckableMessageBox::isChecked() const { return d->checkBox->isChecked(); } -void CheckableMessageBox::setChecked(bool s) +void Launcher::CheckableMessageBox::setChecked(bool s) { d->checkBox->setChecked(s); } -QString CheckableMessageBox::checkBoxText() const +QString Launcher::CheckableMessageBox::checkBoxText() const { return d->checkBox->text(); } -void CheckableMessageBox::setCheckBoxText(const QString &t) +void Launcher::CheckableMessageBox::setCheckBoxText(const QString &t) { d->checkBox->setText(t); } -bool CheckableMessageBox::isCheckBoxVisible() const +bool Launcher::CheckableMessageBox::isCheckBoxVisible() const { return d->checkBox->isVisible(); } -void CheckableMessageBox::setCheckBoxVisible(bool v) +void Launcher::CheckableMessageBox::setCheckBoxVisible(bool v) { d->checkBox->setVisible(v); } -QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const +QDialogButtonBox::StandardButtons Launcher::CheckableMessageBox::standardButtons() const { return d->buttonBox->standardButtons(); } -void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) +void Launcher::CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) { d->buttonBox->setStandardButtons(s); } -QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const +QPushButton *Launcher::CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const { return d->buttonBox->button(b); } -QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) +QPushButton *Launcher::CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) { return d->buttonBox->addButton(text, role); } -QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const +QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::defaultButton() const { foreach (QAbstractButton *b, d->buttonBox->buttons()) if (QPushButton *pb = qobject_cast(b)) @@ -233,7 +222,7 @@ QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const return QDialogButtonBox::NoButton; } -void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) +void Launcher::CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) { if (QPushButton *b = d->buttonBox->button(s)) { b->setDefault(true); @@ -242,7 +231,7 @@ void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) } QDialogButtonBox::StandardButton -CheckableMessageBox::question(QWidget *parent, +Launcher::CheckableMessageBox::question(QWidget *parent, const QString &title, const QString &question, const QString &checkBoxText, @@ -263,7 +252,7 @@ CheckableMessageBox::question(QWidget *parent, return mb.clickedStandardButton(); } -QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) +QMessageBox::StandardButton Launcher::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) { return static_cast(int(db)); } diff --git a/apps/launcher/utils/checkablemessagebox.hpp b/apps/launcher/utils/checkablemessagebox.hpp index 93fd43fe1..09a501b9c 100644 --- a/apps/launcher/utils/checkablemessagebox.hpp +++ b/apps/launcher/utils/checkablemessagebox.hpp @@ -34,67 +34,83 @@ #include #include -class CheckableMessageBoxPrivate; +class QCheckBox; -class CheckableMessageBox : public QDialog +namespace Launcher { - 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; -}; - + class CheckableMessageBoxPrivate + { + public: + + QLabel *pixmapLabel; + QLabel *messageLabel; + QCheckBox *checkBox; + QDialogButtonBox *buttonBox; + QAbstractButton *clickedButton; + + public: + CheckableMessageBoxPrivate(QDialog *q); + }; + + 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/components/fileorderlist/utils/lineedit.cpp b/apps/launcher/utils/lineedit.cpp similarity index 66% rename from components/fileorderlist/utils/lineedit.cpp rename to apps/launcher/utils/lineedit.cpp index b0f339589..348707580 100644 --- a/components/fileorderlist/utils/lineedit.cpp +++ b/apps/launcher/utils/lineedit.cpp @@ -1,10 +1,12 @@ -#include -#include - #include "lineedit.hpp" LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) +{ + setupClearButton(); +} + +void LineEdit::setupClearButton() { mClearButton = new QToolButton(this); QPixmap pixmap(":images/clear.png"); @@ -15,13 +17,6 @@ LineEdit::LineEdit(QWidget *parent) 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("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)); } void LineEdit::resizeEvent(QResizeEvent *) diff --git a/components/fileorderlist/utils/lineedit.hpp b/apps/launcher/utils/lineedit.hpp similarity index 83% rename from components/fileorderlist/utils/lineedit.hpp rename to apps/launcher/utils/lineedit.hpp index 14bd7b1b4..2dd7da32b 100644 --- a/components/fileorderlist/utils/lineedit.hpp +++ b/apps/launcher/utils/lineedit.hpp @@ -11,6 +11,9 @@ #define LINEEDIT_H #include +#include +#include +#include class QToolButton; @@ -18,6 +21,8 @@ class LineEdit : public QLineEdit { Q_OBJECT + QString mPlaceholderText; + public: LineEdit(QWidget *parent = 0); @@ -27,8 +32,10 @@ protected: private slots: void updateClearButton(const QString &text); -private: +protected: QToolButton *mClearButton; + + void setupClearButton(); }; #endif // LIENEDIT_H diff --git a/components/fileorderlist/utils/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp similarity index 70% rename from components/fileorderlist/utils/profilescombobox.cpp rename to apps/launcher/utils/profilescombobox.cpp index c3ff953ae..c14330724 100644 --- a/components/fileorderlist/utils/profilescombobox.cpp +++ b/apps/launcher/utils/profilescombobox.cpp @@ -5,18 +5,12 @@ #include #include "profilescombobox.hpp" -#include "comboboxlineedit.hpp" ProfilesComboBox::ProfilesComboBox(QWidget *parent) : - QComboBox(parent) + ContentSelectorView::ComboBox(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))); + connect(this, SIGNAL(activated(int)), this, + SLOT(slotIndexChangedByUser(int))); setInsertPolicy(QComboBox::NoInsert); } @@ -37,6 +31,7 @@ void ProfilesComboBox::setEditEnabled(bool editable) setValidator(mValidator); ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); + setLineEdit(edit); setCompleter(0); @@ -45,6 +40,9 @@ void ProfilesComboBox::setEditEnabled(bool editable) connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); + + connect (lineEdit(), SIGNAL(textChanged(QString)), this, + SIGNAL (signalProfileTextChanged (QString))); } void ProfilesComboBox::slotTextChanged(const QString &text) @@ -82,11 +80,20 @@ void ProfilesComboBox::slotEditingFinished() emit(profileRenamed(previous, current)); } -void ProfilesComboBox::slotIndexChanged(int index) +void ProfilesComboBox::slotIndexChangedByUser(int index) { if (index == -1) return; - emit(profileChanged(mOldProfile, currentText())); - mOldProfile = itemText(index); + emit (signalProfileChanged(mOldProfile, currentText())); + mOldProfile = currentText(); +} + +ProfilesComboBox::ComboBoxLineEdit::ComboBoxLineEdit (QWidget *parent) + : LineEdit (parent) +{ + 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)); } diff --git a/apps/launcher/utils/profilescombobox.hpp b/apps/launcher/utils/profilescombobox.hpp new file mode 100644 index 000000000..7b83c41b2 --- /dev/null +++ b/apps/launcher/utils/profilescombobox.hpp @@ -0,0 +1,47 @@ +#ifndef PROFILESCOMBOBOX_HPP +#define PROFILESCOMBOBOX_HPP + +#include "components/contentselector/view/combobox.hpp" +#include "lineedit.hpp" + +#include + +class QString; + +class ProfilesComboBox : public ContentSelectorView::ComboBox +{ + Q_OBJECT + +public: + class ComboBoxLineEdit : public LineEdit + { + public: + explicit ComboBoxLineEdit (QWidget *parent = 0); + }; + +public: + + explicit ProfilesComboBox(QWidget *parent = 0); + void setEditEnabled(bool editable); + void setCurrentProfile(int index) + { + ComboBox::setCurrentIndex(index); + mOldProfile = currentText(); + } + +signals: + void signalProfileTextChanged(const QString &item); + void signalProfileChanged(const QString &previous, const QString ¤t); + void signalProfileChanged(int index); + void profileRenamed(const QString &oldName, const QString &newName); + +private slots: + + void slotEditingFinished(); + void slotIndexChangedByUser(int index); + void slotTextChanged(const QString &text); + +private: + QString mOldProfile; +}; +#endif // PROFILESCOMBOBOX_HPP diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index a4b36b95e..76cbe32d0 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -7,19 +7,18 @@ #include #include -#include - -TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : +Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); mButtonBox = new QDialogButtonBox(this); mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new LineEdit(this); + mLineEdit = new DialogLineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(0); @@ -38,34 +37,51 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid Q_UNUSED(title); #endif - setOkButtonEnabled(false); setModal(true); connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(mLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateOkButton(QString))); } -int TextInputDialog::exec() +int Launcher::TextInputDialog::exec() { mLineEdit->clear(); mLineEdit->setFocus(); return QDialog::exec(); } -void TextInputDialog::setOkButtonEnabled(bool enabled) +QString Launcher::TextInputDialog::getText() const { - QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); - okButton->setEnabled(enabled); + return mLineEdit->text(); +} - QPalette *palette = new QPalette(); - palette->setColor(QPalette::Text,Qt::red); +void Launcher::TextInputDialog::slotUpdateOkButton(QString text) +{ + bool enabled = !(text.isEmpty()); + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); - if (enabled) { + if (enabled) mLineEdit->setPalette(QApplication::palette()); - } else { + else + { // Existing profile name, make the text red + QPalette *palette = new QPalette(); + palette->setColor(QPalette::Text,Qt::red); mLineEdit->setPalette(*palette); } +} + +Launcher::TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) : + LineEdit (parent) +{ + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + + 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)); } diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index cbb453ac8..bb01778be 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -2,27 +2,39 @@ #define TEXTINPUTDIALOG_HPP #include -//#include "lineedit.hpp" + +#include "lineedit.hpp" class QDialogButtonBox; -class LineEdit; -class TextInputDialog : public QDialog +namespace Launcher { - Q_OBJECT -public: - explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - inline LineEdit *lineEdit() { return mLineEdit; } - void setOkButtonEnabled(bool enabled); + class TextInputDialog : public QDialog + { + Q_OBJECT + + class DialogLineEdit : public LineEdit + { + public: + explicit DialogLineEdit (QWidget *parent = 0); + }; + + DialogLineEdit *mLineEdit; + QDialogButtonBox *mButtonBox; + + public: + + explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); + ~TextInputDialog () {} - LineEdit *mLineEdit; + QString getText() const; - int exec(); + int exec(); -private: - QDialogButtonBox *mButtonBox; + private slots: + void slotUpdateOkButton(QString text); - -}; + }; +} #endif // TEXTINPUTDIALOG_HPP diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 8732b3eab..648ab3ebe 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -16,7 +16,7 @@ MwIniImporter::MwIniImporter() const char *map[][2] = { { "fps", "General:Show FPS" }, - { "nosound", "General:Disable Audio" }, + { "no-sound", "General:Disable Audio" }, { 0, 0 } }; const char *fallback[] = { @@ -623,6 +623,17 @@ MwIniImporter::MwIniImporter() "Moons:Masser Fade Out Finish", "Moons:Script Color", + // blood + "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", + 0 }; @@ -813,8 +824,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const { - std::vector esmFiles; - std::vector espFiles; + std::vector contentFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); @@ -832,29 +842,19 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) co std::string filetype(entry->substr(entry->length()-3)); Misc::StringUtils::toLower(filetype); - if(filetype.compare("esm") == 0) { - esmFiles.push_back(*entry); - } - else if(filetype.compare("esp") == 0) { - espFiles.push_back(*entry); + if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { + contentFiles.push_back(*entry); } } gameFile = ""; } - cfg.erase("master"); - cfg.insert( std::make_pair > ("master", std::vector() ) ); - - for(std::vector::const_iterator it=esmFiles.begin(); it!=esmFiles.end(); ++it) { - cfg["master"].push_back(*it); - } - - cfg.erase("plugin"); - cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + cfg.erase("content"); + cfg.insert( std::make_pair("content", std::vector() ) ); - for(std::vector::const_iterator it=espFiles.begin(); it!=espFiles.end(); ++it) { - cfg["plugin"].push_back(*it); + for(std::vector::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) { + cfg["content"].push_back(*it); } } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f918cfebf..5c3cd0dcc 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -5,11 +5,11 @@ opencs_units (. editor) set (CMAKE_BUILD_TYPE DEBUG) opencs_units (model/doc - document + document operation saving ) opencs_units_noqt (model/doc - documentmanager + documentmanager stage savingstate savingstages ) opencs_hdrs_noqt (model/doc @@ -24,27 +24,27 @@ opencs_units (model/world opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection - refidadapter refiddata refidadapterimp ref collectionbase refcollection columns + refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection ) opencs_hdrs_noqt (model/world - columnimp idcollection collection + columnimp idcollection collection info ) opencs_units (model/tools - tools operation reportmodel + tools reportmodel ) opencs_units_noqt (model/tools - stage verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck - birthsigncheck spellcheck + mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck + birthsigncheck spellcheck referenceablecheck ) opencs_units (view/doc - viewmanager view operations operation subview startup filedialog newgame filewidget - adjusterwidget + viewmanager view operations operation subview startup filedialog newgame + filewidget adjusterwidget ) @@ -60,13 +60,17 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode + scenetoolmode infocreator ) +opencs_units (view/render + scenewidget + ) + opencs_units_noqt (view/world dialoguesubview subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator + scripthighlighter idvalidator dialoguecreator ) @@ -124,11 +128,13 @@ opencs_units (view/filter set (OPENCS_US ) -set (OPENCS_RES ../../files/opencs/resources.qrc - ../../files/launcher/launcher.qrc +set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc + ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc ) -set (OPENCS_UI ../../files/ui/datafilespage.ui +set (OPENCS_UI + ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui + ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui ) source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR}) @@ -137,7 +143,7 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED) +find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED) include(${QT_USE_FILE}) qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) @@ -146,15 +152,46 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(APPLE) + set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns) +else() + set (OPENCS_MAC_ICON "") +endif(APPLE) + add_executable(opencs + MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} + ${OPENCS_MAC_ICON} ) +if(APPLE) + set_target_properties(opencs PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" + OUTPUT_NAME "OpenCS" + MACOSX_BUNDLE_ICON_FILE "opencs.icns" + MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" + MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} + MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} + ) + + set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) +endif(APPLE) + target_link_libraries(opencs ${Boost_LIBRARIES} ${QT_LIBRARIES} components ) + +if(DPKG_PROGRAM) + INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) +endif() + +if(APPLE) + INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) +endif() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9a6832ec0..44926610b 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -6,17 +6,21 @@ #include #include +#include +#include + #include "model/doc/document.hpp" #include "model/world/data.hpp" - -CS::Editor::Editor() : mViewManager (mDocumentManager) +CS::Editor::Editor() + : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) { mIpcServerName = "org.openmw.OpenCS"; setupDataFiles(); mNewGame.setLocalData (mLocal); + mFileDialog.setLocalData (mLocal); connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); @@ -28,23 +32,27 @@ CS::Editor::Editor() : mViewManager (mDocumentManager) connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); - connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); - connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile())); + connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)), + this, SLOT(openFiles (const boost::filesystem::path&))); + + connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)), + this, SLOT(createNewFile (const boost::filesystem::path&))); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), - this, SLOT (createNewGame (const boost::filesystem::path&))); + this, SLOT (createNewGame (const boost::filesystem::path&))); } void CS::Editor::setupDataFiles() { boost::program_options::variables_map variables; - boost::program_options::options_description desc; + boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); 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")); + ("encoding", boost::program_options::value()->default_value("win1252")) + ("resources", boost::program_options::value()->default_value("resources")); boost::program_options::notify(variables); @@ -78,14 +86,13 @@ void CS::Editor::setupDataFiles() return; } - // Set the charset for reading the esm/esp files - QString encoding = QString::fromStdString(variables["encoding"].as()); - mFileDialog.setEncoding(encoding); - dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + mDocumentManager.setResourceDir (variables["resources"].as()); + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { + QString path = QString::fromStdString(iter->string()); mFileDialog.addFiles(path); } @@ -109,48 +116,39 @@ void CS::Editor::createGame() void CS::Editor::createAddon() { mStartup.hide(); - - mFileDialog.newFile(); + mFileDialog.showDialog (CSVDoc::ContentAction_New); } void CS::Editor::loadDocument() { mStartup.hide(); - - mFileDialog.openFile(); + mFileDialog.showDialog (CSVDoc::ContentAction_Edit); } -void CS::Editor::openFiles() +void CS::Editor::openFiles (const boost::filesystem::path &savePath) { std::vector files; - QStringList paths = mFileDialog.checkedItemsPaths(); - foreach (const QString &path, paths) { + foreach (const QString &path, mFileDialog.selectedFilePaths()) files.push_back(path.toStdString()); - } - /// \todo Get the save path from the file dialogue - - CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false); + CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false); mViewManager.addView (document); mFileDialog.hide(); } -void CS::Editor::createNewFile() +void CS::Editor::createNewFile (const boost::filesystem::path &savePath) { std::vector files; - QStringList paths = mFileDialog.checkedItemsPaths(); - foreach (const QString &path, paths) { + foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); } - files.push_back(mFileDialog.fileName().toStdString()); - - /// \todo Get the save path from the file dialogue. + files.push_back(mFileDialog.filename().toStdString()); - CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); + CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true); mViewManager.addView (document); mFileDialog.hide(); @@ -212,6 +210,20 @@ int CS::Editor::run() if (mLocal.empty()) return 1; + // TODO: setting + Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName("OpenGL Rendering Subsystem")); + + Ogre::Root::getSingleton().initialise(false); + + // Create a hidden background window to keep resources + Ogre::NameValuePairList params; + params.insert(std::make_pair("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 = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, ¶ms); + hiddenWindow->setActive(false); + mStartup.show(); QApplication::setQuitOnLastWindowClosed (true); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index c83b2a685..930aa9d64 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -26,15 +26,15 @@ namespace CS { Q_OBJECT + Files::ConfigurationManager mCfgMgr; CSMSettings::UserSettings mUserSettings; CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; CSVDoc::NewGameDialogue mNewGame; CSVSettings::UserSettingsDialog mSettings; - FileDialog mFileDialog; + CSVDoc::FileDialog mFileDialog; - Files::ConfigurationManager mCfgMgr; boost::filesystem::path mLocal; void setupDataFiles(); @@ -59,8 +59,8 @@ namespace CS void createAddon(); void loadDocument(); - void openFiles(); - void createNewFile(); + void openFiles (const boost::filesystem::path &path); + void createNewFile (const boost::filesystem::path& path); void createNewGame (const boost::filesystem::path& file); void showStartup(); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ef7123c20..57eaf2d25 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -7,6 +7,12 @@ #include #include +#include + +#ifdef Q_OS_MAC +#include +#endif + class Application : public QApplication { private: @@ -33,7 +39,32 @@ class Application : public QApplication int main(int argc, char *argv[]) { Q_INIT_RESOURCE (resources); + + // TODO: Ogre startup shouldn't be here, but it currently has to: + // SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :( + Application mApplication (argc, argv); + OgreInit::OgreInit ogreInit; + ogreInit.init("./opencsOgre.log"); // TODO log path? + +#ifdef Q_OS_MAC + QDir dir(QCoreApplication::applicationDirPath()); + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + QDir::setCurrent(dir.absolutePath()); + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + mApplication.setLibraryPaths(libraryPaths); +#endif mApplication.setWindowIcon (QIcon (":./opencs.png")); @@ -42,7 +73,7 @@ int main(int argc, char *argv[]) if(!editor.makeIPCServer()) { editor.connectToIPCServer(); - return 0; + // return 0; } return editor.run(); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index d7138f671..3ef14ee7e 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1,8 +1,15 @@ #include "document.hpp" + #include +#include + +#ifndef Q_MOC_RUN +#include +#endif + void CSMDoc::Document::load (const std::vector::const_iterator& begin, - const std::vector::const_iterator& end, bool lastAsModified) + const std::vector::const_iterator& end, bool lastAsModified) { assert (begin!=end); @@ -12,10 +19,10 @@ void CSMDoc::Document::load (const std::vector::const_i --end2; for (std::vector::const_iterator iter (begin); iter!=end2; ++iter) - getData().loadFile (*iter, true); + getData().loadFile (*iter, true, false); if (lastAsModified) - getData().loadFile (*end2, false); + getData().loadFile (*end2, false, false); } void CSMDoc::Document::addGmsts() @@ -2058,9 +2065,9 @@ void CSMDoc::Document::addOptionalGlobals() { static const char *sGlobals[] = { - "dayspassed", - "pcwerewolf", - "pcyear", + "DaysPassed", + "PCWerewolf", + "PCYear", 0 }; @@ -2137,11 +2144,86 @@ void CSMDoc::Document::createBase() getData().getSkills().add (record); } + + static const char *sVoice[] = + { + "Intruder", + "Attack", + "Hello", + "Thief", + "Alarm", + "Idle", + "Flee", + "Hit", + 0 + }; + + for (int i=0; sVoice[i]; ++i) + { + ESM::Dialogue record; + record.mId = sVoice[i]; + record.mType = ESM::Dialogue::Voice; + record.blank(); + + getData().getTopics().add (record); + } + + static const char *sGreetings[] = + { + "Greeting 0", + "Greeting 1", + "Greeting 2", + "Greeting 3", + "Greeting 4", + "Greeting 5", + "Greeting 6", + "Greeting 7", + "Greeting 8", + "Greeting 9", + 0 + }; + + for (int i=0; sGreetings[i]; ++i) + { + ESM::Dialogue record; + record.mId = sGreetings[i]; + record.mType = ESM::Dialogue::Greeting; + record.blank(); + + getData().getTopics().add (record); + } + + static const char *sPersuasion[] = + { + "Intimidate Success", + "Intimidate Fail", + "Service Refusal", + "Admire Success", + "Taunt Success", + "Bribe Success", + "Info Refusal", + "Admire Fail", + "Taunt Fail", + "Bribe Fail", + 0 + }; + + for (int i=0; sPersuasion[i]; ++i) + { + ESM::Dialogue record; + record.mId = sPersuasion[i]; + record.mType = ESM::Dialogue::Persuasion; + record.blank(); + + getData().getTopics().add (record); + } } -CSMDoc::Document::Document (const std::vector& files, - const boost::filesystem::path& savePath, bool new_) -: mSavePath (savePath), mTools (mData) +CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_) + : mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir), + mProjectPath ((configuration.getUserDataPath() / "projects") / + (savePath.filename().string() + ".project")), + mSaving (*this, mProjectPath) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2158,6 +2240,44 @@ CSMDoc::Document::Document (const std::vector& files, load (files.begin(), end, !new_); } + if (new_) + { + mData.setDescription (""); + mData.setAuthor (""); + } + + bool filtersFound = false; + + if (boost::filesystem::exists (mProjectPath)) + { + filtersFound = true; + } + else + { + boost::filesystem::path locCustomFiltersPath (configuration.getUserDataPath()); + locCustomFiltersPath /= "defaultfilters"; + + if (boost::filesystem::exists(locCustomFiltersPath)) + { + boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath); + filtersFound = true; + } + else + { + boost::filesystem::path filters(mResDir); + filters /= "defaultfilters"; + + if (boost::filesystem::exists(filters)) + { + boost::filesystem::copy_file(filters, mProjectPath); + filtersFound = true; + } + } + } + + if (filtersFound) + getData().loadFile (mProjectPath, false, true); + addOptionalGmsts(); addOptionalGlobals(); @@ -2166,9 +2286,10 @@ CSMDoc::Document::Document (const std::vector& files, 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())); + connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); + connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int))); + connect (&mSaving, SIGNAL (reportMessage (const QString&, int)), + this, SLOT (reportMessage (const QString&, int))); } CSMDoc::Document::~Document() @@ -2187,7 +2308,7 @@ int CSMDoc::Document::getState() const if (!mUndoStack.isClean()) state |= State_Modified; - if (mSaveCount) + if (mSaving.isRunning()) state |= State_Locked | State_Saving | State_Operation; if (int operations = mTools.getRunningOperations()) @@ -2201,12 +2322,20 @@ const boost::filesystem::path& CSMDoc::Document::getSavePath() const return mSavePath; } +const std::vector& CSMDoc::Document::getContentFiles() const +{ + return mContentFiles; +} + void CSMDoc::Document::save() { - mSaveCount = 1; - mSaveTimer.start (500); + if (mSaving.isRunning()) + throw std::logic_error ( + "Failed to initiate save, because a save operation is already running."); + + mSaving.start(); + emit stateChanged (getState(), this); - emit progress (1, 16, State_Saving, 1, this); } CSMWorld::UniversalId CSMDoc::Document::verify() @@ -2218,44 +2347,26 @@ CSMWorld::UniversalId CSMDoc::Document::verify() void CSMDoc::Document::abortOperation (int type) { - mTools.abortOperation (type); - if (type==State_Saving) - { - mSaveCount=0; - mSaveTimer.stop(); - emit stateChanged (getState(), this); - } + mSaving.abort(); + else + mTools.abortOperation (type); } - void CSMDoc::Document::modificationStateChanged (bool clean) { emit stateChanged (getState(), this); } - -void CSMDoc::Document::operationDone (int type) +void CSMDoc::Document::reportMessage (const QString& message, int type) { - emit stateChanged (getState(), this); + /// \todo find a better way to get these messages to the user. + std::cout << message.toUtf8().constData() << std::endl; } -void CSMDoc::Document::saving() +void CSMDoc::Document::operationDone (int type) { - ++mSaveCount; - - emit progress (mSaveCount, 16, State_Saving, 1, this); - - if (mSaveCount>15) - { - //clear the stack before resetting the save state - //to avoid emitting incorrect states - mUndoStack.setClean(); - - mSaveCount = 0; - mSaveTimer.stop(); - emit stateChanged (getState(), this); - } + emit stateChanged (getState(), this); } const CSMWorld::Data& CSMDoc::Document::getData() const diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 3532721ea..437b0c513 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -14,6 +14,7 @@ #include "../tools/tools.hpp" #include "state.hpp" +#include "saving.hpp" class QAbstractItemModel; @@ -23,6 +24,11 @@ namespace ESM struct Global; } +namespace Files +{ + class ConfigurationManager; +} + namespace CSMDoc { class Document : public QObject @@ -32,16 +38,17 @@ namespace CSMDoc private: boost::filesystem::path mSavePath; + std::vector mContentFiles; CSMWorld::Data mData; CSMTools::Tools mTools; + boost::filesystem::path mProjectPath; + Saving mSaving; + boost::filesystem::path mResDir; // 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&); @@ -64,8 +71,7 @@ namespace CSMDoc public: - Document (const std::vector& files, - const boost::filesystem::path& savePath, bool new_); + Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_); ~Document(); @@ -75,6 +81,10 @@ namespace CSMDoc const boost::filesystem::path& getSavePath() const; + const std::vector& getContentFiles() const; + ///< \attention The last element in this collection is the file that is being edited, + /// but with its original path instead of the save path. + void save(); CSMWorld::UniversalId verify(); @@ -98,10 +108,9 @@ namespace CSMDoc void modificationStateChanged (bool clean); - void operationDone (int type); + void reportMessage (const QString& message, int type); - void saving(); - ///< dummy implementation -> remove when proper save is implemented. + void operationDone (int type); public slots: @@ -110,3 +119,4 @@ namespace CSMDoc } #endif + diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index b079109ea..3ff75c9c1 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -4,9 +4,22 @@ #include #include +#include + +#ifndef Q_MOC_RUN +#include +#endif + #include "document.hpp" -CSMDoc::DocumentManager::DocumentManager() {} +CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) +: mConfiguration (configuration) +{ + boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; + + if (!boost::filesystem::is_directory (projectPath)) + boost::filesystem::create_directories (projectPath); +} CSMDoc::DocumentManager::~DocumentManager() { @@ -17,7 +30,7 @@ CSMDoc::DocumentManager::~DocumentManager() CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (files, savePath, new_); + Document *document = new Document (mConfiguration, files, savePath, mResDir, new_); mDocuments.push_back (document); @@ -35,4 +48,9 @@ bool CSMDoc::DocumentManager::removeDocument (Document *document) delete document; return mDocuments.empty(); -} \ No newline at end of file +} + +void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir) +{ + mResDir = boost::filesystem::system_complete(parResDir); +} diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index dfded8d5c..b80a18642 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -6,6 +6,11 @@ #include +namespace Files +{ + class ConfigurationManager; +} + namespace CSMDoc { class Document; @@ -13,18 +18,18 @@ namespace CSMDoc class DocumentManager { std::vector mDocuments; + const Files::ConfigurationManager& mConfiguration; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); public: - DocumentManager(); + DocumentManager (const Files::ConfigurationManager& configuration); ~DocumentManager(); - Document *addDocument (const std::vector& files, - const boost::filesystem::path& savePath, bool new_); + Document *addDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, 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 @@ -32,6 +37,10 @@ namespace CSMDoc bool removeDocument (Document *document); ///< \return last document removed? + void setResourceDir (const boost::filesystem::path& parResDir); + + private: + boost::filesystem::path mResDir; }; } diff --git a/apps/opencs/model/tools/operation.cpp b/apps/opencs/model/doc/operation.cpp similarity index 50% rename from apps/opencs/model/tools/operation.cpp rename to apps/opencs/model/doc/operation.cpp index 71761cdae..d29cc2631 100644 --- a/apps/opencs/model/tools/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -6,16 +6,16 @@ #include -#include "../doc/state.hpp" - +#include "state.hpp" #include "stage.hpp" -void CSMTools::Operation::prepareStages() +void CSMDoc::Operation::prepareStages() { mCurrentStage = mStages.begin(); mCurrentStep = 0; mCurrentStepTotal = 0; mTotalSteps = 0; + mError = false; for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) { @@ -24,38 +24,61 @@ void CSMTools::Operation::prepareStages() } } -CSMTools::Operation::Operation (int type) : mType (type) {} +CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways) +: mType (type), mOrdered (ordered), mFinalAlways (finalAlways) +{ + connect (this, SIGNAL (finished()), this, SLOT (operationDone())); +} -CSMTools::Operation::~Operation() +CSMDoc::Operation::~Operation() { for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) delete iter->first; } -void CSMTools::Operation::run() +void CSMDoc::Operation::run() { prepareStages(); QTimer timer; - timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify())); + timer.connect (&timer, SIGNAL (timeout()), this, SLOT (executeStage())); timer.start (0); exec(); } -void CSMTools::Operation::appendStage (Stage *stage) +void CSMDoc::Operation::appendStage (Stage *stage) { mStages.push_back (std::make_pair (stage, 0)); } -void CSMTools::Operation::abort() +bool CSMDoc::Operation::hasError() const { - exit(); + return mError; } -void CSMTools::Operation::verify() +void CSMDoc::Operation::abort() +{ + if (!isRunning()) + return; + + mError = true; + + if (mFinalAlways) + { + if (mStages.begin()!=mStages.end() && mCurrentStage!=--mStages.end()) + { + mCurrentStep = 0; + mCurrentStage = --mStages.end(); + } + } + else + mCurrentStage = mStages.end(); +} + +void CSMDoc::Operation::executeStage() { std::vector messages; @@ -68,7 +91,16 @@ void CSMTools::Operation::verify() } else { - mCurrentStage->first->perform (mCurrentStep++, messages); + try + { + mCurrentStage->first->perform (mCurrentStep++, messages); + } + catch (const std::exception& e) + { + emit reportMessage (e.what(), mType); + abort(); + } + ++mCurrentStepTotal; break; } @@ -81,4 +113,9 @@ void CSMTools::Operation::verify() if (mCurrentStage==mStages.end()) exit(); +} + +void CSMDoc::Operation::operationDone() +{ + emit done (mType); } \ No newline at end of file diff --git a/apps/opencs/model/tools/operation.hpp b/apps/opencs/model/doc/operation.hpp similarity index 63% rename from apps/opencs/model/tools/operation.hpp rename to apps/opencs/model/doc/operation.hpp index 4731c58fa..316eda78f 100644 --- a/apps/opencs/model/tools/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -1,11 +1,11 @@ -#ifndef CSM_TOOLS_OPERATION_H -#define CSM_TOOLS_OPERATION_H +#ifndef CSM_DOC_OPERATION_H +#define CSM_DOC_OPERATION_H #include #include -namespace CSMTools +namespace CSMDoc { class Stage; @@ -19,12 +19,17 @@ namespace CSMTools int mCurrentStep; int mCurrentStepTotal; int mTotalSteps; + int mOrdered; + bool mFinalAlways; + bool mError; void prepareStages(); public: - Operation (int type); + Operation (int type, bool ordered, bool finalAlways = false); + ///< \param ordered Stages must be executed in the given order. + /// \param finalAlways Execute last stage even if an error occurred during earlier stages. virtual ~Operation(); @@ -35,19 +40,25 @@ namespace CSMTools /// /// \attention Do no call this function while this Operation is running. + bool hasError() const; + signals: void progress (int current, int max, int type); void reportMessage (const QString& message, int type); + void done (int type); + public slots: void abort(); private slots: - void verify(); + void executeStage(); + + void operationDone(); }; } diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp new file mode 100644 index 000000000..73278ba97 --- /dev/null +++ b/apps/opencs/model/doc/saving.cpp @@ -0,0 +1,71 @@ + +#include "saving.hpp" + +#include "../world/data.hpp" +#include "../world/idcollection.hpp" + +#include "state.hpp" +#include "savingstages.hpp" +#include "document.hpp" + +CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath) +: Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath) +{ + // save project file + appendStage (new OpenSaveStage (mDocument, mState, true)); + + appendStage (new WriteHeaderStage (mDocument, mState, true)); + + appendStage (new WriteFilterStage (mDocument, mState, CSMFilter::Filter::Scope_Project)); + + appendStage (new CloseSaveStage (mState)); + + // save content file + appendStage (new OpenSaveStage (mDocument, mState, false)); + + appendStage (new WriteHeaderStage (mDocument, mState, false)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getGlobals(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getGmsts(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getSkills(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getClasses(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getFactions(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getRaces(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getSounds(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getScripts(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getRegions(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getBirthsigns(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getSpells(), mState)); + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); + + appendStage (new WriteRefIdCollectionStage (mDocument, mState)); + + + appendStage (new CloseSaveStage (mState)); + + appendStage (new FinalSavingStage (mDocument, mState)); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/saving.hpp b/apps/opencs/model/doc/saving.hpp new file mode 100644 index 000000000..cd1bbef98 --- /dev/null +++ b/apps/opencs/model/doc/saving.hpp @@ -0,0 +1,27 @@ +#ifndef CSM_DOC_SAVING_H +#define CSM_DOC_SAVING_H + +#include + +#include "operation.hpp" +#include "savingstate.hpp" + +namespace CSMDoc +{ + class Document; + + class Saving : public Operation + { + Q_OBJECT + + Document& mDocument; + SavingState mState; + + public: + + Saving (Document& document, const boost::filesystem::path& projectPath); + + }; +} + +#endif diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp new file mode 100644 index 000000000..d7df2117d --- /dev/null +++ b/apps/opencs/model/doc/savingstages.cpp @@ -0,0 +1,263 @@ + +#include "savingstages.hpp" + +#include + +#include + +#include + +#include + +#include "../world/infocollection.hpp" + +#include "document.hpp" +#include "savingstate.hpp" + +CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state, bool projectFile) +: mDocument (document), mState (state), mProjectFile (projectFile) +{} + +int CSMDoc::OpenSaveStage::setup() +{ + return 1; +} + +void CSMDoc::OpenSaveStage::perform (int stage, std::vector& messages) +{ + mState.start (mDocument, mProjectFile); + + mState.getStream().open ((mProjectFile ? mState.getPath() : mState.getTmpPath()).string().c_str()); + + if (!mState.getStream().is_open()) + throw std::runtime_error ("failed to open stream for saving"); +} + + +CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state, bool simple) +: mDocument (document), mState (state), mSimple (simple) +{} + +int CSMDoc::WriteHeaderStage::setup() +{ + return 1; +} + +void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& messages) +{ + mState.getWriter().setVersion(); + + mState.getWriter().clearMaster(); + + mState.getWriter().setFormat (0); + + if (mSimple) + { + mState.getWriter().setAuthor (""); + mState.getWriter().setDescription (""); + mState.getWriter().setRecordCount (0); + } + else + { + mState.getWriter().setAuthor (mDocument.getData().getAuthor()); + mState.getWriter().setDescription (mDocument.getData().getDescription()); + mState.getWriter().setRecordCount ( + mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + + mDocument.getData().count (CSMWorld::RecordBase::State_Deleted)); + + /// \todo refine dependency list (at least remove redundant dependencies) + std::vector dependencies = mDocument.getContentFiles(); + std::vector::const_iterator end (--dependencies.end()); + + for (std::vector::const_iterator iter (dependencies.begin()); + iter!=end; ++iter) + { + std::string name = iter->filename().string(); + uint64_t size = boost::filesystem::file_size (*iter); + + mState.getWriter().addMaster (name, size); + } + } + + mState.getWriter().save (mState.getStream()); +} + + +CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage (Document& document, + SavingState& state, bool journal) +: mDocument (document), mState (state), + mTopics (journal ? document.getData().getJournals() : document.getData().getTopics()), + mInfos (journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos()) +{} + +int CSMDoc::WriteDialogueCollectionStage::setup() +{ + return mTopics.getSize(); +} + +void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector& messages) +{ + const CSMWorld::Record& topic = mTopics.getRecord (stage); + + CSMWorld::RecordBase::State state = topic.mState; + + if (state==CSMWorld::RecordBase::State_Deleted) + { + // if the topic is deleted, we do not need to bother with INFO records. + + /// \todo wrote record with delete flag + + return; + } + + // Test, if we need to save anything associated info records. + bool infoModified = false; + + CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); + + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) + { + CSMWorld::RecordBase::State state = iter->mState; + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly || + state==CSMWorld::RecordBase::State_Deleted) + { + infoModified = true; + break; + } + } + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly || + infoModified) + { + mState.getWriter().startRecord (topic.mModified.sRecordId); + mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); + topic.mModified.save (mState.getWriter()); + mState.getWriter().endRecord (topic.mModified.sRecordId); + + // write modified selected info records + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; + ++iter) + { + CSMWorld::RecordBase::State state = iter->mState; + + if (state==CSMWorld::RecordBase::State_Deleted) + { + /// \todo wrote record with delete flag + } + else if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly) + { + ESM::DialInfo info = iter->get(); + info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + + if (iter!=range.first) + { + CSMWorld::InfoCollection::RecordConstIterator prev = iter; + --prev; + + info.mPrev = + prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1); + } + + CSMWorld::InfoCollection::RecordConstIterator next = iter; + ++next; + + if (next!=range.second) + { + info.mNext = + next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); + } + + mState.getWriter().startRecord (info.sRecordId); + mState.getWriter().writeHNCString ("INAM", info.mId); + info.save (mState.getWriter()); + mState.getWriter().endRecord (info.sRecordId); + } + } + } +} + + +CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::WriteRefIdCollectionStage::setup() +{ + return mDocument.getData().getReferenceables().getSize(); +} + +void CSMDoc::WriteRefIdCollectionStage::perform (int stage, std::vector& messages) +{ + mDocument.getData().getReferenceables().save (stage, mState.getWriter()); +} + + +CSMDoc::WriteFilterStage::WriteFilterStage (Document& document, SavingState& state, + CSMFilter::Filter::Scope scope) +: WriteCollectionStage > (document.getData().getFilters(), + state), + mDocument (document), mScope (scope) +{} + +void CSMDoc::WriteFilterStage::perform (int stage, std::vector& messages) +{ + const CSMWorld::Record& record = + mDocument.getData().getFilters().getRecord (stage); + + if (record.get().mScope==mScope) + WriteCollectionStage >::perform (stage, messages); +} + + +CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) +: mState (state) +{} + +int CSMDoc::CloseSaveStage::setup() +{ + return 1; +} + +void CSMDoc::CloseSaveStage::perform (int stage, std::vector& messages) +{ + mState.getStream().close(); + + if (!mState.getStream()) + throw std::runtime_error ("saving failed"); +} + + +CSMDoc::FinalSavingStage::FinalSavingStage (Document& document, SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::FinalSavingStage::setup() +{ + return 1; +} + +void CSMDoc::FinalSavingStage::perform (int stage, std::vector& messages) +{ + if (mState.hasError()) + { + mState.getWriter().close(); + mState.getStream().close(); + + if (boost::filesystem::exists (mState.getTmpPath())) + boost::filesystem::remove (mState.getTmpPath()); + } + else if (!mState.isProjectFile()) + { + if (boost::filesystem::exists (mState.getPath())) + boost::filesystem::remove (mState.getPath()); + + boost::filesystem::rename (mState.getTmpPath(), mState.getPath()); + + mDocument.getUndoStack().setClean(); + } +} \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp new file mode 100644 index 000000000..b8eb0a3b3 --- /dev/null +++ b/apps/opencs/model/doc/savingstages.hpp @@ -0,0 +1,201 @@ +#ifndef CSM_DOC_SAVINGSTAGES_H +#define CSM_DOC_SAVINGSTAGES_H + +#include "stage.hpp" + +#include "../world/record.hpp" +#include "../world/idcollection.hpp" + +#include "../filter/filter.hpp" + +#include "savingstate.hpp" + +namespace ESM +{ + struct Dialogue; +} + +namespace CSMWorld +{ + class InfoCollection; +} + +namespace CSMDoc +{ + class Document; + class SavingState; + + class OpenSaveStage : public Stage + { + Document& mDocument; + SavingState& mState; + bool mProjectFile; + + public: + + OpenSaveStage (Document& document, SavingState& state, bool projectFile); + ///< \param projectFile Saving the project file instead of the content file. + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class WriteHeaderStage : public Stage + { + Document& mDocument; + SavingState& mState; + bool mSimple; + + public: + + WriteHeaderStage (Document& document, SavingState& state, bool simple); + ///< \param simple Simplified header (used for project files). + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + + template + class WriteCollectionStage : public Stage + { + const CollectionT& mCollection; + SavingState& mState; + + public: + + WriteCollectionStage (const CollectionT& collection, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template + WriteCollectionStage::WriteCollectionStage (const CollectionT& collection, + SavingState& state) + : mCollection (collection), mState (state) + {} + + template + int WriteCollectionStage::setup() + { + return mCollection.getSize(); + } + + template + void WriteCollectionStage::perform (int stage, std::vector& messages) + { + CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic (change ESMWriter interface?) + type += reinterpret_cast (&mCollection.getRecord (stage).mModified.sRecordId)[i]; + + mState.getWriter().startRecord (mCollection.getRecord (stage).mModified.sRecordId); + mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); + mCollection.getRecord (stage).mModified.save (mState.getWriter()); + mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId); + } + else if (state==CSMWorld::RecordBase::State_Deleted) + { + /// \todo write record with delete flag + } + } + + + class WriteDialogueCollectionStage : public Stage + { + Document& mDocument; + SavingState& mState; + const CSMWorld::IdCollection& mTopics; + CSMWorld::InfoCollection& mInfos; + + public: + + WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + + class WriteRefIdCollectionStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + WriteRefIdCollectionStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + + class WriteFilterStage : public WriteCollectionStage > + { + Document& mDocument; + CSMFilter::Filter::Scope mScope; + + public: + + WriteFilterStage (Document& document, SavingState& state, CSMFilter::Filter::Scope scope); + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + + class CloseSaveStage : public Stage + { + SavingState& mState; + + public: + + CloseSaveStage (SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class FinalSavingStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + FinalSavingStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp new file mode 100644 index 000000000..4a1abb888 --- /dev/null +++ b/apps/opencs/model/doc/savingstate.cpp @@ -0,0 +1,65 @@ + +#include "savingstate.hpp" + +#include "operation.hpp" +#include "document.hpp" + +CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath) +: mOperation (operation), + /// \todo set encoding properly, once config implementation has been fixed. + mEncoder (ToUTF8::calculateEncoding ("win1252")), + mProjectPath (projectPath), mProjectFile (false) +{ + mWriter.setEncoder (&mEncoder); +} + +bool CSMDoc::SavingState::hasError() const +{ + return mOperation.hasError(); +} + +void CSMDoc::SavingState::start (Document& document, bool project) +{ + mProjectFile = project; + + if (mStream.is_open()) + mStream.close(); + + mStream.clear(); + + if (project) + mPath = mProjectPath; + else + mPath = document.getSavePath(); + + boost::filesystem::path file (mPath.filename().string() + ".tmp"); + + mTmpPath = mPath.parent_path(); + + mTmpPath /= file; +} + +const boost::filesystem::path& CSMDoc::SavingState::getPath() const +{ + return mPath; +} + +const boost::filesystem::path& CSMDoc::SavingState::getTmpPath() const +{ + return mTmpPath; +} + +std::ofstream& CSMDoc::SavingState::getStream() +{ + return mStream; +} + +ESM::ESMWriter& CSMDoc::SavingState::getWriter() +{ + return mWriter; +} + +bool CSMDoc::SavingState::isProjectFile() const +{ + return mProjectFile; +} \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp new file mode 100644 index 000000000..8cf7883e5 --- /dev/null +++ b/apps/opencs/model/doc/savingstate.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_DOC_SAVINGSTATE_H +#define CSM_DOC_SAVINGSTATE_H + +#include + +#include + +#include + +namespace CSMDoc +{ + class Operation; + class Document; + + class SavingState + { + Operation& mOperation; + boost::filesystem::path mPath; + boost::filesystem::path mTmpPath; + ToUTF8::Utf8Encoder mEncoder; + std::ofstream mStream; + ESM::ESMWriter mWriter; + boost::filesystem::path mProjectPath; + bool mProjectFile; + + public: + + SavingState (Operation& operation, const boost::filesystem::path& projectPath); + + bool hasError() const; + + void start (Document& document, bool project); + ///< \param project Save project file instead of content file. + + const boost::filesystem::path& getPath() const; + + const boost::filesystem::path& getTmpPath() const; + + std::ofstream& getStream(); + + ESM::ESMWriter& getWriter(); + + bool isProjectFile() const; + ///< Currently saving project file? (instead of content file) + }; + + +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp new file mode 100644 index 000000000..99b765770 --- /dev/null +++ b/apps/opencs/model/doc/stage.cpp @@ -0,0 +1,4 @@ + +#include "stage.hpp" + +CSMDoc::Stage::~Stage() {} \ No newline at end of file diff --git a/apps/opencs/model/tools/stage.hpp b/apps/opencs/model/doc/stage.hpp similarity index 65% rename from apps/opencs/model/tools/stage.hpp rename to apps/opencs/model/doc/stage.hpp index 3020936f3..1f96c60b4 100644 --- a/apps/opencs/model/tools/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -1,10 +1,10 @@ -#ifndef CSM_TOOLS_STAGE_H -#define CSM_TOOLS_STAGE_H +#ifndef CSM_DOC_STAGE_H +#define CSM_DOC_STAGE_H #include #include -namespace CSMTools +namespace CSMDoc { class Stage { @@ -16,7 +16,7 @@ namespace CSMTools ///< \return number of steps virtual void perform (int stage, std::vector& messages) = 0; - ///< Messages resulting from this tage will be appended to \a messages. + ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/filter/andnode.hpp b/apps/opencs/model/filter/andnode.hpp index 980ea554c..887175700 100644 --- a/apps/opencs/model/filter/andnode.hpp +++ b/apps/opencs/model/filter/andnode.hpp @@ -7,8 +7,6 @@ namespace CSMFilter { class AndNode : public NAryNode { - bool mAnd; - public: AndNode (const std::vector >& nodes); diff --git a/apps/opencs/model/filter/ornode.hpp b/apps/opencs/model/filter/ornode.hpp index 63ed2f10c..c39e35095 100644 --- a/apps/opencs/model/filter/ornode.hpp +++ b/apps/opencs/model/filter/ornode.hpp @@ -7,8 +7,6 @@ namespace CSMFilter { class OrNode : public NAryNode { - bool mAnd; - public: OrNode (const std::vector >& nodes); diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 8f4fcb70c..6e286d943 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -61,11 +61,11 @@ namespace CSMFilter bool isString() const; }; - Token::Token (Type type) : mType (type) {} + Token::Token (Type type) : mType (type), mNumber(0.0) {} - Token::Token (Type type, const std::string& string) : mType (type), mString (string) {} + Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {} - Token::Token (const std::string& string) : mType (Type_String), mString (string) {} + Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {} Token::Token (double number) : mType (Type_Number), mNumber (number) {} @@ -614,4 +614,4 @@ boost::shared_ptr CSMFilter::Parser::getFilter() const throw std::logic_error ("No filter available"); return mFilter; -} \ No newline at end of file +} diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp index a1daee4ac..87a85e8e4 100644 --- a/apps/opencs/model/settings/settingsitem.hpp +++ b/apps/opencs/model/settings/settingsitem.hpp @@ -44,7 +44,11 @@ namespace CSMSettings inline QStringPair *getValuePair() { return mValuePair; } /// set value range (spinbox / integer use) - inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); } + inline void setValuePair (QStringPair valuePair) + { + delete mValuePair; + mValuePair = new QStringPair(valuePair); + } inline bool isMultivalue () { return mIsMultiValue; } diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 1ce28ed75..94cee8a43 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -251,7 +251,7 @@ void CSMSettings::UserSettings::loadSettings (const QString &fileName) bool localOk = loadFromFile(localFilePath); //user - mUserFilePath = QString::fromStdString(mCfgMgr.getUserPath().string()) + fileName; + mUserFilePath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + fileName; loadFromFile(mUserFilePath); if (!(localOk || globalOk)) diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp index 42b5a6b24..bdd65b44a 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that birthsign records are internally consistent - class BirthsignCheckStage : public Stage + class BirthsignCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mBirthsigns; diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp index a29d7c8b7..3604b451c 100644 --- a/apps/opencs/model/tools/classcheck.hpp +++ b/apps/opencs/model/tools/classcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that class records are internally consistent - class ClassCheckStage : public Stage + class ClassCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mClasses; diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp index 868650572..7cd80347d 100644 --- a/apps/opencs/model/tools/factioncheck.hpp +++ b/apps/opencs/model/tools/factioncheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that faction records are internally consistent - class FactionCheckStage : public Stage + class FactionCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mFactions; diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp index 342e2d754..5fddf08d3 100644 --- a/apps/opencs/model/tools/mandatoryid.hpp +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -6,7 +6,7 @@ #include "../world/universalid.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMWorld { @@ -16,7 +16,7 @@ namespace CSMWorld namespace CSMTools { /// \brief Verify stage: make sure that records with specific IDs exist. - class MandatoryIdStage : public Stage + class MandatoryIdStage : public CSMDoc::Stage { const CSMWorld::CollectionBase& mIdCollection; CSMWorld::UniversalId mCollectionId; diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp index 155f79902..ff9948bf6 100644 --- a/apps/opencs/model/tools/racecheck.hpp +++ b/apps/opencs/model/tools/racecheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that race records are internally consistent - class RaceCheckStage : public Stage + class RaceCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mRaces; bool mPlayable; diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp new file mode 100644 index 000000000..dab61bfff --- /dev/null +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -0,0 +1,1095 @@ +#include "referenceablecheck.hpp" +#include "../world/record.hpp" +#include "../world/universalid.hpp" +#include + +CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( + const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& faction) + : + mReferencables(referenceable), + mClasses(classes), + mRaces(races), + mFactions(faction), + mPlayerPresent(false) +{ +} + +void CSMTools::ReferenceableCheckStage::perform(int stage, std::vector< std::string >& messages) +{ + //Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage. + const int bookSize(mReferencables.getBooks().getSize()); + + if (stage < bookSize) + { + bookCheck(stage, mReferencables.getBooks(), messages); + return; + } + + stage -= bookSize; + + const int activatorSize(mReferencables.getActivators().getSize()); + + if (stage < activatorSize) + { + activatorCheck(stage, mReferencables.getActivators(), messages); + return; + } + + stage -= activatorSize; + + const int potionSize(mReferencables.getPotions().getSize()); + + if (stage < potionSize) + { + potionCheck(stage, mReferencables.getPotions(), messages); + return; + } + + stage -= potionSize; + + const int apparatusSize(mReferencables.getApparati().getSize()); + + if (stage < apparatusSize) + { + apparatusCheck(stage, mReferencables.getApparati(), messages); + return; + } + + stage -= apparatusSize; + + const int armorSize(mReferencables.getArmors().getSize()); + + if (stage < armorSize) + { + armorCheck(stage, mReferencables.getArmors(), messages); + return; + } + + stage -= armorSize; + + const int clothingSize(mReferencables.getClothing().getSize()); + + if (stage < clothingSize) + { + clothingCheck(stage, mReferencables.getClothing(), messages); + return; + } + + stage -= clothingSize; + + const int containerSize(mReferencables.getContainers().getSize()); + + if (stage < containerSize) + { + containerCheck(stage, mReferencables.getContainers(), messages); + return; + } + + stage -= containerSize; + + const int doorSize(mReferencables.getDoors().getSize()); + + if (stage < doorSize) + { + doorCheck(stage, mReferencables.getDoors(), messages); + return; + } + + stage -= doorSize; + + const int ingredientSize(mReferencables.getIngredients().getSize()); + + if (stage < ingredientSize) + { + ingredientCheck(stage, mReferencables.getIngredients(), messages); + return; + } + + stage -= ingredientSize; + + const int creatureLevListSize(mReferencables.getCreatureLevelledLists().getSize()); + + if (stage < creatureLevListSize) + { + creaturesLevListCheck(stage, mReferencables.getCreatureLevelledLists(), messages); + return; + } + + stage -= creatureLevListSize; + + const int itemLevelledListSize(mReferencables.getItemLevelledList().getSize()); + + if (stage < itemLevelledListSize) + { + itemLevelledListCheck(stage, mReferencables.getItemLevelledList(), messages); + return; + } + + stage -= itemLevelledListSize; + + const int lightSize(mReferencables.getLights().getSize()); + + if (stage < lightSize) + { + lightCheck(stage, mReferencables.getLights(), messages); + return; + } + + stage -= lightSize; + + const int lockpickSize(mReferencables.getLocpicks().getSize()); + + if (stage < lockpickSize) + { + lockpickCheck(stage, mReferencables.getLocpicks(), messages); + return; + } + + stage -= lockpickSize; + + const int miscSize(mReferencables.getMiscellaneous().getSize()); + + if (stage < miscSize) + { + miscCheck(stage, mReferencables.getMiscellaneous(), messages); + return; + } + + stage -= miscSize; + + const int npcSize(mReferencables.getNPCs().getSize()); + + if (stage < npcSize) + { + npcCheck(stage, mReferencables.getNPCs(), messages); + return; + } + + stage -= npcSize; + + const int weaponSize(mReferencables.getWeapons().getSize()); + + if (stage < weaponSize) + { + weaponCheck(stage, mReferencables.getWeapons(), messages); + return; + } + + stage -= weaponSize; + + const int probeSize(mReferencables.getProbes().getSize()); + + if (stage < probeSize) + { + probeCheck(stage, mReferencables.getProbes(), messages); + return; + } + + stage -= probeSize; + + const int repairSize(mReferencables.getRepairs().getSize()); + + if (stage < repairSize) + { + repairCheck(stage, mReferencables.getRepairs(), messages); + return; + } + + stage -= repairSize; + + const int staticSize(mReferencables.getStatics().getSize()); + + if (stage < staticSize) + { + staticCheck(stage, mReferencables.getStatics(), messages); + return; + } + + stage -= staticSize; + + const int creatureSize(mReferencables.getCreatures().getSize()); + + if (stage < creatureSize) + { + creatureCheck(stage, mReferencables.getCreatures(), messages); + return; + } +// if we come that far, we are about to perform our last, final check. + finalCheck(messages); + return; +} + +int CSMTools::ReferenceableCheckStage::setup() +{ + mPlayerPresent = false; + return mReferencables.getSize() + 1; +} + +void CSMTools::ReferenceableCheckStage::bookCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Book >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Book& book = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId); + + inventoryItemCheck(book, messages, id.toString(), true); +} + +void CSMTools::ReferenceableCheckStage::activatorCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Activator >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Activator& activator = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId); + + //Checking for model, IIRC all activators should have a model + if (activator.mModel.empty()) + { + messages.push_back(id.toString() + "|" + activator.mId + " has no model"); + } +} + +void CSMTools::ReferenceableCheckStage::potionCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Potion >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Potion& potion = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId); + + inventoryItemCheck(potion, messages, id.toString()); + //IIRC potion can have empty effects list just fine. +} + + +void CSMTools::ReferenceableCheckStage::apparatusCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Apparatus >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Apparatus& apparatus = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId); + + inventoryItemCheck(apparatus, messages, id.toString()); + + toolCheck(apparatus, messages, id.toString()); +} + +void CSMTools::ReferenceableCheckStage::armorCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Armor >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Armor& armor = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId); + + inventoryItemCheck(armor, messages, id.toString(), true); + + //checking for armor class, armor should have poistive armor class, but 0 is considered legal + if (armor.mData.mArmor < 0) + { + messages.push_back(id.toString() + "|" + armor.mId + " has negative armor class"); + } + + //checking for health. Only positive numbers are allowed, or 0 is illegal + if (armor.mData.mHealth <= 0) + { + messages.push_back(id.toString() + "|" + armor.mId + " has non positive health"); + } +} + +void CSMTools::ReferenceableCheckStage::clothingCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Clothing >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Clothing& clothing = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId); + inventoryItemCheck(clothing, messages, id.toString(), true); +} + +void CSMTools::ReferenceableCheckStage::containerCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Container >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Container& container = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId); + + //Checking for model, IIRC all containers should have a model + if (container.mModel.empty()) + { + messages.push_back(id.toString() + "|" + container.mId + " has no model"); + } + + //Checking for capacity (weight) + if (container.mWeight < 0) //0 is allowed + { + messages.push_back(id.toString() + "|" + container.mId + " has negative weight (capacity)"); + } + + //checking for name + if (container.mName.empty()) + { + messages.push_back(id.toString() + "|" + container.mId + " has an empty name"); + } +} + +void CSMTools::ReferenceableCheckStage::creatureCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Creature >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Creature& creature = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Creature, creature.mId); + + if (creature.mModel.empty()) + { + messages.push_back(id.toString() + "|" + creature.mId + " has no model"); + } + + if (creature.mName.empty()) + { + messages.push_back(id.toString() + "|" + creature.mId + " has an empty name"); + } + + //stats checks + if (creature.mData.mLevel < 1) + { + messages.push_back(id.toString() + "|" + creature.mId + " has non-postive level"); + } + + if (creature.mData.mStrength < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative strength"); + } + + if (creature.mData.mIntelligence < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative intelligence"); + } + + if (creature.mData.mWillpower < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative willpower"); + } + + if (creature.mData.mAgility < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative agility"); + } + + if (creature.mData.mSpeed < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative speed"); + } + + if (creature.mData.mEndurance < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative endurance"); + } + + if (creature.mData.mPersonality < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative personality"); + } + + if (creature.mData.mLuck < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative luck"); + } + + if (creature.mData.mHealth < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative health"); + } + + if (creature.mData.mSoul < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative soul value"); + } + + for (int i = 0; i < 6; ++i) + { + if (creature.mData.mAttack[i] < 0) + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative attack strength"); + break; + } + } + + //TODO, find meaning of other values + if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures + { + messages.push_back(id.toString() + "|" + creature.mId + " has negative gold "); + } +} + +void CSMTools::ReferenceableCheckStage::doorCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Door >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Door& Door = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Door, Door.mId); + + //usual, name or model + if (Door.mName.empty()) + { + messages.push_back(id.toString() + "|" + Door.mId + " has an empty name"); + } + + if (Door.mModel.empty()) + { + messages.push_back(id.toString() + "|" + Door.mId + " has no model"); + } + + //TODO, check what static unsigned int sRecordId; is for +} + +void CSMTools::ReferenceableCheckStage::ingredientCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Ingredient >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Ingredient& Ingredient = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, Ingredient.mId); + + inventoryItemCheck(Ingredient, messages, id.toString()); +} + +void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::CreatureLevList& CreatureLevList = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, CreatureLevList.mId); //CreatureLevList but Type_CreatureLevelledList :/ + + listCheck(CreatureLevList, messages, id.toString()); +} + +void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::ItemLevList& ItemLevList = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_ItemLevelledList, ItemLevList.mId); + + listCheck(ItemLevList, messages, id.toString()); +} + +void CSMTools::ReferenceableCheckStage::lightCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Light >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Light& light = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Light, light.mId); + + if (light.mData.mRadius < 0) + { + messages.push_back(id.toString() + "|" + light.mId + " has negative light radius"); + } + + if (light.mData.mFlags & ESM::Light::Carry) + { + inventoryItemCheck(light, messages, id.toString()); + + if (light.mData.mTime == 0) + { + messages.push_back(id.toString() + "|" + light.mId + " has zero duration"); + } + } +} + +void CSMTools::ReferenceableCheckStage::lockpickCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Lockpick >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Lockpick& lockpick = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId); + + inventoryItemCheck(lockpick, messages, id.toString()); + + toolCheck(lockpick, messages, id.toString(), true); +} + +void CSMTools::ReferenceableCheckStage::miscCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Miscellaneous& miscellaneous = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId); + + inventoryItemCheck(miscellaneous, messages, id.toString()); +} + +void CSMTools::ReferenceableCheckStage::npcCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::NPC >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::NPC& npc = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc, npc.mId); + + short level(npc.mNpdt52.mLevel); + char disposition(npc.mNpdt52.mDisposition); + char reputation(npc.mNpdt52.mReputation); + char rank(npc.mNpdt52.mRank); + //Don't know what unknown is for + int gold(npc.mNpdt52.mGold); + + //Detect if player is present + if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl? + { + mPlayerPresent = true; + } + + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated + { + if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0008 = autocalculated flag + { + messages.push_back(id.toString() + "|" + npc.mId + " mNpdtType or flags mismatch!"); //should not happend? + return; + } + + level = npc.mNpdt12.mLevel; + disposition = npc.mNpdt12.mDisposition; + reputation = npc.mNpdt12.mReputation; + rank = npc.mNpdt12.mRank; + gold = npc.mNpdt12.mGold; + } + else + { + if (npc.mNpdt52.mMana < 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " mana has negative value"); + } + + if (npc.mNpdt52.mFatigue < 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " fatigue has negative value"); + } + + if (npc.mNpdt52.mAgility == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " agility has zero value"); + } + + if (npc.mNpdt52.mEndurance == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " endurance has zero value"); + } + + if (npc.mNpdt52.mIntelligence == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " intelligence has zero value"); + } + + if (npc.mNpdt52.mLuck == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " luck has zero value"); + } + + if (npc.mNpdt52.mPersonality == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " personality has zero value"); + } + + if (npc.mNpdt52.mStrength == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " strength has zero value"); + } + + if (npc.mNpdt52.mSpeed == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " speed has zero value"); + } + + if (npc.mNpdt52.mWillpower == 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " willpower has zero value"); + } + } + + if (level < 1) + { + messages.push_back(id.toString() + "|" + npc.mId + " level is non positive"); + } + + if (gold < 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " gold has negative value"); + } + + if (npc.mName.empty()) + { + messages.push_back(id.toString() + "|" + npc.mId + " has any empty name"); + } + + if (npc.mClass.empty()) + { + messages.push_back(id.toString() + "|" + npc.mId + " has any empty class"); + } + else //checking if there is such class + { + if (mClasses.searchId(npc.mClass) == -1) + { + messages.push_back(id.toString() + "|" + npc.mId + " has invalid class"); + } + } + + if (npc.mRace.empty()) + { + messages.push_back(id.toString() + "|" + npc.mId + " has any empty race"); + } + else //checking if there is a such race + { + if (mRaces.searchId(npc.mRace) == -1) + { + messages.push_back(id.toString() + "|" + npc.mId + " has invalid race"); + } + } + + if (disposition < 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " has negative disposition"); + } + + if (reputation < 0) //It seems that no character in Morrowind.esm have negative reputation. I'm assuming that negative reputation is invalid + { + messages.push_back(id.toString() + "|" + npc.mId + " has negative reputation"); + } + + if (npc.mFaction.empty() == false) + { + if (rank < 0) + { + messages.push_back(id.toString() + "|" + npc.mId + " has negative rank"); + } + + if (mFactions.searchId(npc.mFaction) == -1) + { + messages.push_back(id.toString() + "|" + npc.mId + " has invalid faction"); + } + } + + if (npc.mHead.empty()) + { + messages.push_back(id.toString() + "|" + npc.mId + " has no head"); + } + + if (npc.mHair.empty()) + { + messages.push_back(id.toString() + "|" + npc.mId + " has no hair"); + } + + //TODO: reputation, Disposition, rank, everything else +} + +void CSMTools::ReferenceableCheckStage::weaponCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Weapon >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Weapon& weapon = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Weapon, weapon.mId); + + //TODO, It seems that this stuff for spellcasting is obligatory and In fact We should check if records are present + if + ( //THOSE ARE HARDCODED! + !(weapon.mId == "VFX_Hands" || + weapon.mId == "VFX_Absorb" || + weapon.mId == "VFX_Reflect" || + weapon.mId == "VFX_DefaultBolt" || + //TODO I don't know how to get full list of effects :/ + //DANGER!, ACHTUNG! FIXME! The following is the list of the magical bolts, valid for Morrowind.esm. However those are not hardcoded. + weapon.mId == "magic_bolt" || + weapon.mId == "shock_bolt" || + weapon.mId == "shield_bolt" || + weapon.mId == "VFX_DestructBolt" || + weapon.mId == "VFX_PoisonBolt" || + weapon.mId == "VFX_RestoreBolt" || + weapon.mId == "VFX_AlterationBolt" || + weapon.mId == "VFX_ConjureBolt" || + weapon.mId == "VFX_FrostBolt" || + weapon.mId == "VFX_MysticismBolt" || + weapon.mId == "VFX_IllusionBolt" || + weapon.mId == "VFX_Multiple2" || + weapon.mId == "VFX_Multiple3" || + weapon.mId == "VFX_Multiple4" || + weapon.mId == "VFX_Multiple5" || + weapon.mId == "VFX_Multiple6" || + weapon.mId == "VFX_Multiple7" || + weapon.mId == "VFX_Multiple8" || + weapon.mId == "VFX_Multiple9")) + { + inventoryItemCheck(weapon, messages, id.toString(), true); + + if (!(weapon.mData.mType == ESM::Weapon::MarksmanBow || + weapon.mData.mType == ESM::Weapon::MarksmanCrossbow || + weapon.mData.mType == ESM::Weapon::MarksmanThrown || + weapon.mData.mType == ESM::Weapon::Arrow || + weapon.mData.mType == ESM::Weapon::Bolt)) + { + if (weapon.mData.mSlash[0] > weapon.mData.mSlash[1]) + { + messages.push_back(id.toString() + "|" + weapon.mId + " has minimum slash damage higher than maximum"); + } + + if (weapon.mData.mThrust[0] > weapon.mData.mThrust[1]) + { + messages.push_back(id.toString() + "|" + weapon.mId + " has minimum thrust damage higher than maximum"); + } + } + + if (weapon.mData.mChop[0] > weapon.mData.mChop[1]) + { + messages.push_back(id.toString() + "|" + weapon.mId + " has minimum chop damage higher than maximum"); + } + + if (!(weapon.mData.mType == ESM::Weapon::Arrow || + weapon.mData.mType == ESM::Weapon::Bolt || + weapon.mData.mType == ESM::Weapon::MarksmanThrown)) + { + //checking of health + if (weapon.mData.mHealth <= 0) + { + messages.push_back(id.toString() + "|" + weapon.mId + " has non-positivie health"); + } + + if (weapon.mData.mReach < 0) + { + messages.push_back(id.toString() + "|" + weapon.mId + " has negative reach"); + } + } + } +} + +void CSMTools::ReferenceableCheckStage::probeCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Probe >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Probe& probe = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId); + + inventoryItemCheck(probe, messages, id.toString()); + toolCheck(probe, messages, id.toString(), true); +} + +void CSMTools::ReferenceableCheckStage::repairCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Repair >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Repair& repair = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Repair, repair.mId); + + inventoryItemCheck(repair, messages, id.toString()); + toolCheck(repair, messages, id.toString(), true); +} + +void CSMTools::ReferenceableCheckStage::staticCheck( + int stage, + const CSMWorld::RefIdDataContainer< ESM::Static >& records, + std::vector< std::string >& messages) +{ + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + + if (baseRecord.isDeleted()) + { + return; + } + + const ESM::Static& staticElement = (dynamic_cast& >(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Static, staticElement.mId); + + if (staticElement.mModel.empty()) + { + messages.push_back(id.toString() + "|" + staticElement.mId + " has no model"); + } +} + +//final check + +void CSMTools::ReferenceableCheckStage::finalCheck(std::vector< std::string >& messages) +{ + if (!mPlayerPresent) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc); + messages.push_back(id.toString() + "| There is no player record"); + } +} + + +//Templates begins here + +template void CSMTools::ReferenceableCheckStage::inventoryItemCheck( + const ITEM& someItem, + std::vector< std::string >& messages, + const std::string& someID, bool enchantable) +{ + if (someItem.mName.empty()) + { + messages.push_back(someID + "|" + someItem.mId + " has an empty name"); + } + + //Checking for weight + if (someItem.mData.mWeight < 0) + { + messages.push_back(someID + "|" + someItem.mId + " has negative weight"); + } + + //Checking for value + if (someItem.mData.mValue < 0) + { + messages.push_back(someID + "|" + someItem.mId + " has negative value"); + } + +//checking for model + if (someItem.mModel.empty()) + { + messages.push_back(someID + "|" + someItem.mId + " has no model"); + } + + //checking for icon + if (someItem.mIcon.empty()) + { + messages.push_back(someID + "|" + someItem.mId + " has no icon"); + } + + if (enchantable) + { + if (someItem.mData.mEnchant < 0) + { + messages.push_back(someID + "|" + someItem.mId + " has negative enchantment"); + } + } +} + +template void CSMTools::ReferenceableCheckStage::inventoryItemCheck( + const ITEM& someItem, + std::vector< std::string >& messages, + const std::string& someID) +{ + if (someItem.mName.empty()) + { + messages.push_back(someID + "|" + someItem.mId + " has an empty name"); + } + + //Checking for weight + if (someItem.mData.mWeight < 0) + { + messages.push_back(someID + "|" + someItem.mId + " has negative weight"); + } + + //Checking for value + if (someItem.mData.mValue < 0) + { + messages.push_back(someID + "|" + someItem.mId + " has negative value"); + } + + //checking for model + if (someItem.mModel.empty()) + { + messages.push_back(someID + "|" + someItem.mId + " has no model"); + } + + //checking for icon + if (someItem.mIcon.empty()) + { + messages.push_back(someID + "|" + someItem.mId + " has no icon"); + } +} + +template void CSMTools::ReferenceableCheckStage::toolCheck( + const TOOL& someTool, + std::vector< std::string >& messages, + const std::string& someID, bool canBeBroken) +{ + if (someTool.mData.mQuality <= 0) + { + messages.push_back(someID + "|" + someTool.mId + " has non-positive quality"); + } + + if (canBeBroken) + { + if (someTool.mData.mUses <= 0) + { + messages.push_back(someID + "|" + someTool.mId + " has non-positive uses count"); + } + } +} + +template void CSMTools::ReferenceableCheckStage::toolCheck( + const TOOL& someTool, + std::vector< std::string >& messages, + const std::string& someID) +{ + if (someTool.mData.mQuality <= 0) + { + messages.push_back(someID + "|" + someTool.mId + " has non-positive quality"); + } +} + +template void CSMTools::ReferenceableCheckStage::listCheck( + const LIST& someList, + std::vector< std::string >& messages, + const std::string& someID) +{ + for (unsigned i = 0; i < someList.mList.size(); ++i) + { + if (mReferencables.searchId(someList.mList[i].mId).first == -1) + { + messages.push_back(someID + "|" + someList.mId + " contains item without referencable"); + } + + if (someList.mList[i].mLevel < 1) + { + messages.push_back(someID + "|" + someList.mId + " contains item with non-positive level"); + } + } +} +// kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp new file mode 100644 index 000000000..338983cc7 --- /dev/null +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -0,0 +1,78 @@ +#ifndef REFERENCEABLECHECKSTAGE_H +#define REFERENCEABLECHECKSTAGE_H + +#include "../world/universalid.hpp" +#include "../doc/stage.hpp" +#include "../world/data.hpp" +#include "../world/refiddata.hpp" + +namespace CSMTools +{ + class ReferenceableCheckStage : public CSMDoc::Stage + { + public: + ReferenceableCheckStage(const CSMWorld::RefIdData& referenceable, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions); + + virtual void perform(int stage, std::vector< std::string >& messages); + virtual int setup(); + + private: + //CONCRETE CHECKS + void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, std::vector< std::string >& messages); + void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, std::vector< std::string >& messages); + void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); + + //FINAL CHECK + void finalCheck(std::vector& messages); + + //TEMPLATE CHECKS + template void inventoryItemCheck(const ITEM& someItem, + std::vector& messages, + const std::string& someID, + bool enchantable); //for all enchantable items. + + template void inventoryItemCheck(const ITEM& someItem, + std::vector& messages, + const std::string& someID); //for non-enchantable items. + + template void toolCheck(const TOOL& someTool, + std::vector& messages, + const std::string& someID, + bool canbebroken); //for tools with uses. + + template void toolCheck(const TOOL& someTool, + std::vector& messages, + const std::string& someID); //for tools without uses. + + template void listCheck(const LIST& someList, + std::vector< std::string >& messages, + const std::string& someID); + + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + bool mPlayerPresent; + }; +} +#endif // REFERENCEABLECHECKSTAGE_H diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp index b42135651..c8c437cbd 100644 --- a/apps/opencs/model/tools/regioncheck.hpp +++ b/apps/opencs/model/tools/regioncheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that region records are internally consistent - class RegionCheckStage : public Stage + class RegionCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mRegions; diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index b12531875..d88361746 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -68,4 +68,4 @@ void CSMTools::ReportModel::add (const std::string& row) 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/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp index 30a3f01ca..662bdadee 100644 --- a/apps/opencs/model/tools/skillcheck.hpp +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that skill records are internally consistent - class SkillCheckStage : public Stage + class SkillCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSkills; diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index a309763a1..00b45cd93 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that sound records are internally consistent - class SoundCheckStage : public Stage + class SoundCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSounds; diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp index 056639219..880ddafcd 100644 --- a/apps/opencs/model/tools/spellcheck.hpp +++ b/apps/opencs/model/tools/spellcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that spell records are internally consistent - class SpellCheckStage : public Stage + class SpellCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSpells; diff --git a/apps/opencs/model/tools/stage.cpp b/apps/opencs/model/tools/stage.cpp deleted file mode 100644 index 6f4567e57..000000000 --- a/apps/opencs/model/tools/stage.cpp +++ /dev/null @@ -1,4 +0,0 @@ - -#include "stage.hpp" - -CSMTools::Stage::~Stage() {} \ No newline at end of file diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 803861203..64e39ad2f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -3,9 +3,8 @@ #include -#include "verifier.hpp" - #include "../doc/state.hpp" +#include "../doc/operation.hpp" #include "../world/data.hpp" #include "../world/universalid.hpp" @@ -20,8 +19,9 @@ #include "regioncheck.hpp" #include "birthsigncheck.hpp" #include "spellcheck.hpp" +#include "referenceablecheck.hpp" -CSMTools::Operation *CSMTools::Tools::get (int type) +CSMDoc::Operation *CSMTools::Tools::get (int type) { switch (type) { @@ -31,19 +31,19 @@ CSMTools::Operation *CSMTools::Tools::get (int type) return 0; } -const CSMTools::Operation *CSMTools::Tools::get (int type) const +const CSMDoc::Operation *CSMTools::Tools::get (int type) const { return const_cast (this)->get (type); } -CSMTools::Verifier *CSMTools::Tools::getVerifier() +CSMDoc::Operation *CSMTools::Tools::getVerifier() { if (!mVerifier) { - mVerifier = new Verifier; + mVerifier = new CSMDoc::Operation (CSMDoc::State_Verifying, false); connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone())); + connect (mVerifier, SIGNAL (done (int)), this, SIGNAL (done (int))); connect (mVerifier, SIGNAL (reportMessage (const QString&, int)), this, SLOT (verifierMessage (const QString&, int))); @@ -75,6 +75,8 @@ CSMTools::Verifier *CSMTools::Tools::getVerifier() mVerifier->appendStage (new BirthsignCheckStage (mData.getBirthsigns())); mVerifier->appendStage (new SpellCheckStage (mData.getSpells())); + + mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); } return mVerifier; @@ -103,7 +105,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier() void CSMTools::Tools::abortOperation (int type) { - if (Operation *operation = get (type)) + if (CSMDoc::Operation *operation = get (type)) operation->abort(); } @@ -118,7 +120,7 @@ int CSMTools::Tools::getRunningOperations() const int result = 0; for (int i=0; sOperations[i]!=-1; ++i) - if (const Operation *operation = get (sOperations[i])) + if (const CSMDoc::Operation *operation = get (sOperations[i])) if (operation->isRunning()) result |= sOperations[i]; @@ -133,15 +135,11 @@ CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& 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 index 652345c6d..0079fab34 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -11,10 +11,13 @@ namespace CSMWorld class UniversalId; } -namespace CSMTools +namespace CSMDoc { - class Verifier; class Operation; +} + +namespace CSMTools +{ class ReportModel; class Tools : public QObject @@ -22,7 +25,7 @@ namespace CSMTools Q_OBJECT CSMWorld::Data& mData; - Verifier *mVerifier; + CSMDoc::Operation *mVerifier; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number @@ -31,12 +34,12 @@ namespace CSMTools Tools (const Tools&); Tools& operator= (const Tools&); - Verifier *getVerifier(); + CSMDoc::Operation *getVerifier(); - Operation *get (int type); + CSMDoc::Operation *get (int type); ///< Returns a 0-pointer, if operation hasn't been used yet. - const Operation *get (int type) const; + const CSMDoc::Operation *get (int type) const; ///< Returns a 0-pointer, if operation hasn't been used yet. public: @@ -58,8 +61,6 @@ namespace CSMTools private slots: - void verifierDone(); - void verifierMessage (const QString& message, int type); signals: diff --git a/apps/opencs/model/tools/verifier.cpp b/apps/opencs/model/tools/verifier.cpp deleted file mode 100644 index 9c00d4ea7..000000000 --- a/apps/opencs/model/tools/verifier.cpp +++ /dev/null @@ -1,7 +0,0 @@ - -#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 deleted file mode 100644 index 054f87169..000000000 --- a/apps/opencs/model/tools/verifier.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#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/collection.hpp b/apps/opencs/model/world/collection.hpp index 84a00cef8..d342e88a4 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -51,6 +51,18 @@ namespace CSMWorld Collection (const Collection&); Collection& operator= (const Collection&); + protected: + + const std::map& getIdMap() const; + + const std::vector >& getRecords() const; + + bool reorderRowsImp (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + public: Collection(); @@ -86,6 +98,10 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + virtual void cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type); + virtual int searchId (const std::string& id) const; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) @@ -104,7 +120,8 @@ namespace CSMWorld virtual const Record& getRecord (int index) const; - virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const; + virtual int getAppendIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None) const; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted = true) const; @@ -112,12 +129,87 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list + virtual void insertRecord (const RecordBase& record, int index, + UniversalId::Type type = UniversalId::Type_None); + ///< Insert record before index. + /// + /// If the record type does not match, an exception is thrown. + /// + /// If the index is invalid either generally (by being out of range) or for the particular + /// record, an exception is thrown. + + virtual bool reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + void addColumn (Column *column); void setRecord (int index, const Record& record); ///< \attention This function must not change the ID. }; + template + const std::map& Collection::getIdMap() const + { + return mIndex; + } + + template + const std::vector >& Collection::getRecords() const + { + return mRecords; + } + + template + bool Collection::reorderRowsImp (int baseIndex, + const std::vector& newOrder) + { + if (!newOrder.empty()) + { + int size = static_cast (newOrder.size()); + + // check that all indices are present + std::vector test (newOrder); + std::sort (test.begin(), test.end()); + if (*test.begin()!=0 || *--test.end()!=size-1) + return false; + + // reorder records + std::vector > buffer (size); + + for (int i=0; i::iterator iter (mIndex.begin()); iter!=mIndex.end(); + ++iter) + if (iter->second>=baseIndex && iter->secondsecond = newOrder.at (iter->second-baseIndex)+baseIndex; + } + + return true; + } + + template + void Collection::cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type) + { + Record copy; + copy.mModified = getRecord(origin).get(); + copy.mState = RecordBase::State_ModifiedOnly; + copy.get().mId = destination; + + insertRecord(copy, getAppendIndex(destination, type)); + } + template Collection::Collection() {} @@ -142,8 +234,7 @@ namespace CSMWorld record2.mState = Record::State_ModifiedOnly; record2.mModified = record; - mRecords.push_back (record2); - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1)); + insertRecord (record2, getAppendIndex (id)); } else { @@ -260,7 +351,12 @@ namespace CSMWorld ESXRecordT record; IdAccessorT().getId (record) = id; record.blank(); - add (record); + + Record record2; + record2.mState = Record::State_ModifiedOnly; + record2.mModified = record; + + insertRecord (record2, getAppendIndex (id, type), type); } template @@ -286,14 +382,14 @@ namespace CSMWorld void Collection::appendRecord (const RecordBase& record, UniversalId::Type type) { - mRecords.push_back (dynamic_cast&> (record)); - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( - dynamic_cast&> (record).get())), - mRecords.size()-1)); + insertRecord (record, + getAppendIndex (IdAccessorT().getId ( + dynamic_cast&> (record).get()), type), type); } template - int Collection::getAppendIndex (UniversalId::Type type) const + int Collection::getAppendIndex (const std::string& id, + UniversalId::Type type) const { return static_cast (mRecords.size()); } @@ -326,6 +422,29 @@ namespace CSMWorld return mRecords.at (index); } + template + void Collection::insertRecord (const RecordBase& record, int index, + UniversalId::Type type) + { + if (index<0 || index>static_cast (mRecords.size())) + throw std::runtime_error ("index out of range"); + + const Record& record2 = dynamic_cast&> (record); + + mRecords.insert (mRecords.begin()+index, record2); + + if (index (mRecords.size())-1) + { + for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); + ++iter) + if (iter->second>=index) + ++(iter->second); + } + + mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( + record2.get())), index)); + } + template void Collection::setRecord (int index, const Record& record) { @@ -334,6 +453,12 @@ namespace CSMWorld mRecords.at (index) = record; } + + template + bool Collection::reorderRows (int baseIndex, const std::vector& newOrder) + { + return false; + } } #endif diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index 932ea27b5..241f198cb 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -1,6 +1,31 @@ #include "collectionbase.hpp" +#include + +#include "columnbase.hpp" + CSMWorld::CollectionBase::CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {} + +int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const +{ + int columns = getColumns(); + + for (int i=0; i +#include #include "universalid.hpp" +#include "columns.hpp" class QVariant; @@ -72,17 +74,35 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) = 0; ///< If the record type does not match, an exception is thrown. + virtual void cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type) = 0; + virtual const RecordBase& getRecord (const std::string& id) const = 0; virtual const RecordBase& getRecord (int index) const = 0; - virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const = 0; + virtual int getAppendIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None) const = 0; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted = true) const = 0; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list + + virtual bool reorderRows (int baseIndex, const std::vector& newOrder) = 0; + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + + int searchColumnIndex (Columns::ColumnId id) const; + ///< Return index of column with the given \a id. If no such column exists, -1 is returned. + + int findColumnIndex (Columns::ColumnId id) const; + ///< Return index of column with the given \a id. If no such column exists, an exception is + /// thrown. }; } diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c1b423c94..70f38c534 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -43,7 +43,10 @@ namespace CSMWorld Display_CreatureType, Display_WeaponType, Display_RecordState, - Display_RefRecordType + Display_RefRecordType, + Display_DialogueType, + Display_QuestStatusType, + Display_Gender }; int mColumnId; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index a13ac9a8a..18aac9e0b 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,6 +9,7 @@ #include "columnbase.hpp" #include "columns.hpp" +#include "info.hpp" namespace CSMWorld { @@ -814,14 +815,14 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return QString::fromUtf8 (record.get().mCellId.c_str()); + return QString::fromUtf8 (record.get().mCell.c_str()); } virtual void set (Record& record, const QVariant& data) { ESXRecordT record2 = record.get(); - record2.mCellId = data.toString().toUtf8().constData(); + record2.mCell = data.toString().toUtf8().constData(); record.setModified (record2); } @@ -1217,6 +1218,37 @@ namespace CSMWorld } }; + template + struct ScopeColumn : public Column + { + ScopeColumn() + : Column (Columns::ColumnId_Scope, ColumnBase::Display_Integer, 0) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mScope); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mScope = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + + template struct PosColumn : public Column { @@ -1284,6 +1316,373 @@ namespace CSMWorld return true; } }; + + template + struct DialogueTypeColumn : public Column + { + DialogueTypeColumn (bool hidden = false) + : Column (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType, + hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mType); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mType = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + + template + struct QuestStatusTypeColumn : public Column + { + QuestStatusTypeColumn() + : Column (Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mQuestStatus); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mQuestStatus = static_cast (data.toInt()); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct QuestDescriptionColumn : public Column + { + QuestDescriptionColumn() : Column (Columns::ColumnId_QuestDescription, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mResponse.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mResponse = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct QuestIndexColumn : public Column + { + QuestIndexColumn() + : Column (Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mDisposition; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mDisposition = data.toInt(); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct TopicColumn : public Column + { + TopicColumn (bool journal) : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mTopicId.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mTopicId = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + + template + struct ActorColumn : public Column + { + ActorColumn() : Column (Columns::ColumnId_Actor, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mActor.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mActor = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct RaceColumn : public Column + { + RaceColumn() : Column (Columns::ColumnId_Race, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mRace.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct ClassColumn : public Column + { + ClassColumn() : Column (Columns::ColumnId_Class, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mClass.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mClass = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct PcFactionColumn : public Column + { + PcFactionColumn() : Column (Columns::ColumnId_PcFaction, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mPcFaction.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mPcFaction = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct ResponseColumn : public Column + { + ResponseColumn() : Column (Columns::ColumnId_Response, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mResponse.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mResponse = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct DispositionColumn : public Column + { + DispositionColumn() + : Column (Columns::ColumnId_Disposition, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mDisposition; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mDisposition = data.toInt(); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct RankColumn : public Column + { + RankColumn() + : Column (Columns::ColumnId_Rank, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mData.mRank); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mRank = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct PcRankColumn : public Column + { + PcRankColumn() + : Column (Columns::ColumnId_PcRank, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mData.mPCrank); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mPCrank = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct GenderColumn : public Column + { + GenderColumn() + : Column (Columns::ColumnId_Gender, ColumnBase::Display_Gender) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mData.mGender); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mData.mGender = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index ca37840ad..2f3911270 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -159,6 +159,20 @@ namespace CSMWorld { ColumnId_DoorPositionXRot, "Teleport Rot X" }, { ColumnId_DoorPositionYRot, "Teleport Rot Y" }, { ColumnId_DoorPositionZRot, "Teleport Rot Z" }, + { ColumnId_DialogueType, "Dialogue Type" }, + { ColumnId_QuestIndex, "Quest Index" }, + { ColumnId_QuestStatusType, "Quest Status" }, + { ColumnId_QuestDescription, "Quest Description" }, + { ColumnId_Topic, "Topic" }, + { ColumnId_Journal, "Journal" }, + { ColumnId_Actor, "Actor" }, + { ColumnId_PcFaction, "PC Faction" }, + { ColumnId_Response, "Response" }, + { ColumnId_Disposition, "Disposition" }, + { ColumnId_Rank, "Rank" }, + { ColumnId_Gender, "Gender" }, + { ColumnId_PcRank, "PC Rank" }, + { ColumnId_Scope, "Scope", }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, @@ -206,7 +220,7 @@ int CSMWorld::Columns::getId (const std::string& name) std::string name2 = Misc::StringUtils::lowerCase (name); for (int i=0; sNames[i].mName; ++i) - if (name2==Misc::StringUtils::lowerCase (sNames[i].mName)) + if (Misc::StringUtils::ciEqual(sNames[i].mName, name2)) return sNames[i].mId; return -1; @@ -249,7 +263,7 @@ namespace static const char *sCreatureTypes[] = { - "Creature", "Deadra", "Undead", "Humanoid", 0 + "Creature", "Daedra", "Undead", "Humanoid", 0 }; static const char *sWeaponTypes[] = @@ -269,6 +283,21 @@ namespace "unknown", "none", "short", "integer", "long", "float", "string", 0 }; + static const char *sDialogueTypeEnums[] = + { + "Topic", "Voice", "Greeting", "Persuasion", 0 + }; + + static const char *sQuestStatusTypes[] = + { + "None", "Name", "Finished", "Restart", 0 + }; + + static const char *sGenderEnums[] = + { + "Male", "Female", 0 + }; + const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -283,6 +312,9 @@ namespace case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes; case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; + case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums; + case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes; + case CSMWorld::Columns::ColumnId_Gender: return sGenderEnums; default: return 0; } @@ -310,4 +342,4 @@ std::vector CSMWorld::Columns::getEnums (ColumnId column) } return enums; -} \ No newline at end of file +} diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 9b26cac4c..52e022e78 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -152,6 +152,20 @@ namespace CSMWorld ColumnId_DoorPositionXRot = 139, ColumnId_DoorPositionYRot = 140, ColumnId_DoorPositionZRot = 141, + ColumnId_DialogueType = 142, + ColumnId_QuestIndex = 143, + ColumnId_QuestStatusType = 144, + ColumnId_QuestDescription = 145, + ColumnId_Topic = 146, + ColumnId_Journal = 147, + ColumnId_Actor = 148, + ColumnId_PcFaction = 149, + ColumnId_Response = 150, + ColumnId_Disposition = 151, + ColumnId_Rank = 152, + ColumnId_Gender = 153, + ColumnId_PcRank = 154, + ColumnId_Scope = 155, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index f6f421c6a..b60ffeb29 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -4,10 +4,10 @@ #include #include "idtable.hpp" -#include "idtable.hpp" +#include CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, - const QVariant& new_, QUndoCommand *parent) + const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_) { mOld = mModel.data (mIndex, Qt::EditRole); @@ -25,7 +25,7 @@ void CSMWorld::ModifyCommand::undo() mModel.setData (mIndex, mOld); } -CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) { setText (("Create record " + id).c_str()); @@ -54,7 +54,7 @@ void CSMWorld::CreateCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } -CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +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()); @@ -89,7 +89,7 @@ void CSMWorld::RevertCommand::undo() mModel.setRecord (mId, *mOld); } -CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +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()); @@ -122,4 +122,53 @@ void CSMWorld::DeleteCommand::redo() void CSMWorld::DeleteCommand::undo() { mModel.setRecord (mId, *mOld); +} + + +CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex, + const std::vector& newOrder) +: mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder) +{} + +void CSMWorld::ReorderRowsCommand::redo() +{ + mModel.reorderRows (mBaseIndex, mNewOrder); +} + +void CSMWorld::ReorderRowsCommand::undo() +{ + int size = static_cast (mNewOrder.size()); + std::vector reverse (size); + + for (int i=0; i< size; ++i) + reverse.at (mNewOrder[i]) = i; + + mModel.reorderRows (mBaseIndex, reverse); +} + +CSMWorld::CloneCommand::CloneCommand (CSMWorld::IdTable& model, + const std::string& idOrigin, + const std::string& IdDestination, + const CSMWorld::UniversalId::Type type, + QUndoCommand* parent) : + QUndoCommand (parent), + mModel (model), + mIdOrigin (idOrigin), + mIdDestination (Misc::StringUtils::lowerCase (IdDestination)), + mType (type) +{ + setText ( ("Clone record " + idOrigin + " to the " + IdDestination).c_str()); +} + +void CSMWorld::CloneCommand::redo() +{ + mModel.cloneRecord (mIdOrigin, mIdDestination, mType); + + for (std::map::const_iterator iter (mValues.begin()); iter != mValues.end(); ++iter) + mModel.setData (mModel.getModelIndex (mIdDestination, iter->first), iter->second); +} + +void CSMWorld::CloneCommand::undo() +{ + mModel.removeRow (mModel.getModelIndex (mIdDestination, 0).row()); } \ No newline at end of file diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 3fc2522ea..ec6350658 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -38,6 +39,26 @@ namespace CSMWorld virtual void undo(); }; + class CloneCommand : public QUndoCommand + { + IdTable& mModel; + std::string mIdOrigin; + std::string mIdDestination; + UniversalId::Type mType; + std::map mValues; + + public: + + CloneCommand (IdTable& model, const std::string& idOrigin, + const std::string& IdDestination, + const UniversalId::Type type, + QUndoCommand* parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + class CreateCommand : public QUndoCommand { IdTable& mModel; @@ -99,6 +120,21 @@ namespace CSMWorld virtual void undo(); }; + + class ReorderRowsCommand : public QUndoCommand + { + IdTable& mModel; + int mBaseIndex; + std::vector mNewOrder; + + public: + + ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector& newOrder); + + 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 index 1e290d45f..8d53c4e53 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -44,6 +44,17 @@ void CSMWorld::Data::appendIds (std::vector& ids, const CollectionB ids.insert (ids.end(), ids2.begin(), ids2.end()); } +int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collection) +{ + int number = 0; + + for (int i=0; i); @@ -141,6 +152,41 @@ CSMWorld::Data::Data() : mRefs (mCells) mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AlwaysSucceeds, 0x4)); + mTopics.addColumn (new StringIdColumn); + mTopics.addColumn (new RecordStateColumn); + mTopics.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Topic)); + mTopics.addColumn (new DialogueTypeColumn); + + mJournals.addColumn (new StringIdColumn); + mJournals.addColumn (new RecordStateColumn); + mJournals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); + mJournals.addColumn (new DialogueTypeColumn (true)); + + mTopicInfos.addColumn (new StringIdColumn (true)); + mTopicInfos.addColumn (new RecordStateColumn); + mTopicInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_TopicInfo)); + mTopicInfos.addColumn (new TopicColumn (false)); + mTopicInfos.addColumn (new ActorColumn); + mTopicInfos.addColumn (new RaceColumn); + mTopicInfos.addColumn (new ClassColumn); + mTopicInfos.addColumn (new FactionColumn); + mTopicInfos.addColumn (new CellColumn); + mTopicInfos.addColumn (new DispositionColumn); + mTopicInfos.addColumn (new RankColumn); + mTopicInfos.addColumn (new GenderColumn); + mTopicInfos.addColumn (new PcFactionColumn); + mTopicInfos.addColumn (new PcRankColumn); + mTopicInfos.addColumn (new SoundFileColumn); + mTopicInfos.addColumn (new ResponseColumn); + + mJournalInfos.addColumn (new StringIdColumn (true)); + mJournalInfos.addColumn (new RecordStateColumn); + mJournalInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); + mJournalInfos.addColumn (new TopicColumn (true)); + mJournalInfos.addColumn (new QuestStatusTypeColumn); + mJournalInfos.addColumn (new QuestIndexColumn); + mJournalInfos.addColumn (new QuestDescriptionColumn); + mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); @@ -152,6 +198,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mRefs.addColumn (new StringIdColumn (true)); mRefs.addColumn (new RecordStateColumn); + mRefs.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Reference)); mRefs.addColumn (new CellColumn); mRefs.addColumn (new IdColumn); mRefs.addColumn (new PosColumn (&CellRef::mPos, 0, false)); @@ -182,8 +229,10 @@ CSMWorld::Data::Data() : mRefs (mCells) mFilters.addColumn (new StringIdColumn); mFilters.addColumn (new RecordStateColumn); + mFilters.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Filter)); mFilters.addColumn (new FilterColumn); mFilters.addColumn (new DescriptionColumn); + mFilters.addColumn (new ScopeColumn); addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); @@ -196,6 +245,10 @@ CSMWorld::Data::Data() : mRefs (mCells) addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region); addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign); addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); + addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); + addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); + addModel (new IdTable (&mTopicInfos), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); + addModel (new IdTable (&mJournalInfos), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell); addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); @@ -319,6 +372,47 @@ CSMWorld::IdCollection& CSMWorld::Data::getSpells() return mSpells; } + +const CSMWorld::IdCollection& CSMWorld::Data::getTopics() const +{ + return mTopics; +} + +CSMWorld::IdCollection& CSMWorld::Data::getTopics() +{ + return mTopics; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getJournals() const +{ + return mJournals; +} + +CSMWorld::IdCollection& CSMWorld::Data::getJournals() +{ + return mJournals; +} + +const CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() const +{ + return mTopicInfos; +} + +CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() +{ + return mTopicInfos; +} + +const CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() const +{ + return mJournalInfos; +} + +CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() +{ + return mJournalInfos; +} + const CSMWorld::IdCollection& CSMWorld::Data::getCells() const { return mCells; @@ -387,7 +481,7 @@ void CSMWorld::Data::merge() mGlobals.merge(); } -void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) +void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, bool project) { ESM::ESMReader reader; @@ -397,6 +491,11 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) reader.open (path.string()); + const ESM::Dialogue *dialogue = 0; + + mAuthor = reader.getAuthor(); + mDescription = reader.getDesc(); + // 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()) @@ -447,9 +546,80 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break; case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break; + case ESM::REC_DIAL: + { + std::string id = reader.getHNOString ("NAME"); + + ESM::Dialogue record; + record.mId = id; + record.load (reader); + + if (record.mType==ESM::Dialogue::Journal) + { + mJournals.load (record, base); + dialogue = &mJournals.getRecord (id).get(); + } + else if (record.mType==ESM::Dialogue::Deleted) + { + dialogue = 0; // record vector can be shuffled around which would make pointer + // to record invalid + + if (mJournals.tryDelete (id)) + { + /// \todo handle info records + } + else if (mTopics.tryDelete (id)) + { + /// \todo handle info records + } + else + { + /// \todo report deletion of non-existing record + } + } + else + { + mTopics.load (record, base); + dialogue = &mTopics.getRecord (id).get(); + } + + break; + } + + case ESM::REC_INFO: + { + if (!dialogue) + { + /// \todo INFO record without matching DIAL record -> report to user + reader.skipRecord(); + break; + } + + if (dialogue->mType==ESM::Dialogue::Journal) + mJournalInfos.load (reader, base, *dialogue); + else + mTopicInfos.load (reader, base, *dialogue); + + break; + } + + case ESM::REC_FILT: + + if (project) + { + mFilters.load (reader, base); + mFilters.setData (mFilters.getSize()-1, + mFilters.findColumnIndex (CSMWorld::Columns::ColumnId_Scope), + static_cast (CSMFilter::Filter::Scope_Project)); + break; + } + + // fall through (filter record in a content file is an error with format 0) + default: /// \todo throw an exception instead, once all records are implemented + /// or maybe report error and continue? reader.skipRecord(); } } @@ -469,10 +639,50 @@ bool CSMWorld::Data::hasId (const std::string& id) const getRegions().searchId (id)!=-1 || getBirthsigns().searchId (id)!=-1 || getSpells().searchId (id)!=-1 || + getTopics().searchId (id)!=-1 || + getJournals().searchId (id)!=-1 || getCells().searchId (id)!=-1 || getReferenceables().searchId (id)!=-1; } +int CSMWorld::Data::count (RecordBase::State state) const +{ + return + count (state, mGlobals) + + count (state, mGmsts) + + count (state, mSkills) + + count (state, mClasses) + + count (state, mFactions) + + count (state, mRaces) + + count (state, mSounds) + + count (state, mScripts) + + count (state, mRegions) + + count (state, mBirthsigns) + + count (state, mSpells) + + count (state, mCells) + + count (state, mReferenceables); +} + +void CSMWorld::Data::setDescription (const std::string& description) +{ + mDescription = description; +} + +std::string CSMWorld::Data::getDescription() const +{ + return mDescription; +} + +void CSMWorld::Data::setAuthor (const std::string& author) +{ + mAuthor = author; +} + +std::string CSMWorld::Data::getAuthor() const +{ + return mAuthor; +} + std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; @@ -487,6 +697,8 @@ std::vector CSMWorld::Data::getIds (bool listDeleted) const appendIds (ids, mRegions, listDeleted); appendIds (ids, mBirthsigns, listDeleted); appendIds (ids, mSpells, listDeleted); + appendIds (ids, mTopics, listDeleted); + appendIds (ids, mJournals, listDeleted); appendIds (ids, mCells, listDeleted); appendIds (ids, mReferenceables, listDeleted); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index e900bb10f..152c3ac41 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "../filter/filter.hpp" @@ -28,6 +29,7 @@ #include "cell.hpp" #include "refidcollection.hpp" #include "refcollection.hpp" +#include "infocollection.hpp" class QAbstractItemModel; @@ -48,12 +50,18 @@ namespace CSMWorld IdCollection mRegions; IdCollection mBirthsigns; IdCollection mSpells; + IdCollection mTopics; + IdCollection mJournals; + InfoCollection mTopicInfos; + InfoCollection mJournalInfos; IdCollection mCells; RefIdCollection mReferenceables; RefCollection mRefs; IdCollection mFilters; std::vector mModels; std::map mModelIndex; + std::string mAuthor; + std::string mDescription; // not implemented Data (const Data&); @@ -66,6 +74,8 @@ namespace CSMWorld bool listDeleted); ///< Append all IDs from collection to \a ids. + static int count (RecordBase::State state, const CollectionBase& collection); + public: Data(); @@ -116,6 +126,22 @@ namespace CSMWorld IdCollection& getSpells(); + const IdCollection& getTopics() const; + + IdCollection& getTopics(); + + const IdCollection& getJournals() const; + + IdCollection& getJournals(); + + const InfoCollection& getTopicInfos() const; + + InfoCollection& getTopicInfos(); + + const InfoCollection& getJournalInfos() const; + + InfoCollection& getJournalInfos(); + const IdCollection& getCells() const; IdCollection& getCells(); @@ -141,8 +167,10 @@ namespace CSMWorld void merge(); ///< Merge modified into base. - void loadFile (const boost::filesystem::path& path, bool base); + void loadFile (const boost::filesystem::path& path, bool base, bool project); ///< Merging content of a file into base or modified. + /// + /// \param project load project file instead of content file bool hasId (const std::string& id) const; @@ -151,6 +179,17 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list + int count (RecordBase::State state) const; + ///< Return number of top-level records with the given \a state. + + void setDescription (const std::string& description); + + std::string getDescription() const; + + void setAuthor (const std::string& author); + + std::string getAuthor() const; + signals: void idListChanged(); diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 04e65eea7..a7b37be5b 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -7,21 +7,24 @@ namespace CSMWorld { - /// \brief Single type collection of top level records template > class IdCollection : public Collection { public: - void load (ESM::ESMReader& reader, bool base, - UniversalId::Type type = UniversalId::Type_None); - ///< \param type Will be ignored, unless the collection supports multiple record types + void load (ESM::ESMReader& reader, bool base); + + void load (const ESXRecordT& record, bool base); + + bool tryDelete (const std::string& id); + ///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored. + /// + /// \return Has the ID been deleted? }; template - void IdCollection::load (ESM::ESMReader& reader, bool base, - UniversalId::Type type) + void IdCollection::load (ESM::ESMReader& reader, bool base) { std::string id = reader.getHNOString ("NAME"); @@ -56,30 +59,62 @@ namespace CSMWorld IdAccessorT().getId (record) = id; record.load (reader); - int index = this->searchId (IdAccessorT().getId (record)); + load (record, base); + } + } - if (index==-1) - { - // new record - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = record; + template + void IdCollection::load (const ESXRecordT& record, bool base) + { + int index = this->searchId (IdAccessorT().getId (record)); - this->appendRecord (record2); - } + if (index==-1) + { + // new record + Record record2; + record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2.mBase : record2.mModified) = record; + + this->appendRecord (record2); + } + else + { + // old record + Record record2 = Collection::getRecord (index); + + if (base) + record2.mBase = record; else - { - // old record - Record record2 = Collection::getRecord (index); + record2.setModified (record); - if (base) - record2.mBase = record; - else - record2.setModified (record); + this->setRecord (index, record2); + } + } - this->setRecord (index, record2); - } + template + bool IdCollection::tryDelete (const std::string& id) + { + int index = this->searchId (id); + + if (index==-1) + return false; + + Record record = Collection::getRecord (index); + + if (record.isDeleted()) + return false; + + if (record.mState==RecordBase::State_ModifiedOnly) + { + Collection::removeRows (index, 1); } + else + { + record.mState = RecordBase::State_Deleted; + this->setRecord (index, record); + } + + return true; } } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index baaf75289..bea307aa2 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -4,15 +4,12 @@ #include "collectionbase.hpp" #include "columnbase.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection) : mIdCollection (idCollection) -{ - -} +CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering) +: mIdCollection (idCollection), mReordering (reordering) +{} CSMWorld::IdTable::~IdTable() -{ - -} +{} int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const { @@ -118,7 +115,7 @@ QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type) { - int index = mIdCollection->getAppendIndex(); + int index = mIdCollection->getAppendIndex (id, type); beginInsertRows (QModelIndex(), index, index); @@ -127,6 +124,17 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type endInsertRows(); } +void CSMWorld::IdTable::cloneRecord(const std::string& origin, + const std::string& destination, + CSMWorld::UniversalId::Type type) +{ + int index = mIdCollection->getAppendIndex (destination); + beginInsertRows (QModelIndex(), index, index); + mIdCollection->cloneRecord(origin, destination, type); + endInsertRows(); +} + + QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { return index (mIdCollection->getIndex (id), column); @@ -138,7 +146,7 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco if (index==-1) { - int index = mIdCollection->getAppendIndex(); + int index = mIdCollection->getAppendIndex (id); beginInsertRows (QModelIndex(), index, index); @@ -161,21 +169,23 @@ const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const { - int columns = mIdCollection->getColumns(); - - for (int i=0; igetColumn (i).mColumnId==id) - return i; - - return -1; + return mIdCollection->searchColumnIndex (id); } int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const { - int index = searchColumnIndex (id); + return mIdCollection->findColumnIndex (id); +} - if (index==-1) - throw std::logic_error ("invalid column index"); +void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newOrder) +{ + if (!newOrder.empty()) + if (mIdCollection->reorderRows (baseIndex, newOrder)) + emit dataChanged (index (baseIndex, 0), + index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1)); +} - return index; +CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const +{ + return mReordering; } \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 00c577236..74923867d 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H +#include + #include #include "universalid.hpp" @@ -15,7 +17,18 @@ namespace CSMWorld { Q_OBJECT + public: + + enum Reordering + { + Reordering_None, + Reordering_WithinTopic + }; + + private: + CollectionBase *mIdCollection; + Reordering mReordering; // not implemented IdTable (const IdTable&); @@ -23,7 +36,7 @@ namespace CSMWorld public: - IdTable (CollectionBase *idCollection); + IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); @@ -50,6 +63,10 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord(const std::string& origin, + const std::string& destination, + UniversalId::Type type = UniversalId::Type_None); + QModelIndex getModelIndex (const std::string& id, int column) const; void setRecord (const std::string& id, const RecordBase& record); @@ -63,6 +80,12 @@ namespace CSMWorld int findColumnIndex (Columns::ColumnId id) const; ///< Return index of column with the given \a id. If no such column exists, an exception is /// thrown. + + void reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + + Reordering getReordering() const; }; } diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 2b757adfe..f51b7f818 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -33,7 +33,9 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) : QSortFilterProxyModel (parent) -{} +{ + setSortCaseSensitivity (Qt::CaseInsensitive); +} QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { diff --git a/apps/opencs/model/world/info.hpp b/apps/opencs/model/world/info.hpp new file mode 100644 index 000000000..1bcb2dc2d --- /dev/null +++ b/apps/opencs/model/world/info.hpp @@ -0,0 +1,14 @@ +#ifndef CSM_WOLRD_INFO_H +#define CSM_WOLRD_INFO_H + +#include + +namespace CSMWorld +{ + struct Info : public ESM::DialInfo + { + std::string mTopicId; + }; +} + +#endif diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp new file mode 100644 index 000000000..50d09f313 --- /dev/null +++ b/apps/opencs/model/world/infocollection.cpp @@ -0,0 +1,184 @@ + +#include "infocollection.hpp" + +#include +#include + +#include +#include + +#include + +void CSMWorld::InfoCollection::load (const Info& record, bool base) +{ + int index = searchId (record.mId); + + if (index==-1) + { + // new record + Record record2; + record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2.mBase : record2.mModified) = record; + + int index = -1; + + std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); + + if (!record2.get().mPrev.empty()) + { + index = getIndex (record2.get().mPrev, topic); + + if (index!=-1) + ++index; + } + + if (index==-1 && !record2.get().mNext.empty()) + { + index = getIndex (record2.get().mNext, topic); + } + + if (index==-1) + { + Range range = getTopicRange (topic); + + index = std::distance (getRecords().begin(), range.second); + } + + insertRecord (record2, index); + } + else + { + // old record + Record record2 = getRecord (index); + + if (base) + record2.mBase = record; + else + record2.setModified (record); + + setRecord (index, record2); + } +} + +int CSMWorld::InfoCollection::getIndex (const std::string& id, const std::string& topic) const +{ + std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; + + std::pair range = getTopicRange (topic); + + for (; range.first!=range.second; ++range.first) + if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId)) + return std::distance (getRecords().begin(), range.first); + + return -1; +} + +int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const +{ + std::string::size_type separator = id.find_last_of ('#'); + + if (separator==std::string::npos) + throw std::runtime_error ("invalid info ID: " + id); + + std::pair range = getTopicRange (id.substr (0, separator)); + + if (range.first==range.second) + return Collection >::getAppendIndex (id, type); + + return std::distance (getRecords().begin(), range.second); +} + +bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector& newOrder) +{ + // check if the range is valid + int lastIndex = baseIndex + newOrder.size() -1; + + if (lastIndex>=getSize()) + return false; + + // Check that topics match + if (getRecord (baseIndex).get().mTopicId!=getRecord (lastIndex).get().mTopicId) + return false; + + // reorder + return reorderRowsImp (baseIndex, newOrder); +} + +void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) +{ + std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + + reader.getHNOString ("INAM"); + + if (reader.isNextSub ("DELE")) + { + int index = searchId (id); + + 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 + { + Record record = getRecord (index); + record.mState = RecordBase::State_Deleted; + setRecord (index, record); + } + } + else + { + Info record; + record.mTopicId = dialogue.mId; + record.mId = id; + record.load (reader); + + load (record, base); + } +} + +CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic) + const +{ + std::string topic2 = Misc::StringUtils::lowerCase (topic); + + std::map::const_iterator iter = getIdMap().lower_bound (topic2); + + // Skip invalid records: The beginning of a topic string could be identical to another topic + // string. + for (; iter!=getIdMap().end(); ++iter) + { + std::string testTopicId = + Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId); + + if (testTopicId==topic2) + break; + + std::size_t size = topic2.size(); + + if (testTopicId.size()second; + + // Find end + RecordConstIterator end = begin; + + for (; end!=getRecords().end(); ++end) + if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2)) + break; + + return Range (begin, end); +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp new file mode 100644 index 000000000..ae61f5d39 --- /dev/null +++ b/apps/opencs/model/world/infocollection.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_WOLRD_INFOCOLLECTION_H +#define CSM_WOLRD_INFOCOLLECTION_H + +#include "collection.hpp" +#include "info.hpp" + +namespace ESM +{ + class Dialogue; +} + +namespace CSMWorld +{ + class InfoCollection : public Collection > + { + public: + + typedef std::vector >::const_iterator RecordConstIterator; + typedef std::pair Range; + + private: + + void load (const Info& record, bool base); + + int getIndex (const std::string& id, const std::string& topic) const; + ///< Return index for record \a id or -1 (if not present; deleted records are considered) + /// + /// \param id info ID without topic prefix + + public: + + virtual int getAppendIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None) const; + ///< \param type Will be ignored, unless the collection supports multiple record types + + virtual bool reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + + void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); + + Range getTopicRange (const std::string& topic) const; + ///< Return iterators that point to the beginning and past the end of the range for + /// the given topic. + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index af363bafb..cf9e496ee 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -6,8 +6,7 @@ void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id) { mId = id; - mCellId = cell.mId; + mCell = cell.mId; - if (!mDeleted) - cell.addRef (mId); + cell.addRef (mId); } \ No newline at end of file diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index 3d107d675..fcf016ee2 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -16,7 +16,7 @@ namespace CSMWorld struct CellRef : public ESM::CellRef { std::string mId; - std::string mCellId; + std::string mCell; void load (ESM::ESMReader &esm, Cell& cell, const std::string& id); ///< Load cell ref and register it with \a cell. diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 696aeefaa..9ee59bd1e 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -5,6 +5,8 @@ #include "ref.hpp" #include "cell.hpp" +#include "universalid.hpp" +#include "record.hpp" void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base) { @@ -14,7 +16,8 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CellRef ref; - while (cell2.getNextRef (reader, ref)) + bool deleted = false; + while (cell2.getNextRef (reader, ref, deleted)) { /// \todo handle deleted and moved references ref.load (reader, cell2, getNewId()); @@ -34,4 +37,15 @@ std::string CSMWorld::RefCollection::getNewId() std::ostringstream stream; stream << "ref#" << mNextId++; return stream.str(); +} + +void CSMWorld::RefCollection::cloneRecord(const std::string& origin, + const std::string& destination, + const CSMWorld::UniversalId::Type type, + const CSMWorld::UniversalId::ArgumentType argumentType) +{ + Record clone(getRecord(origin)); + clone.mState = CSMWorld::RecordBase::State_ModifiedOnly; + clone.get().mId = destination; + insertRecord(clone, getAppendIndex(destination, type), type); } \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index b5f8c8064..dcfd2036c 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -8,6 +8,7 @@ namespace CSMWorld { struct Cell; + struct UniversalId; /// \brief References in cells class RefCollection : public Collection @@ -25,6 +26,11 @@ namespace CSMWorld ///< Load a sequence of references. std::string getNewId(); + + void cloneRecord(const std::string& origin, + const std::string& destination, + const CSMWorld::UniversalId::Type type, + const CSMWorld::UniversalId::ArgumentType argumentType); }; } diff --git a/apps/opencs/model/world/refidadapter.hpp b/apps/opencs/model/world/refidadapter.hpp index df0ceae70..0870a2d3e 100644 --- a/apps/opencs/model/world/refidadapter.hpp +++ b/apps/opencs/model/world/refidadapter.hpp @@ -31,6 +31,7 @@ namespace CSMWorld ///< If the data type does not match an exception is thrown. virtual std::string getId (const RecordBase& record) const = 0; + virtual void setId(RecordBase& record, const std::string& id) = 0; }; } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index d5d84a8f7..bd509a86b 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -34,6 +34,8 @@ namespace CSMWorld BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base); virtual std::string getId (const RecordBase& record) const; + + virtual void setId (RecordBase& record, const std::string& id); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -50,6 +52,12 @@ namespace CSMWorld : mType (type), mBase (base) {} + template + void BaseRefIdAdapter::setId (RecordBase& record, const std::string& id) + { + (dynamic_cast&> (record).get().mId) = id; + } + template std::string BaseRefIdAdapter::getId (const RecordBase& record) const { diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index cda2711cc..8a1af3509 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -2,6 +2,7 @@ #include "refidcollection.hpp" #include +#include #include @@ -181,7 +182,7 @@ CSMWorld::RefIdCollection::RefIdCollection() unsigned int mFlag; } sCreatureFlagTable[] = { - { Columns::ColumnId_Biped, ESM::Creature::Biped }, + { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, { Columns::ColumnId_NoMovement, ESM::Creature::None }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, @@ -449,6 +450,16 @@ void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record) mData.getRecord (mData.globalToLocalIndex (index)).assign (record); } +void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, + const std::string& destination, + const CSMWorld::UniversalId::Type type) +{ + std::auto_ptr newRecord(mData.getRecord(mData.searchId(origin)).clone()); + newRecord->mState = RecordBase::State_ModifiedOnly; + mAdapters.find(type)->second->setId(*newRecord, destination); + mData.insertRecord(*newRecord, type, destination); +} + void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record, UniversalId::Type type) { @@ -530,7 +541,7 @@ void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, Univers } } -int CSMWorld::RefIdCollection::getAppendIndex (UniversalId::Type type) const +int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const { return mData.getAppendIndex (type); } @@ -539,3 +550,19 @@ std::vector CSMWorld::RefIdCollection::getIds (bool listDeleted) co { return mData.getIds (listDeleted); } + +bool CSMWorld::RefIdCollection::reorderRows (int baseIndex, const std::vector& newOrder) +{ + return false; +} + +void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const +{ + mData.save (index, writer); +} + +const CSMWorld::RefIdData& CSMWorld::RefIdCollection::getDataSet() const +{ + return mData; +} + diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 22f83150d..dd6213677 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -9,6 +9,11 @@ #include "collectionbase.hpp" #include "refiddata.hpp" +namespace ESM +{ + class ESMWriter; +} + namespace CSMWorld { class RefIdAdapter; @@ -64,6 +69,10 @@ namespace CSMWorld virtual void removeRows (int index, int count); + virtual void cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type); + virtual void appendBlankRecord (const std::string& id, UniversalId::Type type); ///< \param type Will be ignored, unless the collection supports multiple record types @@ -87,14 +96,25 @@ namespace CSMWorld void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); - virtual int getAppendIndex (UniversalId::Type type) const; + virtual int getAppendIndex (const std::string& id, UniversalId::Type type) const; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted) const; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list + + virtual bool reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + + void save (int index, ESM::ESMWriter& writer) const; + + const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( }; } #endif + diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 9457937f1..9d980a99c 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -218,3 +218,130 @@ std::vector CSMWorld::RefIdData::getIds (bool listDeleted) const return ids; } + +void CSMWorld::RefIdData::save (int index, ESM::ESMWriter& writer) const +{ + LocalIndex localIndex = globalToLocalIndex (index); + + std::map::const_iterator iter = + mRecordContainers.find (localIndex.second); + + if (iter==mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + iter->second->save (localIndex.first, writer); +} + +const CSMWorld::RefIdDataContainer< ESM::Book >& CSMWorld::RefIdData::getBooks() const +{ + return mBooks; +} + +const CSMWorld::RefIdDataContainer< ESM::Activator >& CSMWorld::RefIdData::getActivators() const +{ + return mActivators; +} + +const CSMWorld::RefIdDataContainer< ESM::Potion >& CSMWorld::RefIdData::getPotions() const +{ + return mPotions; +} + +const CSMWorld::RefIdDataContainer< ESM::Apparatus >& CSMWorld::RefIdData::getApparati() const +{ + return mApparati; +} + +const CSMWorld::RefIdDataContainer< ESM::Armor >& CSMWorld::RefIdData::getArmors() const +{ + return mArmors; +} + +const CSMWorld::RefIdDataContainer< ESM::Clothing >& CSMWorld::RefIdData::getClothing() const +{ + return mClothing; +} + +const CSMWorld::RefIdDataContainer< ESM::Container >& CSMWorld::RefIdData::getContainers() const +{ + return mContainers; +} + +const CSMWorld::RefIdDataContainer< ESM::Creature >& CSMWorld::RefIdData::getCreatures() const +{ + return mCreatures; +} + +const CSMWorld::RefIdDataContainer< ESM::Door >& CSMWorld::RefIdData::getDoors() const +{ + return mDoors; +} + +const CSMWorld::RefIdDataContainer< ESM::Ingredient >& CSMWorld::RefIdData::getIngredients() const +{ + return mIngredients; +} + +const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& CSMWorld::RefIdData::getCreatureLevelledLists() const +{ + return mCreatureLevelledLists; +} + +const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& CSMWorld::RefIdData::getItemLevelledList() const +{ + return mItemLevelledLists; +} + +const CSMWorld::RefIdDataContainer< ESM::Light >& CSMWorld::RefIdData::getLights() const +{ + return mLights; +} + +const CSMWorld::RefIdDataContainer< ESM::Lockpick >& CSMWorld::RefIdData::getLocpicks() const +{ + return mLockpicks; +} + +const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& CSMWorld::RefIdData::getMiscellaneous() const +{ + return mMiscellaneous; +} + +const CSMWorld::RefIdDataContainer< ESM::NPC >& CSMWorld::RefIdData::getNPCs() const +{ + return mNpcs; +} + +const CSMWorld::RefIdDataContainer< ESM::Weapon >& CSMWorld::RefIdData::getWeapons() const +{ + return mWeapons; +} + +const CSMWorld::RefIdDataContainer< ESM::Probe >& CSMWorld::RefIdData::getProbes() const +{ + return mProbes; +} + +const CSMWorld::RefIdDataContainer< ESM::Repair >& CSMWorld::RefIdData::getRepairs() const +{ + return mRepairs; +} + +const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStatics() const +{ + return mStatics; +} + +void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +{ + std::map::iterator iter = + mRecordContainers.find (type); + + if (iter==mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + iter->second->insertRecord(record); + + mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), + LocalIndex (iter->second->getSize()-1, type))); +} diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index e221fbc7c..cf98fee21 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "record.hpp" #include "universalid.hpp" @@ -45,12 +46,16 @@ namespace CSMWorld virtual RecordBase& getRecord (int index)= 0; virtual void appendRecord (const std::string& id) = 0; + + virtual void insertRecord (RecordBase& record) = 0; virtual void load (int index, ESM::ESMReader& reader, bool base) = 0; virtual void erase (int index, int count) = 0; virtual std::string getId (int index) const = 0; + + virtual void save (int index, ESM::ESMWriter& writer) const = 0; }; template @@ -65,14 +70,25 @@ namespace CSMWorld virtual RecordBase& getRecord (int index); virtual void appendRecord (const std::string& id); + + virtual void insertRecord (RecordBase& record); virtual void load (int index, ESM::ESMReader& reader, bool base); virtual void erase (int index, int count); virtual std::string getId (int index) const; + + virtual void save (int index, ESM::ESMWriter& writer) const; }; + template + void RefIdDataContainer::insertRecord(RecordBase& record) + { + Record& newRecord = dynamic_cast& >(record); + mContainer.push_back(newRecord); + } + template int RefIdDataContainer::getSize() const { @@ -123,6 +139,26 @@ namespace CSMWorld return mContainer.at (index).get().mId; } + template + void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const + { + CSMWorld::RecordBase::State state = mContainer.at (index).mState; + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly) + { + writer.startRecord (mContainer.at (index).mModified.sRecordId); + writer.writeHNCString ("NAME", getId (index)); + mContainer.at (index).mModified.save (writer); + writer.endRecord (mContainer.at (index).mModified.sRecordId); + } + else if (state==CSMWorld::RecordBase::State_Deleted) + { + /// \todo write record with delete flag + } + } + + class RefIdData { public: @@ -170,6 +206,8 @@ namespace CSMWorld LocalIndex searchId (const std::string& id) const; void erase (int index, int count); + + void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; @@ -187,7 +225,35 @@ namespace CSMWorld ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list + + void save (int index, ESM::ESMWriter& writer) const; + + //RECORD CONTAINERS ACCESS METHODS + const RefIdDataContainer& getBooks() const; + const RefIdDataContainer& getActivators() const; + const RefIdDataContainer& getPotions() const; + const RefIdDataContainer& getApparati() const; + const RefIdDataContainer& getArmors() const; + const RefIdDataContainer& getClothing() const; + const RefIdDataContainer& getContainers() const; + const RefIdDataContainer& getCreatures() const; + const RefIdDataContainer& getDoors() const; + const RefIdDataContainer& getIngredients() const; + const RefIdDataContainer& getCreatureLevelledLists() const; + const RefIdDataContainer& getItemLevelledList() const; + const RefIdDataContainer& getLights() const; + const RefIdDataContainer& getLocpicks() const; + const RefIdDataContainer& getMiscellaneous() const; + const RefIdDataContainer& getNPCs() const; + const RefIdDataContainer& getWeapons() const; + const RefIdDataContainer& getProbes() const; + const RefIdDataContainer& getRepairs() const; + const RefIdDataContainer& getStatics() const; }; } #endif + + + + diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index c9edd0c16..e633f4f69 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -29,6 +29,10 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Referenceables", 0 }, @@ -54,6 +58,10 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./land.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 246640733..0c17da03b 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -87,6 +87,14 @@ namespace CSMWorld Type_RegionMap, Type_Filter, Type_Filters, + Type_Topics, + Type_Topic, + Type_Journals, + Type_Journal, + Type_TopicInfos, + Type_TopicInfo, + Type_JournalInfos, + Type_JournalInfo, Type_Scene }; diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 9e01f685c..09e58690f 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -11,7 +11,7 @@ #include CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) -: QWidget (parent), mValid (false) + : QWidget (parent), mValid (false), mAction (ContentAction_Undefined) { QHBoxLayout *layout = new QHBoxLayout (this); @@ -30,6 +30,11 @@ CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) setLayout (layout); } +void CSVDoc::AdjusterWidget::setAction (ContentAction action) +{ + mAction = action; +} + void CSVDoc::AdjusterWidget::setLocalData (const boost::filesystem::path& localData) { mLocalData = localData; @@ -43,41 +48,60 @@ boost::filesystem::path CSVDoc::AdjusterWidget::getPath() const return mResultPath; } +bool CSVDoc::AdjusterWidget::isValid() const +{ + return mValid; +} + +void CSVDoc::AdjusterWidget::setFilenameCheck (bool doCheck) +{ + mDoFilenameCheck = doCheck; +} + void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { QString message; - if (name.isEmpty()) + mValid = (!name.isEmpty()); + + if (!mValid) { - mValid = false; message = "No name."; } else { boost::filesystem::path path (name.toUtf8().data()); - path.replace_extension (addon ? ".omwaddon" : ".omwgame"); + bool isLegacyPath = (path.extension() == ".esm" || + path.extension() == ".esp"); - if (path.parent_path().string()==mLocalData.string()) + bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); + + if (isLegacyPath) + path.replace_extension (addon ? ".omwaddon" : ".omwgame"); + + //if the file came from data-local and is not a legacy file to be converted, + //don't worry about doing a file check. + if (!isFilePathChanged && !isLegacyPath) { // path already points to the local data directory message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); mResultPath = path; - mValid = true; } + //in all other cases, ensure the path points to data-local and do an existing file check else { // path points somewhere else or is a leaf name. - path = mLocalData / path.filename(); + if (isFilePathChanged) + path = mLocalData / path.filename(); message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); mResultPath = path; - mValid = true; if (boost::filesystem::exists (path)) { /// \todo add an user setting to make this an error. - message += "

But a file with the same name already exists. If you continue, it will be overwritten."; + message += "

A file with the same name already exists. If you continue, it will be overwritten."; } } } @@ -88,4 +112,4 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) pixmap (QSize (16, 16))); emit stateChanged (mValid); -} \ No newline at end of file +} diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index f578dc4ae..91e308236 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -9,21 +9,36 @@ class QLabel; namespace CSVDoc { + enum ContentAction + { + ContentAction_New, + ContentAction_Edit, + ContentAction_Undefined + }; + class AdjusterWidget : public QWidget { Q_OBJECT + public: + boost::filesystem::path mLocalData; QLabel *mMessage; QLabel *mIcon; bool mValid; boost::filesystem::path mResultPath; + ContentAction mAction; + bool mDoFilenameCheck; public: AdjusterWidget (QWidget *parent = 0); void setLocalData (const boost::filesystem::path& localData); + void setAction (ContentAction action); + + void setFilenameCheck (bool doCheck); + bool isValid() const; boost::filesystem::path getPath() const; ///< This function must not be called if there is no valid path. @@ -38,4 +53,4 @@ namespace CSVDoc }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index f956317a7..ab56415a1 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -9,264 +9,166 @@ #include #include #include +#include -#include -#include -#include +#include "components/contentselector/model/esmfile.hpp" +#include "components/contentselector/view/contentselector.hpp" -#include +#include "filewidget.hpp" +#include "adjusterwidget.hpp" -FileDialog::FileDialog(QWidget *parent) : - QDialog(parent) +CSVDoc::FileDialog::FileDialog(QWidget *parent) : + QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0) { - setupUi(this); + ui.setupUi (this); + resize(400, 400); - // 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; + setObjectName ("FileDialog"); + mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + mAdjusterWidget = new AdjusterWidget (this); +} - 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); +void CSVDoc::FileDialog::addFiles(const QString &path) +{ + mSelector->addFiles(path); +} - // Set the row height to the size of the checkboxes - mastersTable->verticalHeader()->setDefaultSectionSize(height); - mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mastersTable->verticalHeader()->hide(); +QStringList CSVDoc::FileDialog::selectedFilePaths() +{ + QStringList filePaths; - 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); + foreach (ContentSelectorModel::EsmFile *file, mSelector->selectedFiles() ) + filePaths.append(file->filePath()); - pluginsTable->verticalHeader()->setDefaultSectionSize(height); - pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + return filePaths; +} - // Hide the profile elements - profileLabel->hide(); - profilesComboBox->hide(); - newProfileButton->hide(); - deleteProfileButton->hide(); +void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) +{ + mAdjusterWidget->setLocalData (localData); +} - // Add some extra widgets - QHBoxLayout *nameLayout = new QHBoxLayout(); - QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); +void CSVDoc::FileDialog::showDialog (ContentAction action) +{ + mAction = action; - mNameLabel = new QLabel(tr("File Name:"), this); + ui.projectGroupBoxLayout->insertWidget (0, mAdjusterWidget); - QRegExpValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$")); - mNameLineEdit = new LineEdit(this); - mNameLineEdit->setValidator(validator); + switch (mAction) + { + case ContentAction_New: + buildNewFileView(); + break; - nameLayout->addSpacerItem(spacer); - nameLayout->addWidget(mNameLabel); - nameLayout->addWidget(mNameLineEdit); + case ContentAction_Edit: + buildOpenFileView(); + break; - mButtonBox = new QDialogButtonBox(this); + default: + break; + } - mCreateButton = new QPushButton(tr("Create"), this); - mCreateButton->setEnabled(false); + mAdjusterWidget->setFilenameCheck (mAction == ContentAction_New); - verticalLayout->addLayout(nameLayout); - verticalLayout->addWidget(mButtonBox); + //connections common to both dialog view flavors + connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), + this, SLOT (slotUpdateAcceptButton (int))); - // Set sizes - QList sizeList; - sizeList << 175; - sizeList << 200; + connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); - splitter->setSizes(sizeList); + show(); + raise(); + activateWindow(); +} - resize(600, 400); +void CSVDoc::FileDialog::buildNewFileView() +{ + setWindowTitle(tr("Create a new addon")); - 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))); + QPushButton* createButton = ui.projectButtonBox->button (QDialogButtonBox::Ok); + createButton->setText ("Create"); + createButton->setEnabled (false); - connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + mFileWidget = new FileWidget (this); - connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + mFileWidget->setType (true); + mFileWidget->extensionLabelIsVisible(true); - connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); + ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); - connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); -} + connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), + mAdjusterWidget, SLOT (setName (const QString&, bool))); -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(); + connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), + this, SLOT (slotUpdateAcceptButton(const QString &, bool))); + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); } -void FileDialog::updateOpenButton(const QStringList &items) +void CSVDoc::FileDialog::buildOpenFileView() { - QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open); - - if (!openButton) - return; + setWindowTitle(tr("Open")); + ui.projectGroupBox->setTitle (QString("")); - openButton->setEnabled(!items.isEmpty()); -} + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); -void FileDialog::updateCreateButton(const QString &name) -{ - if (!mCreateButton->isVisible()) - return; + connect (mSelector, SIGNAL (signalAddonFileSelected (int)), this, SLOT (slotUpdateAcceptButton (int))); + connect (mSelector, SIGNAL (signalAddonFileUnselected (int)), this, SLOT (slotUpdateAcceptButton (int))); - mCreateButton->setEnabled(!name.isEmpty()); + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); } -void FileDialog::filterChanged(const QString &filter) +void CSVDoc::FileDialog::slotUpdateAcceptButton (int) { - QRegExp filterRe(filter, Qt::CaseInsensitive, QRegExp::FixedString); - mFilterProxyModel->setFilterRegExp(filterRe); -} + QString name = ""; -void FileDialog::addFiles(const QString &path) -{ - mDataFilesModel->addFiles(path); - mDataFilesModel->sort(3); // Sort by date accessed -} + if (mAction == ContentAction_New) + name = mFileWidget->getName(); -void FileDialog::setEncoding(const QString &encoding) -{ - mDataFilesModel->setEncoding(encoding); + slotUpdateAcceptButton (name, true); } -void FileDialog::setCheckState(QModelIndex index) +void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) { - if (!index.isValid()) - return; - - QObject *object = QObject::sender(); - - // Not a signal-slot call - if (!object) - return; + bool success = (mSelector->selectedFiles().size() > 0); + bool isNew = (mAction == ContentAction_New); - 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 (isNew) + success = success && !(name.isEmpty()); + else + { + ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); + mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); } - 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; + ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); } -QStringList FileDialog::checkedItemsPaths() +QString CSVDoc::FileDialog::filename() const { - return mDataFilesModel->checkedItemsPaths(); -} + if (mAction == ContentAction_New) + return ""; -QString FileDialog::fileName() -{ - return mNameLineEdit->text(); + return mSelector->currentFile(); } -void FileDialog::openFile() +void CSVDoc::FileDialog::slotRejected() { - 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(); + emit rejected(); + close(); } -void FileDialog::newFile() +void CSVDoc::FileDialog::slotNewFile() { - setWindowTitle(tr("New")); - - mNameLabel->show(); - mNameLineEdit->clear(); - mNameLineEdit->show(); - mCreateButton->show(); - - mButtonBox->setStandardButtons(QDialogButtonBox::Cancel); - mButtonBox->addButton(mCreateButton, QDialogButtonBox::ActionRole); - - show(); - raise(); - activateWindow(); + emit signalCreateNewFile (mAdjusterWidget->getPath()); } -void FileDialog::accept() +void CSVDoc::FileDialog::slotOpenFile() { - emit openFiles(); -} + ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); -void FileDialog::createButtonClicked() -{ - emit createNewFile(); + mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); + + emit signalOpenFiles (mAdjusterWidget->getPath()); } diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index b21618d5d..d9fd56943 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -4,63 +4,71 @@ #include #include -#include "ui_datafilespage.h" +#include +#include "adjusterwidget.hpp" -class QDialogButtonBox; -class QSortFilterProxyModel; -class QAbstractItemModel; -class QPushButton; -class QStringList; -class QString; -class QMenu; +#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +Q_DECLARE_METATYPE (boost::filesystem::path) +#endif + +#include "ui_filedialog.h" class DataFilesModel; class PluginsProxyModel; -class FileDialog : public QDialog, private Ui::DataFilesPage +namespace ContentSelectorView +{ + class ContentSelector; +} + +namespace CSVDoc { - Q_OBJECT -public: - explicit FileDialog(QWidget *parent = 0); - void addFiles(const QString &path); - void setEncoding(const QString &encoding); + class FileWidget; + + class FileDialog : public QDialog + { + Q_OBJECT + + private: + + ContentSelectorView::ContentSelector *mSelector; + Ui::FileDialog ui; + ContentAction mAction; + FileWidget *mFileWidget; + AdjusterWidget *mAdjusterWidget; + + public: - void openFile(); - void newFile(); - void accepted(); + explicit FileDialog(QWidget *parent = 0); + void showDialog (ContentAction action); - QStringList checkedItemsPaths(); - QString fileName(); + void addFiles (const QString &path); -signals: - void openFiles(); - void createNewFile(); - -public slots: - void accept(); + QString filename() const; + QStringList selectedFilePaths(); -private slots: - void updateViews(); - void updateOpenButton(const QStringList &items); - void updateCreateButton(const QString &name); - void setCheckState(QModelIndex index); + void setLocalData (const boost::filesystem::path& localData); - void filterChanged(const QString &filter); + private: - void createButtonClicked(); + void buildNewFileView(); + void buildOpenFileView(); -private: - QLabel *mNameLabel; - LineEdit *mNameLineEdit; + signals: - QPushButton *mCreateButton; - QDialogButtonBox *mButtonBox; + void signalOpenFiles (const boost::filesystem::path &path); + void signalCreateNewFile (const boost::filesystem::path &path); - DataFilesModel *mDataFilesModel; + void signalUpdateAcceptButton (bool, int); - PluginsProxyModel *mPluginsProxyModel; - QSortFilterProxyModel *mMastersProxyModel; - QSortFilterProxyModel *mFilterProxyModel; -}; + private slots: + void slotNewFile(); + void slotOpenFile(); + void slotUpdateAcceptButton (int); + void slotUpdateAcceptButton (const QString &, bool); + void slotRejected(); + }; +} #endif // FILEDIALOG_HPP diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index c8f33e92d..9cd2fad42 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -50,4 +50,9 @@ QString CSVDoc::FileWidget::getName() const void CSVDoc::FileWidget::textChanged (const QString& text) { emit nameChanged (getName(), mAddon); -} \ No newline at end of file +} + +void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) +{ + mType->setVisible(visible); +} diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index c51c29632..ff09d71a3 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -27,6 +27,8 @@ namespace CSVDoc QString getName() const; + void extensionLabelIsVisible(bool visible); + private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/doc/newgame.hpp b/apps/opencs/view/doc/newgame.hpp index aa97682ff..9ad7ea169 100644 --- a/apps/opencs/view/doc/newgame.hpp +++ b/apps/opencs/view/doc/newgame.hpp @@ -6,7 +6,10 @@ #include #include +#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED Q_DECLARE_METATYPE (boost::filesystem::path) +#endif class QPushButton; diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 4cc64f2df..5d59492c6 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -104,6 +104,17 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) layout->addWidget (createButtons()); layout->addWidget (createTools()); + /// \todo remove this label once loading and saving are fully implemented + QLabel *warning = new QLabel ("WARNING:

OpenCS is in alpha stage.
The code for loading and saving is incomplete.
This version of OpenCS is only a preview.
Do NOT use it for real editing!
You will lose records both on loading and on saving.

Please note:
If you lose data and come to the OpenMW forum to complain,
we will mock you.
"); + + QFont font; + font.setPointSize (12); + font.setBold (true); + + warning->setFont (font); + + layout->addWidget (warning, 1); + setLayout (layout); QRect scr = QApplication::desktop()->screenGeometry(); diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp index 2c4158f85..059b24fd0 100644 --- a/apps/opencs/view/doc/subviewfactoryimp.hpp +++ b/apps/opencs/view/doc/subviewfactoryimp.hpp @@ -26,16 +26,25 @@ namespace CSVDoc template class SubViewFactoryWithCreator : public SubViewFactoryBase { + bool mSorting; + public: + SubViewFactoryWithCreator (bool sorting = true); + virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); }; + template + SubViewFactoryWithCreator::SubViewFactoryWithCreator (bool sorting) + : mSorting (sorting) + {} + template CSVDoc::SubView *SubViewFactoryWithCreator::makeSubView ( const CSMWorld::UniversalId& id, CSMDoc::Document& document) { - return new SubViewT (id, document, CreatorFactoryT()); + return new SubViewT (id, document, CreatorFactoryT(), mSorting); } } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index b29250d20..533ca7f57 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -136,33 +136,54 @@ void CSVDoc::View::setupMechanicsMenu() connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); mechanics->addAction (gmsts); + QAction *scripts = new QAction (tr ("Scripts"), this); + connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + mechanics->addAction (scripts); + + QAction *spells = new QAction (tr ("Spells"), this); + connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); + mechanics->addAction (spells); +} + +void CSVDoc::View::setupCharacterMenu() +{ + QMenu *characters = menuBar()->addMenu (tr ("Characters")); + QAction *skills = new QAction (tr ("Skills"), this); connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); - mechanics->addAction (skills); + characters->addAction (skills); QAction *classes = new QAction (tr ("Classes"), this); connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); - mechanics->addAction (classes); + characters->addAction (classes); QAction *factions = new QAction (tr ("Factions"), this); connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); - mechanics->addAction (factions); + characters->addAction (factions); QAction *races = new QAction (tr ("Races"), this); connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); - mechanics->addAction (races); - - QAction *scripts = new QAction (tr ("Scripts"), this); - connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); - mechanics->addAction (scripts); + characters->addAction (races); QAction *birthsigns = new QAction (tr ("Birthsigns"), this); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); - mechanics->addAction (birthsigns); + characters->addAction (birthsigns); - QAction *spells = new QAction (tr ("Spells"), this); - connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); - mechanics->addAction (spells); + QAction *topics = new QAction (tr ("Topics"), this); + connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); + characters->addAction (topics); + + QAction *journals = new QAction (tr ("Journals"), this); + connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); + characters->addAction (journals); + + QAction *topicInfos = new QAction (tr ("Topic Infos"), this); + connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); + characters->addAction (topicInfos); + + QAction *journalInfos = new QAction (tr ("Journal Infos"), this); + connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); + characters->addAction (journalInfos); } void CSVDoc::View::setupAssetsMenu() @@ -181,6 +202,7 @@ void CSVDoc::View::setupUi() setupViewMenu(); setupWorldMenu(); setupMechanicsMenu(); + setupCharacterMenu(); setupAssetsMenu(); } @@ -412,6 +434,26 @@ void CSVDoc::View::addSceneSubView() addSubView (CSMWorld::UniversalId::Type_Scene); } +void CSVDoc::View::addTopicsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Topics); +} + +void CSVDoc::View::addJournalsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Journals); +} + +void CSVDoc::View::addTopicInfosSubView() +{ + addSubView (CSMWorld::UniversalId::Type_TopicInfos); +} + +void CSVDoc::View::addJournalInfosSubView() +{ + addSubView (CSMWorld::UniversalId::Type_JournalInfos); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 6f3c38daa..13c15ec9b 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -63,6 +63,8 @@ namespace CSVDoc void setupMechanicsMenu(); + void setupCharacterMenu(); + void setupAssetsMenu(); void setupUi(); @@ -166,6 +168,14 @@ namespace CSVDoc void addSceneSubView(); + void addTopicsSubView(); + + void addJournalsSubView(); + + void addTopicInfosSubView(); + + void addJournalInfosSubView(); + void toggleShowStatusBar (bool show); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 83cd93e5d..4a4dbc124 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -74,7 +74,10 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_ArmorType, CSMWorld::Columns::ColumnId_ArmorType, false }, { CSMWorld::ColumnBase::Display_ClothingType, CSMWorld::Columns::ColumnId_ClothingType, false }, { CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false }, - { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false } + { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false }, + { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false }, + { CSMWorld::ColumnBase::Display_QuestStatusType, CSMWorld::Columns::ColumnId_QuestStatusType, false }, + { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true } }; for (std::size_t i=0; icurrentIndex()) @@ -28,6 +33,15 @@ std::string CSVFilter::FilterCreator::getId() const return getNamespace() + GenericCreator::getId(); } +void CSVFilter::FilterCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + int index = + dynamic_cast (*getData().getTableModel (getCollectionId())). + findColumnIndex (CSMWorld::Columns::ColumnId_Scope); + + command.addValue (index, mScope->currentIndex()); +} + CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) @@ -39,7 +53,7 @@ CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoS mScope->addItem ("Project"); mScope->addItem ("Session"); - /// \ŧodo re-enable for OpenMW 1.1 + /// \todo re-enable for OpenMW 1.1 // mScope->addItem ("Content"); connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (setScope (int))); diff --git a/apps/opencs/view/filter/filtercreator.hpp b/apps/opencs/view/filter/filtercreator.hpp index 82d38d22c..437d01c8d 100644 --- a/apps/opencs/view/filter/filtercreator.hpp +++ b/apps/opencs/view/filter/filtercreator.hpp @@ -25,6 +25,8 @@ namespace CSVFilter virtual std::string getId() const; + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + public: FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp new file mode 100644 index 000000000..620586bd2 --- /dev/null +++ b/apps/opencs/view/render/scenewidget.cpp @@ -0,0 +1,129 @@ +#include "scenewidget.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace CSVRender +{ + + SceneWidget::SceneWidget(QWidget *parent) + : QWidget(parent) + , mWindow(NULL) + , mCamera(NULL) + , mSceneMgr(NULL) + { + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_NoSystemBackground); + + mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + // Throw in a random color just to make sure multiple scenes work + Ogre::Real r = Ogre::Math::RangeRandom(0, 1); + Ogre::Real g = Ogre::Math::RangeRandom(0, 1); + Ogre::Real b = Ogre::Math::RangeRandom(0, 1); + mSceneMgr->setAmbientLight(Ogre::ColourValue(r,g,b,1)); + + Ogre::Light* l = mSceneMgr->createLight(); + l->setType (Ogre::Light::LT_DIRECTIONAL); + l->setDirection (Ogre::Vector3(-0.4, -0.7, 0.3)); + l->setDiffuseColour (Ogre::ColourValue(0.7,0.7,0.7)); + + mCamera = mSceneMgr->createCamera("foo"); + + Ogre::Entity* ent = mSceneMgr->createEntity("cube", Ogre::SceneManager::PT_CUBE); + ent->setMaterialName("BaseWhite"); + + mSceneMgr->getRootSceneNode()->attachObject(ent); + + mCamera->setPosition(300,300,300); + mCamera->lookAt(0,0,0); + mCamera->setNearClipDistance(0.1); + mCamera->setFarClipDistance(3000); + } + + void SceneWidget::updateOgreWindow() + { + if (mWindow) + { + Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + mWindow = NULL; + } + + std::stringstream windowHandle; + windowHandle << this->winId(); + + std::stringstream windowTitle; + static int count=0; + windowTitle << ++count; + + Ogre::NameValuePairList params; + + params.insert(std::make_pair("externalWindowHandle", windowHandle.str())); + params.insert(std::make_pair("title", windowTitle.str())); + params.insert(std::make_pair("FSAA", "0")); // TODO setting + params.insert(std::make_pair("vsync", "false")); // TODO setting + + mWindow = Ogre::Root::getSingleton().createRenderWindow(windowTitle.str(), this->width(), this->height(), false, ¶ms); + mWindow->addViewport(mCamera)->setBackgroundColour(Ogre::ColourValue(0.3,0.3,0.3,1)); + + Ogre::Real aspectRatio = Ogre::Real(width()) / Ogre::Real(height()); + mCamera->setAspectRatio(aspectRatio); + } + + SceneWidget::~SceneWidget() + { + Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + } + + void SceneWidget::paintEvent(QPaintEvent* e) + { + if (!mWindow) + updateOgreWindow(); + + mWindow->update(); + e->accept(); + } + + + QPaintEngine* SceneWidget::paintEngine() const + { + // We don't want another paint engine to get in the way. + // So we return nothing. + return NULL; + } + + void SceneWidget::resizeEvent(QResizeEvent *e) + { + if (!mWindow) + return; + + const QSize &newSize = e->size(); + + // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + mWindow->resize(newSize.width(), newSize.height()); +#else + mWindow->windowMovedOrResized(); +#endif + + Ogre::Real aspectRatio = Ogre::Real(newSize.width()) / Ogre::Real(newSize.height()); + mCamera->setAspectRatio(aspectRatio); + } + + bool SceneWidget::event(QEvent *e) + { + if (e->type() == QEvent::WinIdChange) + { + // I haven't actually seen this happen yet. + if (mWindow) + updateOgreWindow(); + } + return QWidget::event(e); + } + +} diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp new file mode 100644 index 000000000..355a6331e --- /dev/null +++ b/apps/opencs/view/render/scenewidget.hpp @@ -0,0 +1,40 @@ +#ifndef OPENCS_VIEW_SCENEWIDGET_H +#define OPENCS_VIEW_SCENEWIDGET_H + +#include + +namespace Ogre +{ + class Camera; + class SceneManager; + class RenderWindow; +} + +namespace CSVRender +{ + + class SceneWidget : public QWidget + { + Q_OBJECT + + public: + SceneWidget(QWidget *parent); + virtual ~SceneWidget(void); + + QPaintEngine* paintEngine() const; + + private: + void paintEvent(QPaintEvent* e); + void resizeEvent(QResizeEvent* e); + bool event(QEvent* e); + + void updateOgreWindow(); + + Ogre::Camera* mCamera; + Ogre::SceneManager* mSceneMgr; + Ogre::RenderWindow* mWindow; + }; + +} + +#endif diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 74fb06870..cdeee5655 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -61,6 +61,7 @@ void CSVWorld::CellCreator::reset() mX->setValue (0); mY->setValue (0); mType->setCurrentIndex (0); + setType(0); GenericCreator::reset(); } @@ -78,4 +79,25 @@ void CSVWorld::CellCreator::setType (int index) void CSVWorld::CellCreator::valueChanged (int index) { update(); -} \ No newline at end of file +} + +void CSVWorld::CellCreator::cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSVWorld::GenericCreator::cloneMode(originId, type); + if (*(originId.begin()) == '#') //if originid points to the exterior cell + { + setType(1); //enable x and y controls + mType->setCurrentIndex(1); + } else { + setType(0); + mType->setCurrentIndex(0); + } +} + + +void CSVWorld::CellCreator::toggleWidgets(bool active) +{ + CSVWorld::GenericCreator::toggleWidgets(active); + mType->setEnabled(active); +} diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index a5473e2c9..db9fbf8a3 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -29,6 +29,11 @@ namespace CSVWorld virtual void reset(); + virtual void toggleWidgets(bool active = true); + + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type); + private slots: void setType (int index); diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp index df9b116ee..88da70330 100644 --- a/apps/opencs/view/world/creator.hpp +++ b/apps/opencs/view/world/creator.hpp @@ -2,6 +2,7 @@ #define CSV_WORLD_CREATOR_H #include +#include "../../model/world/universalid.hpp" class QUndoStack; @@ -24,8 +25,13 @@ namespace CSVWorld virtual void reset() = 0; + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) = 0; + virtual void setEditLock (bool locked) = 0; + virtual void toggleWidgets(bool active = true) = 0; + signals: void done(); diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp new file mode 100644 index 000000000..3523d5e32 --- /dev/null +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -0,0 +1,35 @@ + +#include "dialoguecreator.hpp" + +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + +void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + int index = + dynamic_cast (*getData().getTableModel (getCollectionId())). + findColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); + + command.addValue (index, mType); +} + +CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, int type) +: GenericCreator (data, undoStack, id, true), mType (type) +{} + +CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMWorld::Data& data, + QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +{ + return new DialogueCreator (data, undoStack, id, ESM::Dialogue::Topic); +} + +CSVWorld::Creator *CSVWorld::JournalCreatorFactory::makeCreator (CSMWorld::Data& data, + QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +{ + return new DialogueCreator (data, undoStack, id, ESM::Dialogue::Journal); +} \ No newline at end of file diff --git a/apps/opencs/view/world/dialoguecreator.hpp b/apps/opencs/view/world/dialoguecreator.hpp new file mode 100644 index 000000000..26f866909 --- /dev/null +++ b/apps/opencs/view/world/dialoguecreator.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_WORLD_DIALOGUECREATOR_H +#define CSV_WORLD_DIALOGUECREATOR_H + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + class DialogueCreator : public GenericCreator + { + int mType; + + protected: + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + public: + + DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, int type); + }; + + class TopicCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + }; + + class JournalCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + }; +} + +#endif diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index df43d6c5f..cd7a5fa18 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -57,14 +57,21 @@ const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const } CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) -: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false) + const CSMWorld::UniversalId& id, bool relaxedIdRules): + + mData (data), + mUndoStack (undoStack), + mListId (id), + mLocked (false), + mCloneMode(false), + mClonedType(CSMWorld::UniversalId::Type_None) + { mLayout = new QHBoxLayout; mLayout->setContentsMargins (0, 0, 0, 0); mId = new QLineEdit; - mId->setValidator (new IdValidator (this)); + mId->setValidator (new IdValidator (relaxedIdRules, this)); mLayout->addWidget (mId, 1); mCreate = new QPushButton ("Create"); @@ -89,6 +96,7 @@ void CSVWorld::GenericCreator::setEditLock (bool locked) void CSVWorld::GenericCreator::reset() { + mCloneMode = false; mId->setText (""); update(); } @@ -120,16 +128,40 @@ void CSVWorld::GenericCreator::create() { if (!mLocked) { - std::string id = getId(); - - std::auto_ptr command (new CSMWorld::CreateCommand ( + if (mCloneMode) + { + std::string id = getId(); + std::auto_ptr command (new CSMWorld::CloneCommand ( + dynamic_cast (*mData.getTableModel(mListId)), mClonedId, id, mClonedType)); + + mUndoStack.push(command.release()); + + emit done(); + emit requestFocus(id); + } else { + std::string id = getId(); + + std::auto_ptr command (new CSMWorld::CreateCommand ( dynamic_cast (*mData.getTableModel (mListId)), id)); - configureCreateCommand (*command); + configureCreateCommand (*command); - mUndoStack.push (command.release()); + mUndoStack.push (command.release()); - emit done(); - emit requestFocus (id); + emit done(); + emit requestFocus (id); + } } -} \ No newline at end of file +} + +void CSVWorld::GenericCreator::cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + mCloneMode = true; + mClonedId = originId; + mClonedType = type; +} + +void CSVWorld::GenericCreator::toggleWidgets(bool active) +{ +} diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 6752d8591..714853f98 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -1,6 +1,7 @@ #ifndef CSV_WORLD_GENERICCREATOR_H #define CSV_WORLD_GENERICCREATOR_H +class QString; class QPushButton; class QLineEdit; class QHBoxLayout; @@ -28,6 +29,11 @@ namespace CSVWorld std::string mErrors; QHBoxLayout *mLayout; bool mLocked; + std::string mClonedId; + CSMWorld::UniversalId::Type mClonedType; + + protected: + bool mCloneMode; protected: @@ -51,17 +57,21 @@ namespace CSVWorld public: GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id); + const CSMWorld::UniversalId& id, bool relaxedIdRules = false); virtual void setEditLock (bool locked); virtual void reset(); + virtual void toggleWidgets (bool active = true); + + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type); + virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. - private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index cf6e5d77b..7c210daae 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -12,15 +12,25 @@ bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const return false; } -CSVWorld::IdValidator::IdValidator (QObject *parent) : QValidator (parent) {} +CSVWorld::IdValidator::IdValidator (bool relaxed, QObject *parent) +: QValidator (parent), mRelaxed (relaxed) +{} QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const { - bool first = true; - - for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false) - if (!isValid (*iter, first)) + if (mRelaxed) + { + if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1 || input.indexOf ("#")!=-1) return QValidator::Invalid; + } + else + { + bool first = true; + + for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false) + if (!isValid (*iter, first)) + return QValidator::Invalid; + } return QValidator::Acceptable; } \ No newline at end of file diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index db0ecb27a..8ca162440 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -7,13 +7,16 @@ namespace CSVWorld { class IdValidator : public QValidator { + bool mRelaxed; + private: bool isValid (const QChar& c, bool first) const; public: - IdValidator (QObject *parent = 0); + IdValidator (bool relaxed = false, QObject *parent = 0); + ///< \param relaxed Relaxed rules for IDs that also functino as user visible text virtual State validate (QString& input, int& pos) const; diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp new file mode 100644 index 000000000..f09222930 --- /dev/null +++ b/apps/opencs/view/world/infocreator.cpp @@ -0,0 +1,81 @@ + +#include "infocreator.hpp" + +#include + +#include +#include +#include + +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + +std::string CSVWorld::InfoCreator::getId() const +{ + std::string id = Misc::StringUtils::lowerCase (mTopic->text().toUtf8().constData()); + + std::string unique = QUuid::createUuid().toByteArray().data(); + + unique.erase (std::remove (unique.begin(), unique.end(), '-'), unique.end()); + + unique = unique.substr (1, unique.size()-2); + + return id + '#' + unique; +} + +void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + int index = + dynamic_cast (*getData().getTableModel (getCollectionId())). + findColumnIndex ( + getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? + CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); + + command.addValue (index, mTopic->text()); +} + +CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: GenericCreator (data, undoStack, id) +{ + QLabel *label = new QLabel ("Topic", this); + insertBeforeButtons (label, false); + + mTopic = new QLineEdit (this); + insertBeforeButtons (mTopic, true); + + setManualEditing (false); + + connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); +} + +void CSVWorld::InfoCreator::reset() +{ + mTopic->setText (""); + GenericCreator::reset(); +} + +std::string CSVWorld::InfoCreator::getErrors() const +{ + // We ignore errors from GenericCreator here, because they can never happen in an InfoCreator. + std::string errors; + + std::string topic = mTopic->text().toUtf8().constData(); + + if ((getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? + getData().getTopics() : getData().getJournals()).searchId (topic)==-1) + { + errors += "Invalid Topic ID"; + } + + return errors; +} + +void CSVWorld::InfoCreator::topicChanged() +{ + update(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp new file mode 100644 index 000000000..e9cb7e596 --- /dev/null +++ b/apps/opencs/view/world/infocreator.hpp @@ -0,0 +1,42 @@ +#ifndef CSV_WORLD_INFOCREATOR_H +#define CSV_WORLD_INFOCREATOR_H + +#include "genericcreator.hpp" + +class QLineEdit; + +namespace CSMWorld +{ + class InfoCollection; +} + +namespace CSVWorld +{ + class InfoCreator : public GenericCreator + { + Q_OBJECT + + QLineEdit *mTopic; + + virtual std::string getId() const; + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + public: + + InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id); + + virtual void reset(); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + + private slots: + + void topicChanged(); + }; +} + +#endif diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 718fe9ca7..7a5fca853 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -40,4 +40,10 @@ void CSVWorld::ReferenceableCreator::reset() { mType->setCurrentIndex (0); GenericCreator::reset(); -} \ No newline at end of file +} + +void CSVWorld::ReferenceableCreator::toggleWidgets(bool active) +{ + CSVWorld::GenericCreator::toggleWidgets(active); + mType->setEnabled(active); +} diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp index 06e0e582b..88545575e 100644 --- a/apps/opencs/view/world/referenceablecreator.hpp +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -23,6 +23,7 @@ namespace CSVWorld const CSMWorld::UniversalId& id); virtual void reset(); + virtual void toggleWidgets(bool active = true); }; } diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index aef25a81d..6b8e02da0 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -40,15 +40,20 @@ CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& void CSVWorld::ReferenceCreator::reset() { + GenericCreator::reset(); mCell->setText (""); mId = getData().getReferences().getNewId(); - GenericCreator::reset(); } std::string CSVWorld::ReferenceCreator::getErrors() const { std::string errors = GenericCreator::getErrors(); + if (mCloneMode) + { + return errors; + } + std::string cell = mCell->text().toUtf8().constData(); if (cell.empty()) @@ -72,4 +77,17 @@ std::string CSVWorld::ReferenceCreator::getErrors() const void CSVWorld::ReferenceCreator::cellChanged() { update(); -} \ No newline at end of file +} + +void CSVWorld::ReferenceCreator::toggleWidgets(bool active) +{ + CSVWorld::GenericCreator::toggleWidgets(active); + mCell->setEnabled(active); +} + +void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSVWorld::GenericCreator::cloneMode(originId, type); + cellChanged(); //otherwise ok button will remain disabled +} diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index 27f81564f..12fb12dd9 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -25,7 +25,11 @@ namespace CSVWorld ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type); + virtual void reset(); + virtual void toggleWidgets(bool active = true); virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index e3618c549..83b30be13 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,6 +9,8 @@ #include "../filter/filterbox.hpp" +#include "../render/scenewidget.hpp" + #include "tablebottombox.hpp" #include "creator.hpp" #include "scenetoolbar.hpp" @@ -41,15 +43,10 @@ toolbar->addTool (new SceneToolMode (toolbar)); toolbar->addTool (new SceneToolMode (toolbar)); layout2->addWidget (toolbar, 0); - /// \todo replace with rendering widget - QPalette palette2 (palette()); - palette2.setColor (QPalette::Background, Qt::white); - QLabel *placeholder = new QLabel ("Here goes the 3D scene", this); - placeholder->setAutoFillBackground (true); - placeholder->setPalette (palette2); - placeholder->setAlignment (Qt::AlignHCenter); - layout2->addWidget (placeholder, 1); + CSVRender::SceneWidget* sceneWidget = new CSVRender::SceneWidget(this); + + layout2->addWidget (sceneWidget, 1); layout->insertLayout (0, layout2, 1); @@ -79,4 +76,4 @@ void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, con void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 0e3465b38..74ce03cce 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -14,6 +14,8 @@ #include "referenceablecreator.hpp" #include "referencecreator.hpp" #include "scenesubview.hpp" +#include "dialoguecreator.hpp" +#include "infocreator.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { @@ -53,6 +55,18 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_References, new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Topics, + new CSVDoc::SubViewFactoryWithCreator); + + manager.add (CSMWorld::UniversalId::Type_Journals, + new CSVDoc::SubViewFactoryWithCreator); + + manager.add (CSMWorld::UniversalId::Type_TopicInfos, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_JournalInfos, + new CSVDoc::SubViewFactoryWithCreator > (false)); + // Subviews for editing/viewing individual records manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 6167c084a..8f6fc46a8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -12,6 +12,7 @@ #include "../../model/world/idtableproxymodel.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/record.hpp" +#include "../../model/world/columns.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" @@ -27,16 +28,52 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (!mEditLock) { if (selectedRows.size()==1) + { menu.addAction (mEditAction); + if (mCreateAction) + menu.addAction(mCloneAction); + } if (mCreateAction) menu.addAction (mCreateAction); - if (listRevertableSelectedIds().size()>0) - menu.addAction (mRevertAction); + /// \todo Reverting temporarily disabled on tables that support reordering, because + /// revert logic currently can not handle reordering. + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) + if (listRevertableSelectedIds().size()>0) + menu.addAction (mRevertAction); if (listDeletableSelectedIds().size()>0) menu.addAction (mDeleteAction); + + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) + { + /// \todo allow reordering of multiple rows + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); + + if (column==-1) + column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); + + if (column!=-1) + { + if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row-1, column))) + { + menu.addAction (mMoveUpAction); + } + + if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row+1, column))) + { + menu.addAction (mMoveDownAction); + } + } + } + } } menu.exec (event->globalPos()); @@ -87,19 +124,33 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const { QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); + // check record state CSMWorld::RecordBase::State state = static_cast ( mModel->data (mModel->index (index.row(), 1)).toInt()); - if (state!=CSMWorld::RecordBase::State_Deleted) - { - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + if (state==CSMWorld::RecordBase::State_Deleted) + continue; - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); + // check other columns (only relevant for a subset of the tables) + int dialogueTypeIndex = + mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); - deletableIds.push_back (id); + if (dialogueTypeIndex!=-1) + { + int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); + + if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + continue; } + + // add the id to the collection + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + std::string id = mModel->data (mModel->index (index.row(), columnIndex)). + toString().toUtf8().constData(); + + deletableIds.push_back (id); } } @@ -107,8 +158,8 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const } CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, - bool createAndDelete) - : mUndoStack (undoStack), mCreateAction (0), mEditLock (false), mRecordStatusDisplay (0) + bool createAndDelete, bool sorting) + : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0) { mModel = &dynamic_cast (*data.getTableModel (id)); @@ -118,7 +169,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q setModel (mProxyModel); horizontalHeader()->setResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); - setSortingEnabled (true); + setSortingEnabled (sorting); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); @@ -152,6 +203,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q mCreateAction = new QAction (tr ("Add Record"), this); connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); addAction (mCreateAction); + + mCloneAction = new QAction (tr ("Clone Record"), this); + connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); + addAction(mCloneAction); } mRevertAction = new QAction (tr ("Revert Record"), this); @@ -162,6 +217,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); addAction (mDeleteAction); + mMoveUpAction = new QAction (tr ("Move Up"), this); + connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); + addAction (mMoveUpAction); + + mMoveDownAction = new QAction (tr ("Move Down"), this); + connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); + addAction (mMoveDownAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -240,6 +303,77 @@ void CSVWorld::Table::editRecord() } } +void CSVWorld::Table::cloneRecord() +{ + if (!mEditLock) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); + if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted()) + { + emit cloneRequest (toClone); + } + } +} + +void CSVWorld::Table::moveUpRecord() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + { + int row2 =selectedRows.begin()->row(); + + if (row2>0) + { + int row = row2-1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; iselectedRows(); + + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + if (rowrowCount()-1) + { + int row2 = row+1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; icolumnCount(); @@ -297,4 +431,4 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { mProxyModel->setFilter (filter); -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index d93109056..d30083333 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -32,8 +32,11 @@ namespace CSVWorld QUndoStack& mUndoStack; QAction *mEditAction; QAction *mCreateAction; + QAction *mCloneAction; QAction *mRevertAction; QAction *mDeleteAction; + QAction *mMoveUpAction; + QAction *mMoveDownAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; bool mEditLock; @@ -49,8 +52,9 @@ namespace CSVWorld public: - Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete); + Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, bool sorting); ///< \param createAndDelete Allow creation and deletion of records. + /// \param sorting Allow changing order of rows in the view via column headers. void setEditLock (bool locked); @@ -70,6 +74,7 @@ namespace CSVWorld /// \param modified Number of added and modified records void createRequest(); + void cloneRequest(const CSMWorld::UniversalId&); private slots: @@ -79,6 +84,12 @@ namespace CSVWorld void editRecord(); + void cloneRecord(); + + void moveUpRecord(); + + void moveDownRecord(); + public slots: void tableSizeUpdate(); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 3edf9af31..239c7410f 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -153,7 +153,19 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); + mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); mCreating = true; -} \ No newline at end of file +} + +void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type) +{ + mCreator->reset(); + mCreator->cloneMode(id, type); + mLayout->setCurrentWidget(mCreator); + mCreator->toggleWidgets(false); + setVisible (true); + mCreating = true; +} diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index a5ae5e0bd..8f0d85163 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -2,6 +2,7 @@ #define CSV_WORLD_BOTTOMBOX_H #include +#include class QLabel; class QStackedLayout; @@ -76,6 +77,8 @@ namespace CSVWorld /// \param modified Number of added and modified records void createRequest(); + void cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 1e05fbf51..981faaf59 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -6,13 +6,12 @@ #include "../../model/doc/document.hpp" #include "../filter/filterbox.hpp" - #include "table.hpp" #include "tablebottombox.hpp" #include "creator.hpp" CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory) + const CreatorFactoryBase& creatorFactory, bool sorting) : SubView (id) { QVBoxLayout *layout = new QVBoxLayout; @@ -23,7 +22,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); layout->insertWidget (0, mTable = - new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2); + new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting), 2); CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); @@ -46,8 +45,15 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D mTable->selectionSizeUpdate(); if (mBottom->canCreateAndDelete()) + { connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); + connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, + SLOT(cloneRequest(const CSMWorld::UniversalId&))); + + connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), + mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); @@ -75,4 +81,9 @@ void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, con void CSVWorld::TableSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); -} \ No newline at end of file +} + +void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) +{ + emit cloneRequest(toClone.getId(), toClone.getType()); +} diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index d61c78935..d728dc2f3 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -5,6 +5,11 @@ class QModelIndex; +namespace CSMWorld +{ + class IdTable; +} + namespace CSMDoc { class Document; @@ -26,7 +31,7 @@ namespace CSVWorld public: TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory); + const CreatorFactoryBase& creatorFactory, bool sorting); virtual void setEditLock (bool locked); @@ -34,9 +39,14 @@ namespace CSVWorld virtual void setStatusBar (bool show); + signals: + void cloneRequest(const std::string&, + const CSMWorld::UniversalId::Type); + private slots: void editRequest (int row); + void cloneRequest (const CSMWorld::UniversalId& toClone); }; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b367e2a1e..84d116848 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -1,23 +1,21 @@ - -# config file -configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp") - # local files set(GAME main.cpp engine.cpp ) +if(NOT WIN32) + set(GAME ${GAME} crashcatcher.cpp) +endif() set(GAME_HEADER engine.hpp - config.hpp ) source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows - compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction - terrainstorage + characterpreview globalmap videoplayer ripplesimulation refraction + terrainstorage renderconst effectmanager ) add_openmw_dir (mwinput @@ -34,7 +32,8 @@ add_openmw_dir (mwgui enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview - tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers + tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog + recharge ) add_openmw_dir (mwdialogue @@ -58,6 +57,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor + contentloader esmloader omwloader actiontrap ) add_openmw_dir (mwclass @@ -68,15 +68,22 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security + aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting + disease pickpocket levelledlist combat steering + ) + +add_openmw_dir (mwstate + statemanagerimp charactermanager character ) add_openmw_dir (mwbase environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager - inputmanager windowmanager + inputmanager windowmanager statemanager ) # Main executable +set(BOOST_COMPONENTS system filesystem program_options thread wave) +find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) IF(OGRE_STATIC) ADD_DEFINITIONS(-DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_GL) @@ -106,6 +113,7 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} + ${SHINY_LIBRARIES} ${Boost_LIBRARIES} ${OPENAL_LIBRARY} ${SOUND_INPUT_LIBRARY} @@ -113,7 +121,6 @@ target_link_libraries(openmw ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} ${MYGUI_PLATFORM_LIBRARIES} - ${SHINY_LIBRARIES} "oics" "sdl4ogre" components @@ -132,12 +139,6 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT}) endif() -# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream -if (UNIX AND NOT APPLE) -target_link_libraries(openmw dl Xt) -endif() - - if(APPLE) find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp new file mode 100644 index 000000000..65a036919 --- /dev/null +++ b/apps/openmw/crashcatcher.cpp @@ -0,0 +1,454 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include + +#include + +#ifdef __linux__ +#include +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif +#elif defined (__APPLE__) +#include +#endif + + +static const char crash_switch[] = "--cc-handle-crash"; + +static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; +static const char pipe_err[] = "!!! Failed to create pipe\n"; +static const char fork_err[] = "!!! Failed to fork debug process\n"; +static const char exec_err[] = "!!! Failed to exec debug process\n"; + +static char argv0[PATH_MAX]; + +static char altstack[SIGSTKSZ]; + + +static struct { + int signum; + pid_t pid; + int has_siginfo; + siginfo_t siginfo; + char buf[1024]; +} crash_info; + + +static const struct { + const char *name; + int signum; +} signals[] = { +{ "Segmentation fault", SIGSEGV }, +{ "Illegal instruction", SIGILL }, +{ "FPU exception", SIGFPE }, +{ "System BUS error", SIGBUS }, +{ NULL, 0 } +}; + +static const struct { + int code; + const char *name; +} sigill_codes[] = { + #ifndef __FreeBSD__ + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, + #endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigfpe_codes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigsegv_codes[] = { + #ifndef __FreeBSD__ + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, + #endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigbus_codes[] = { + #ifndef __FreeBSD__ + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, + #endif + { 0, NULL } +}; + +static int (*cc_user_info)(char*, char*); + + +static void gdb_info(pid_t pid) +{ + char respfile[64]; + char cmd_buf[128]; + FILE *f; + int fd; + + /* Create a temp file to put gdb commands into */ + strcpy(respfile, "gdb-respfile-XXXXXX"); + if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) + { + fprintf(f, "attach %d\n" + "shell echo \"\"\n" + "shell echo \"* Loaded Libraries\"\n" + "info sharedlibrary\n" + "shell echo \"\"\n" + "shell echo \"* Threads\"\n" + "info threads\n" + "shell echo \"\"\n" + "shell echo \"* FPU Status\"\n" + "info float\n" + "shell echo \"\"\n" + "shell echo \"* Registers\"\n" + "info registers\n" + "shell echo \"\"\n" + "shell echo \"* Backtrace\"\n" + "thread apply all backtrace full\n" + "detach\n" + "quit\n", pid); + fclose(f); + + /* Run gdb and print process info. */ + snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); + printf("Executing: %s\n", cmd_buf); + fflush(stdout); + + system(cmd_buf); + /* Clean up */ + remove(respfile); + } + else + { + /* Error creating temp file */ + if(fd >= 0) + { + close(fd); + remove(respfile); + } + printf("!!! Could not create gdb command file\n"); + } + fflush(stdout); +} + +static void sys_info(void) +{ +#ifdef __unix__ + struct utsname info; + if(uname(&info)) + printf("!!! Failed to get system information\n"); + else + printf("System: %s %s %s %s %s\n", + info.sysname, info.nodename, info.release, info.version, info.machine); + + fflush(stdout); +#endif +} + +static size_t safe_write(int fd, const void *buf, size_t len) +{ + size_t ret = 0; + while(ret < len) + { + ssize_t rem; + if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) + { + if(errno == EINTR) + continue; + break; + } + ret += rem; + } + return ret; +} + +static void crash_catcher(int signum, siginfo_t *siginfo, void *context) +{ + //ucontext_t *ucontext = (ucontext_t*)context; + pid_t dbg_pid; + int fd[2]; + + /* Make sure the effective uid is the real uid */ + if(getuid() != geteuid()) + { + raise(signum); + return; + } + + safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); + if(pipe(fd) == -1) + { + safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); + raise(signum); + return; + } + + crash_info.signum = signum; + crash_info.pid = getpid(); + crash_info.has_siginfo = !!siginfo; + if(siginfo) + crash_info.siginfo = *siginfo; + if(cc_user_info) + cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); + + /* Fork off to start a crash handler */ + switch((dbg_pid=fork())) + { + /* Error */ + case -1: + safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); + raise(signum); + return; + + case 0: + dup2(fd[0], STDIN_FILENO); + close(fd[0]); + close(fd[1]); + + execl(argv0, argv0, crash_switch, NULL); + + safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); + _exit(1); + + default: +#ifdef __linux__ + prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); +#endif + safe_write(fd[1], &crash_info, sizeof(crash_info)); + close(fd[0]); + close(fd[1]); + + /* Wait; we'll be killed when gdb is done */ + do { + int status; + if(waitpid(dbg_pid, &status, 0) == dbg_pid && + (WIFEXITED(status) || WIFSIGNALED(status))) + { + /* The debug process died before it could kill us */ + raise(signum); + break; + } + } while(1); + } +} + +static void crash_handler(const char *logfile) +{ + const char *sigdesc = ""; + int i; + + if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) + { + fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); + exit(1); + } + + /* Get the signal description */ + for(i = 0;signals[i].name;++i) + { + if(signals[i].signum == crash_info.signum) + { + sigdesc = signals[i].name; + break; + } + } + + if(crash_info.has_siginfo) + { + switch(crash_info.signum) + { + case SIGSEGV: + for(i = 0;sigsegv_codes[i].name;++i) + { + if(sigsegv_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigsegv_codes[i].name; + break; + } + } + break; + + case SIGFPE: + for(i = 0;sigfpe_codes[i].name;++i) + { + if(sigfpe_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigfpe_codes[i].name; + break; + } + } + break; + + case SIGILL: + for(i = 0;sigill_codes[i].name;++i) + { + if(sigill_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigill_codes[i].name; + break; + } + } + break; + + case SIGBUS: + for(i = 0;sigbus_codes[i].name;++i) + { + if(sigbus_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigbus_codes[i].name; + break; + } + } + break; + } + } + fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stderr); + + if(logfile) + { + /* Create crash log file and redirect shell output to it */ + if(freopen(logfile, "wa", stdout) != stdout) + { + fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + exit(1); + } + fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); + + printf("*** Fatal Error ***\n" + "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + printf("Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stdout); + fflush(stdout); + } + + sys_info(); + + crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; + printf("%s\n", crash_info.buf); + fflush(stdout); + + if(crash_info.pid > 0) + { + gdb_info(crash_info.pid); + kill(crash_info.pid, SIGKILL); + } + + if(logfile) + { + char cwd[MAXPATHLEN]; + getcwd(cwd, MAXPATHLEN); + + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); + } + exit(0); +} + +int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) +{ + struct sigaction sa; + stack_t altss; + int retval; + + if(argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(logfile); + + cc_user_info = user_info; + + if(argv[0][0] == '/') + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + else + { + getcwd(argv0, sizeof(argv0)); + retval = strlen(argv0); + snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); + } + + /* Set an alternate signal stack so SIGSEGVs caused by stack overflows + * still run */ + altss.ss_sp = altstack; + altss.ss_flags = 0; + altss.ss_size = sizeof(altstack); + sigaltstack(&altss, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_catcher; + sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + + retval = 0; + while(num_signals--) + { + if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT && + *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) + { + *signals = 0; + retval = -1; + } + ++signals; + } + return retval; +} + + +// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2) +bool +is_debugger_attached(void) +{ + bool rc = false; + FILE *fd = fopen("/tmp", "r"); + + if (fileno(fd) > 5) + { + rc = true; + } + + fclose(fd); + return rc; +} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e1fd3a0af..bf1cca5e0 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,12 +1,14 @@ #include "engine.hpp" -#include "components/esm/loadcell.hpp" +#include #include #include #include +#include + #include #include @@ -18,6 +20,8 @@ #include #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -39,8 +43,7 @@ #include "mwmechanics/mechanicsmanagerimp.hpp" - -#include +#include "mwstate/statemanagerimp.hpp" void OMW::Engine::executeLocalScripts() { @@ -63,10 +66,6 @@ void OMW::Engine::executeLocalScripts() localScripts.setIgnore (MWWorld::Ptr()); } -void OMW::Engine::setAnimationVerbose(bool animverbose) -{ -} - bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) { bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -90,31 +89,47 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (mUseSound) MWBase::Environment::get().getSoundManager()->update(frametime); - // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + + // update game state + MWBase::Environment::get().getStateManager()->update (frametime); - bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_Running) + { + // global scripts + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + + bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); - // local scripts - executeLocalScripts(); // This does not handle the case where a global script causes a cell - // change, followed by a cell change in a local script during the same - // frame. + // local scripts + executeLocalScripts(); // This does not handle the case where a global script causes a + // cell change, followed by a cell change in a local script during + // the same frame. - // passing of time - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWorld()->advanceTime( - frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); + if (changed) // keep change flag for another frame, if cell changed happened in local script + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + if (!paused) + 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 MWBase::Environment::get().getMechanicsManager()->update(frametime, - MWBase::Environment::get().getWindowManager()->isGuiMode()); + paused); + + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_Running) + { + MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); + if(!paused && player.getClass().getCreatureStats(player).isDead()) + MWBase::Environment::get().getStateManager()->endGame(); + } // update world - MWBase::Environment::get().getWorld()->update(frametime, MWBase::Environment::get().getWindowManager()->isGuiMode()); + MWBase::Environment::get().getWorld()->update(frametime, paused); // update GUI Ogre::RenderWindow* window = mOgre->getWindow(); @@ -137,7 +152,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mOgre (0) , mFpsLevel(0) , mVerboseScripts (false) - , mNewGame (false) + , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) , mScriptContext (0) @@ -147,6 +162,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(NULL) , mActivationDistanceOverride(-1) + , mGrab(true) { std::srand ( std::time(NULL) ); @@ -158,6 +174,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) //kindly ask SDL not to trash our OGL context //might this be related to http://bugzilla.libsdl.org/show_bug.cgi?id=748 ? SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + SDL_SetMainReady(); if(SDL_Init(flags) != 0) { throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError())); @@ -210,7 +227,9 @@ void OMW::Engine::loadBSA() } else { - std::cout << "Archive " << *archive << " not found" << std::endl; + std::stringstream message; + message << "Archive '" << *archive << "' not found"; + throw std::runtime_error(message.str()); } } } @@ -261,34 +280,9 @@ void OMW::Engine::setCell (const std::string& cellName) mCellName = cellName; } -// Set master file (esm) -// - If the given name does not have an extension, ".esm" is added automatically - -void OMW::Engine::addMaster (const std::string& master) -{ - mMaster.push_back(master); - std::string &str = mMaster.back(); - - // Append .esm if not already there - std::string::size_type sep = str.find_last_of ("."); - if (sep == std::string::npos) - { - str += ".esm"; - } -} - -// Add plugin file (esp) -void OMW::Engine::addPlugin (const std::string& plugin) +void OMW::Engine::addContentFile(const std::string& file) { - 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"; - } + mContentFiles.push_back(file); } void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity) @@ -296,9 +290,9 @@ void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity) mVerboseScripts = scriptsVerbosity; } -void OMW::Engine::setNewGame(bool newGame) +void OMW::Engine::setSkipMenu (bool skipMenu) { - mNewGame = newGame; + mSkipMenu = skipMenu; } std::string OMW::Engine::loadSettings (Settings::Manager & settings) @@ -316,7 +310,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); // load user settings if they exist, otherwise just load the default settings as user settings - const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg"; + const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); else if (boost::filesystem::exists(localdefault)) @@ -328,18 +322,25 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) // load nif overrides NifOverrides::Overrides nifOverrides; - if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg")) - nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg"); - else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg")) - nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg"); - - settings.setBool("hardware cursors", "GUI", true); + std::string transparencyOverrides = "/transparency-overrides.cfg"; + std::string materialOverrides = "/material-overrides.cfg"; + if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + transparencyOverrides)) + nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + transparencyOverrides); + else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + transparencyOverrides)) + nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + transparencyOverrides); + if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + materialOverrides)) + nifOverrides.loadMaterialOverrides(mCfgMgr.getLocalPath().string() + materialOverrides); + else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + materialOverrides)) + nifOverrides.loadMaterialOverrides(mCfgMgr.getGlobalPath().string() + materialOverrides); return settingspath; } void OMW::Engine::prepareEngine (Settings::Manager & settings) { + mEnvironment.setStateManager ( + new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); + Nif::NIFFile::CacheLock cachelock; std::string renderSystem = settings.getString("render system", "Video"); @@ -357,8 +358,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mOgre->configure( mCfgMgr.getLogPath().string(), renderSystem, - Settings::Manager::getString("opengl rtt mode", "Video"), - false); + Settings::Manager::getString("opengl rtt mode", "Video")); // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. @@ -370,7 +370,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) addResourcesDirectory(mResDir / "mygui"); addResourcesDirectory(mResDir / "water"); addResourcesDirectory(mResDir / "shadows"); - addZipResource(mResDir / "mygui" / "Obliviontt.zip"); OEngine::Render::WindowSettings windowSettings; windowSettings.fullscreen = settings.getBool("fullscreen", "Video"); @@ -390,9 +389,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so - std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); + std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); - MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists); + MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); MWGui::WindowManager* window = new MWGui::WindowManager( @@ -401,25 +400,21 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setWindowManager (window); // Create the world - mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, + mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, mActivationDistanceOverride)); MWBase::Environment::get().getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); window->initUI(); - if (mNewGame) - // still redundant work here: recreate CharacterCreation(), - // double update visibility etc. - window->setNewGame(true); window->renderWorldMap(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder); - for (size_t i = 0; i < mMaster.size(); i++) - mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[i]); + for (size_t i = 0; i < mContentFiles.size(); i++) + mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); - Compiler::registerExtensions (mExtensions); + Compiler::registerExtensions (mExtensions); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); @@ -432,20 +427,23 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mVerboseScripts, *mScriptContext)); // Create game mechanics system - mEnvironment.setMechanicsManager (new MWMechanics::MechanicsManager); + MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; + mEnvironment.setMechanicsManager (mechanics); // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); mEnvironment.getWorld()->renderPlayer(); + mechanics->buildPlayer(); + window->updatePlayer(); - if (!mNewGame) - { - // load cell - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); + // load cell + ESM::Position pos; + MWBase::World *world = MWBase::Environment::get().getWorld(); + if (!mCellName.empty()) + { if (world->findExteriorPosition(mCellName, pos)) { world->changeToExteriorCell (pos); } @@ -455,7 +453,11 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } } else - mEnvironment.getWorld()->startNewGame(); + { + pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; + pos.rot[0] = pos.rot[1] = pos.pos[2] = 0; + world->changeToExteriorCell (pos); + } Ogre::FrameEvent event; event.timeSinceLastEvent = 0; @@ -481,8 +483,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) void OMW::Engine::go() { - assert (!mCellName.empty()); - assert (!mMaster.empty()); + assert (!mContentFiles.empty()); assert (!mOgre); Settings::Manager settings; @@ -502,8 +503,15 @@ void OMW::Engine::go() if (!mStartupScript.empty()) MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + // start in main menu + if (!mSkipMenu) + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + else + MWBase::Environment::get().getStateManager()->newGame (true); + // Start the main rendering loop - mOgre->start(); + while (!mEnvironment.get().getStateManager()->hasQuitRequest()) + Ogre::Root::getSingleton().renderOneFrame(); // Save user settings settings.saveUser(settingspath); @@ -521,15 +529,20 @@ void OMW::Engine::activate() if (ptr.isEmpty()) return; + if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated + return; + MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); boost::shared_ptr action = - MWWorld::Class::get (ptr).activate (ptr, MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + MWWorld::Class::get (ptr).activate (ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); interpreterContext.activate (ptr, action); std::string script = MWWorld::Class::get (ptr).getScript (ptr); + MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayerPtr()); + if (!script.empty()) { MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr); @@ -547,7 +560,7 @@ void OMW::Engine::screenshot() // Count screenshots. int shotCount = 0; - const std::string screenshotPath = mCfgMgr.getUserPath().string(); + const std::string& screenshotPath = mCfgMgr.getUserDataPath().string(); // Find the first unused filename with a do-while std::ostringstream stream; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 665b0094c..8b2a65b7e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -68,11 +68,10 @@ namespace OMW boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; - std::vector mMaster; - std::vector mPlugins; + std::vector mContentFiles; int mFpsLevel; bool mVerboseScripts; - bool mNewGame; + bool mSkipMenu; bool mUseSound; bool mCompileAll; std::string mFocusName; @@ -80,6 +79,8 @@ namespace OMW bool mScriptConsoleMode; std::string mStartupScript; int mActivationDistanceOverride; + // Grab mouse? + bool mGrab; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -135,13 +136,11 @@ namespace OMW /// Set start cell name (only interiors for now) void setCell(const std::string& cellName); - /// Set master file (esm) - /// - If the given name does not have an extension, ".esm" is added automatically - 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); + /** + * @brief addContentFile - Adds content file (ie. esm/esp, or omwgame/omwaddon) to the content files container. + * @param file - filename (extension is required) + */ + void addContentFile(const std::string& file); /// Enable fps counter void showFPS(int level); @@ -152,8 +151,9 @@ namespace OMW /// Disable or enable all sounds void setSoundUsage(bool soundUsage); - /// Start as a new game. - void setNewGame(bool newGame); + void setSkipMenu (bool skipMenu); + + void setGrabMouse(bool grab) { mGrab = grab; } /// Initialise and enter main loop. void go(); @@ -170,8 +170,6 @@ namespace OMW /// Font encoding void setEncoding(const ToUTF8::FromType& encoding); - void setAnimationVerbose(bool animverbose); - void setFallbackValues(std::map map); /// Enable console-only script functionality diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 27afd734a..67578a214 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,8 +1,10 @@ #include +#include +#include #include -#include +#include #include "engine.hpp" #if defined(_WIN32) && !defined(_CONSOLE) @@ -10,19 +12,25 @@ #include // For OutputDebugString +#define WIN32_LEAN_AND_MEAN #include // makes __argc and __argv available on windows #include #endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#include +extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*)); +extern int is_debugger_attached(void); +#endif + // for Ogre::macBundlePath #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include #endif -#include "config.hpp" - #include /** * Workaround for problems with whitespaces in paths in older versions of Boost library @@ -107,19 +115,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("resources", bpo::value()->default_value("resources"), "set resources directory") - ("start", bpo::value()->default_value("Beshara"), + ("start", bpo::value()->default_value(""), "set initial cell") - ("master", bpo::value()->default_value(StringsVector(), "") - ->multitoken(), "master file(s)") - - ("plugin", bpo::value()->default_value(StringsVector(), "") - ->multitoken(), "plugin file(s)") - - ("anim-verbose", bpo::value()->implicit_value(true) - ->default_value(false), "output animation indices files") + ("content", bpo::value()->default_value(StringsVector(), "") + ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") - ("nosound", bpo::value()->implicit_value(true) + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") ("script-verbose", bpo::value()->implicit_value(true) @@ -134,8 +136,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-run", bpo::value()->default_value(""), "select a file containing a list of console commands that is executed on startup") - ("new-game", bpo::value()->implicit_value(true) - ->default_value(false), "activate char gen/new game mechanics") + ("skip-menu", bpo::value()->implicit_value(true) + ->default_value(false), "skip main menu on game startup") ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") @@ -150,9 +152,9 @@ 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"); + ("no-grab", "Don't grab mouse cursor") - ; + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(desc).allow_unregistered().run(); @@ -163,8 +165,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat bpo::store(valid_opts, variables); bpo::notify(variables); - cfgMgr.readConfiguration(variables, desc); - bool run = true; if (variables.count ("help")) @@ -182,6 +182,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (!run) return false; + cfgMgr.readConfiguration(variables, desc); + + engine.setGrabMouse(!variables.count("no-grab")); + // Font encoding settings std::string encoding(variables["encoding"].as()); std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; @@ -211,40 +215,28 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setResourceDir(variables["resources"].as()); - // master and plugin - StringsVector master = variables["master"].as(); - if (master.empty()) + StringsVector content = variables["content"].as(); + if (content.empty()) { - std::cout << "No master file given. Aborting...\n"; - return false; + std::cout << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..." << std::endl; + return false; } - StringsVector plugin = variables["plugin"].as(); - // 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++) + StringsVector::const_iterator it(content.begin()); + StringsVector::const_iterator end(content.end()); + for (; it != end; ++it) { - engine.addMaster(master[i]); - } - for (std::vector::size_type i = 0; i < plugin.size(); i++) - { - engine.addPlugin(plugin[i]); + engine.addContentFile(*it); } // startup-settings engine.setCell(variables["start"].as()); - engine.setNewGame(variables["new-game"].as()); + engine.setSkipMenu (variables["skip-menu"].as()); // other settings - engine.setSoundUsage(!variables["nosound"].as()); + engine.setSoundUsage(!variables["no-sound"].as()); engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setCompileAll(variables["script-all"].as()); - engine.setAnimationVerbose(variables["anim-verbose"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); @@ -255,6 +247,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat int main(int argc, char**argv) { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // Unix crash catcher + if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) + { + int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); + std::cout << "Installing crash catcher" << std::endl; + } + else + std::cout << "Running in a debugger, not installing crash catcher" << std::endl; +#endif + #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE // set current dir to bundle path boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); @@ -273,7 +277,13 @@ int main(int argc, char**argv) } catch (std::exception &e) { - std::cout << "\nERROR: " << e.what() << std::endl; +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO)) + std::cerr << "\nERROR: " << e.what() << std::endl; + else +#endif + SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); + return 1; } diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 58731d1c7..971bc3b4e 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -51,7 +51,7 @@ namespace MWBase virtual void persuade (int type) = 0; virtual int getTemporaryDispositionChange () const = 0; - virtual void applyTemporaryDispositionChange (int delta) = 0; + virtual void applyDispositionChange (int delta) = 0; }; } diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 5a13a50ec..3bc15746e 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -11,12 +11,14 @@ #include "mechanicsmanager.hpp" #include "inputmanager.hpp" #include "windowmanager.hpp" +#include "statemanager.hpp" MWBase::Environment *MWBase::Environment::sThis = 0; MWBase::Environment::Environment() : mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), - mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0) + mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0), + mStateManager (0) { assert (!sThis); sThis = this; @@ -68,6 +70,11 @@ void MWBase::Environment::setInputManager (InputManager *inputManager) mInputManager = inputManager; } +void MWBase::Environment::setStateManager (StateManager *stateManager) +{ + mStateManager = stateManager; +} + void MWBase::Environment::setFrameDuration (float duration) { mFrameDuration = duration; @@ -121,6 +128,12 @@ MWBase::InputManager *MWBase::Environment::getInputManager() const return mInputManager; } +MWBase::StateManager *MWBase::Environment::getStateManager() const +{ + assert (mStateManager); + return mStateManager; +} + float MWBase::Environment::getFrameDuration() const { return mFrameDuration; @@ -140,17 +153,20 @@ void MWBase::Environment::cleanup() delete mScriptManager; mScriptManager = 0; + delete mWindowManager; + mWindowManager = 0; + delete mWorld; mWorld = 0; delete mSoundManager; mSoundManager = 0; - delete mWindowManager; - mWindowManager = 0; - delete mInputManager; mInputManager = 0; + + delete mStateManager; + mStateManager = 0; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index a80e7ef87..eb636ea2f 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -11,6 +11,7 @@ namespace MWBase class MechanicsManager; class InputManager; class WindowManager; + class StateManager; /// \brief Central hub for mw-subsystems /// @@ -30,6 +31,7 @@ namespace MWBase DialogueManager *mDialogueManager; Journal *mJournal; InputManager *mInputManager; + StateManager *mStateManager; float mFrameDuration; Environment (const Environment&); @@ -60,6 +62,8 @@ namespace MWBase void setInputManager (InputManager *inputManager); + void setStateManager (StateManager *stateManager); + void setFrameDuration (float duration); ///< Set length of current frame in seconds. @@ -79,6 +83,8 @@ namespace MWBase InputManager *getInputManager() const; + StateManager *getStateManager() const; + float getFrameDuration() const; void cleanup(); diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 51e51edda..56d9601fc 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -5,10 +5,18 @@ #include #include +#include + #include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/topic.hpp" #include "../mwdialogue/quest.hpp" +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + namespace MWBase { /// \brief Interface for the player's journal (implemented in MWDialogue) @@ -46,7 +54,7 @@ namespace MWBase virtual int getJournalIndex (const std::string& id) const = 0; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId) = 0; + virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) = 0; virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. @@ -69,6 +77,12 @@ namespace MWBase virtual TTopicIter topicEnd() const = 0; ///< Iterator pointing past the last topic. + + virtual int countSavedGameRecords() const = 0; + + virtual void write (ESM::ESMWriter& writer) const = 0; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; }; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 7e09f9b4d..22dda0ce0 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Ogre { @@ -59,6 +60,8 @@ namespace MWBase /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). + virtual void advanceTime (float duration) = 0; + virtual void setPlayerName (const std::string& name) = 0; ///< Set player name. @@ -74,8 +77,12 @@ namespace MWBase virtual void setPlayerClass (const ESM::Class& class_) = 0; ///< Set player class to custom class. - virtual void restoreDynamicStats() = 0; - ///< If the player is sleeping, this should be called every hour. + virtual void rest(bool sleep) = 0; + ///< If the player is sleeping or waiting, this should be called every hour. + /// @param sleep is the player sleeping or waiting? + + virtual int getHoursToRest() const = 0; + ///< Calculate how many hours the player needs to rest in order to be fully healed virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) = 0; ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. @@ -86,6 +93,36 @@ namespace MWBase virtual int countDeaths (const std::string& id) const = 0; ///< Return the number of deaths for actors with the given ID. + /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! + virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; + + enum OffenseType + { + OT_Theft, // Taking items owned by an NPC or a faction you are not a member of + OT_Assault, // Attacking a peaceful NPC + OT_Murder, // Murdering a peaceful NPC + OT_Trespassing, // Staying in a cell you are not allowed in (where is this defined?) + OT_SleepingInOwnedBed, // Sleeping in a bed owned by an NPC or a faction you are not a member of + OT_Pickpocket // Entering pickpocket mode, leaving it, and being detected. Any items stolen are a separate crime (Theft) + }; + /** + * @brief Commit a crime. If any actors witness the crime and report it, + * reportCrime will be called automatically. + * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. + * @return was the crime reported? + */ + virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + OffenseType type, int arg=0) = 0; + virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + OffenseType type, int arg=0) = 0; + /// Utility to check if taking this item is illegal and calling commitCrime if so + virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count) = 0; + /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so + virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; + /// Attempt sleeping in a bed. If this is illegal, call commitCrime. + /// @return was it illegal, and someone saw you doing it? + virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0; + enum PersuasionType { PT_Admire, @@ -99,21 +136,35 @@ namespace MWBase float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) = 0; ///< Perform a persuasion action on NPC - virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; - ///< Forces an object to refresh its animation state. + virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; + ///< Forces an object to refresh its animation state. + + 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. + + virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; + + virtual void toggleAI() = 0; + virtual bool isAIActive() = 0; - 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 getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; - 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. + ///return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor) + virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; - virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + virtual void playerLoaded() = 0; }; } diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp index 32df2bfa3..ae146e064 100644 --- a/apps/openmw/mwbase/scriptmanager.hpp +++ b/apps/openmw/mwbase/scriptmanager.hpp @@ -35,8 +35,6 @@ namespace MWBase virtual ~ScriptManager() {} - virtual void resetGlobalScripts() = 0; - virtual void run (const std::string& name, Interpreter::Context& interpreterContext) = 0; ///< Run the script with the given name (compile first, if not compiled yet) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 4d764597c..f3973153a 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -147,6 +147,10 @@ namespace MWBase virtual void update(float duration) = 0; virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0; + + virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0; + + virtual void clear() = 0; }; } diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp new file mode 100644 index 000000000..cd907408a --- /dev/null +++ b/apps/openmw/mwbase/statemanager.hpp @@ -0,0 +1,81 @@ +#ifndef GAME_MWSTATE_STATEMANAGER_H +#define GAME_MWSTATE_STATEMANAGER_H + +#include +#include + +namespace MWState +{ + struct Slot; + class Character; +} + +namespace MWBase +{ + /// \brief Interface for game state manager (implemented in MWState) + class StateManager + { + public: + + enum State + { + State_NoGame, + State_Ended, + State_Running + }; + + typedef std::vector::const_iterator CharacterIterator; + + private: + + StateManager (const StateManager&); + ///< not implemented + + StateManager& operator= (const StateManager&); + ///< not implemented + + public: + + StateManager() {} + + virtual ~StateManager() {} + + virtual void requestQuit() = 0; + + virtual bool hasQuitRequest() const = 0; + + virtual void askLoadRecent() = 0; + + virtual State getState() const = 0; + + virtual void newGame (bool bypass = false) = 0; + ///< Start a new game. + /// + /// \param bypass Skip new game mechanics. + + virtual void endGame() = 0; + + virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0; + ///< Write a saved game to \a slot or create a new slot if \a slot == 0. + /// + /// \note Slot must belong to the current character. + + virtual void loadGame (const MWState::Character *character, const MWState::Slot *slot) = 0; + ///< Load a saved game file from \a slot. + /// + /// \note \a slot must belong to \a character. + + virtual MWState::Character *getCurrentCharacter (bool create = true) = 0; + ///< \param create Create a new character, if there is no current character. + + virtual CharacterIterator characterBegin() = 0; + ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned + /// iterator. + + virtual CharacterIterator characterEnd() = 0; + + virtual void update (float duration) = 0; + }; +} + +#endif diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 1cd867223..4fce19e33 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -33,6 +33,8 @@ namespace OEngine namespace ESM { struct Class; + class ESMReader; + class ESMWriter; } namespace MWWorld @@ -55,6 +57,12 @@ namespace MWGui class InventoryWindow; class ContainerWindow; class DialogueWindow; + + enum ShowInDialogueMode { + ShowInDialogueMode_IfPossible, + ShowInDialogueMode_Only, + ShowInDialogueMode_Never + }; } namespace SFO @@ -137,8 +145,8 @@ namespace MWBase virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) = 0; /// Set value for the given ID. - virtual void setValue (const std::string& id, const MWMechanics::Stat& value) = 0; - virtual void setValue (int parSkill, const MWMechanics::Stat& value) = 0; + virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value) = 0; + virtual void setValue (int parSkill, const MWMechanics::SkillValue& value) = 0; virtual void setValue (const std::string& id, const MWMechanics::DynamicStat& value) = 0; virtual void setValue (const std::string& id, const std::string& value) = 0; virtual void setValue (const std::string& id, int value) = 0; @@ -204,6 +212,7 @@ namespace MWBase virtual void activateQuickKey (int index) = 0; + virtual std::string getSelectedSpell() = 0; virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0; virtual void setSelectedEnchantItem(const MWWorld::Ptr& item) = 0; virtual void setSelectedWeapon(const MWWorld::Ptr& item) = 0; @@ -223,20 +232,17 @@ 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 = std::vector(), bool showInDialogueModeOnly = false) = 0; + virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; virtual void staticMessageBox(const std::string& message) = 0; virtual void removeStaticMessageBox() = 0; - - virtual void enterPressed () = 0; - virtual void activateKeyPressed () = 0; virtual int readPressedButton() = 0; ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual void onFrame (float frameDuration) = 0; /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - virtual std::map > getPlayerSkillValues() = 0; - virtual std::map > getPlayerAttributeValues() = 0; + virtual std::map getPlayerSkillValues() = 0; + virtual std::map getPlayerAttributeValues() = 0; virtual SkillList getPlayerMinorSkills() = 0; virtual SkillList getPlayerMajorSkills() = 0; @@ -265,6 +271,7 @@ namespace MWBase virtual void showCompanionWindow(MWWorld::Ptr actor) = 0; virtual void startSpellMaking(MWWorld::Ptr actor) = 0; virtual void startEnchanting(MWWorld::Ptr actor) = 0; + virtual void startRecharge(MWWorld::Ptr soulgem) = 0; virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0; virtual void startTraining(MWWorld::Ptr actor) = 0; virtual void startRepair(MWWorld::Ptr actor) = 0; @@ -280,9 +287,19 @@ namespace MWBase virtual const Translation::Storage& getTranslationDataStorage() const = 0; + /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; virtual Loading::Listener* getLoadingScreen() = 0; + + /// Should the cursor be visible? + virtual bool getCursorVisible() = 0; + + /// Clear all savegame-specific data + virtual void clear() = 0; + + virtual void write (ESM::ESMWriter& writer) = 0; + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c39e87826..3d033838c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -2,6 +2,7 @@ #define GAME_MWBASE_WORLD_H #include +#include #include @@ -30,17 +31,18 @@ namespace OEngine namespace ESM { class ESMReader; + class ESMWriter; struct Position; struct Cell; struct Class; struct Potion; struct Spell; struct NPC; + struct CellId; } namespace MWRender { - class ExternalRendering; class Animation; } @@ -80,7 +82,6 @@ namespace MWBase Render_CollisionDebug, Render_Wireframe, Render_Pathgrid, - Render_Compositors, Render_BoundingBoxes }; @@ -96,13 +97,26 @@ namespace MWBase virtual void startNewGame() = 0; + virtual void clear() = 0; + + virtual int countSavedGameRecords() const = 0; + + virtual void write (ESM::ESMWriter& writer) const = 0; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap) = 0; + virtual OEngine::Render::Fader* getFader() = 0; - ///< \ŧodo remove this function. Rendering details should not be exposed. + ///< \todo remove this function. Rendering details should not be exposed. virtual MWWorld::CellStore *getExterior (int x, int y) = 0; virtual MWWorld::CellStore *getInterior (const std::string& name) = 0; + virtual MWWorld::CellStore *getCell (const ESM::CellId& id) = 0; + + virtual void useDeathCamera() = 0; + virtual void setWaterHeight(const float height) = 0; virtual void toggleWater() = 0; @@ -114,6 +128,7 @@ namespace MWBase virtual const MWWorld::Fallback *getFallback () const = 0; virtual MWWorld::Player& getPlayer() = 0; + virtual MWWorld::Ptr getPlayerPtr() = 0; virtual const MWWorld::ESMStore& getStore() const = 0; @@ -131,7 +146,7 @@ namespace MWBase virtual Ogre::Vector2 getNorthVector (MWWorld::CellStore* cell) = 0; ///< get north vector (OGRE coordinates) for given interior cell - virtual std::vector getDoorMarkers (MWWorld::CellStore* cell) = 0; + virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; ///< get a list of teleport door markers for a given cell, to be displayed on the local map virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) = 0; @@ -140,16 +155,26 @@ namespace MWBase virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior) = 0; ///< see MWRender::LocalMap::isPositionExplored - virtual MWWorld::Globals::Data& getGlobalVariable (const std::string& name) = 0; + virtual void setGlobalInt (const std::string& name, int value) = 0; + ///< Set value independently from real type. + + virtual void setGlobalFloat (const std::string& name, float value) = 0; + ///< Set value independently from real type. - virtual MWWorld::Globals::Data getGlobalVariable (const std::string& name) const = 0; + virtual int getGlobalInt (const std::string& name) const = 0; + ///< Get value independently from real type. + + virtual float getGlobalFloat (const std::string& name) const = 0; + ///< Get value independently from real type. 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 std::string getCellName (const MWWorld::CellStore *cell = 0) const = 0; + ///< Return name of the cell. + /// + /// \note If cell==0, the cell the player is currently in will be used instead to + /// generate a name. virtual void removeRefScript (MWWorld::RefData *ref) = 0; //< Remove the script attached to ref from mLocalScripts @@ -158,6 +183,10 @@ namespace MWBase ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. + virtual MWWorld::Ptr searchPtr (const std::string& name, bool activeOnly) = 0; + ///< Return a pointer to a liveCellRef with the given name. + /// \param activeOnly do non search inactive cells. + virtual MWWorld::Ptr getPtrViaHandle (const std::string& handle) = 0; ///< Return a pointer to a liveCellRef with the given Ogre handle. @@ -182,8 +211,12 @@ namespace MWBase virtual void setDay (int day) = 0; ///< Set in-game time day. - virtual int getDay() = 0; - virtual int getMonth() = 0; + virtual int getDay() const = 0; + virtual int getMonth() const = 0; + virtual int getYear() const = 0; + + virtual std::string getMonthName (int month = -1) const = 0; + ///< Return name of month (-1: current month) virtual MWWorld::TimeStamp getTimeStamp() const = 0; ///< Return current in-game time stamp. @@ -212,6 +245,8 @@ namespace MWBase virtual void changeToExteriorCell (const ESM::Position& position) = 0; ///< Move to exterior cell. + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position) = 0; + virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -222,7 +257,7 @@ namespace MWBase /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to - /// use the "Head" node as a basis. + /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance) = 0; virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; @@ -241,7 +276,7 @@ namespace MWBase virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0; + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -309,14 +344,19 @@ namespace MWBase virtual void update (float duration, bool paused) = 0; - virtual bool placeObject(const MWWorld::Ptr& object, float cursorX, float cursorY) = 0; - ///< place an object into the gameworld at the specified cursor position + virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; + ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) + /// @param number of objects to place /// @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& actor, const MWWorld::Ptr& object) = 0; + virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0; + ///< copy and place an object into the gameworld at the given actor's position + /// @param actor giving the dropped object position + /// @param object + /// @param number of objects to place virtual bool canPlaceObject (float cursorX, float cursorY) = 0; ///< @return true if it is possible to place on object at specified cursor location @@ -328,7 +368,7 @@ namespace MWBase virtual bool isSwimming(const MWWorld::Ptr &object) const = 0; ///Is the head of the creature underwater? virtual bool isSubmerged(const MWWorld::Ptr &object) const = 0; - virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const = 0; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; virtual void togglePOV() = 0; @@ -357,9 +397,10 @@ namespace MWBase virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc - virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; + virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; + ///< get Line of Sight (morrowind stupid implementation) - virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0; + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; virtual int canRest() = 0; ///< check if the player is allowed to rest \n @@ -375,6 +416,7 @@ namespace MWBase virtual void playVideo(const std::string& name, bool allowSkipping) = 0; virtual void stopVideo() = 0; virtual void frameStarted (float dt, bool paused) = 0; + virtual void screenshot (Ogre::Image& image, int w, int h) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise @@ -406,6 +448,62 @@ namespace MWBase virtual bool getGodModeState() = 0; virtual bool toggleGodMode() = 0; + + /** + * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. + * @param actor + * @return true if the spell can be casted (i.e. the animation should start) + */ + virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0; + + virtual void castSpell (const MWWorld::Ptr& actor) = 0; + + virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + + virtual const std::vector& getContentFiles() const = 0; + + virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; + + // Are we in an exterior or pseudo-exterior cell and it's night? + virtual bool isDark() const = 0; + + virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) = 0; + + /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) + /// @note id must be lower case + virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id) = 0; + + enum DetectionType + { + Detect_Enchantment, + Detect_Key, + Detect_Creature + }; + /// List all references (filtered by \a type) detected by \a ptr. The range + /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. + /// @note This also works for references in containers. + virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, + DetectionType type) = 0; + + /// Update the value of some globals according to the world state, which may be used by dialogue entries. + /// This should be called when initiating a dialogue. + virtual void updateDialogueGlobals() = 0; + + /// Moves all stolen items from \a ptr to the closest evidence chest. + virtual void confiscateStolenItems(const MWWorld::Ptr& ptr) = 0; + + virtual void goToJail () = 0; + + /// Spawn a random creature from a levelled list next to the player + virtual void spawnRandomCreature(const std::string& creatureList) = 0; + + /// Spawn a blood effect for \a ptr at \a worldPosition + virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; + + virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 583cb08d3..8bc104d25 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -96,7 +96,11 @@ namespace MWClass std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { + text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + } info.text = text; return info; diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 697b75579..b3a1af288 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -124,10 +124,11 @@ namespace MWClass std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index a511207c4..e3974f243 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -17,7 +17,6 @@ #include "../mwworld/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/player.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -94,7 +93,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; const int size = 11; @@ -116,11 +115,11 @@ namespace MWClass for (int i=0; imBase->mData.mType) { - slots.push_back (int (sMapping[i][1])); + slots_.push_back (int (sMapping[i][1])); break; } - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Armor::getEquipmentSkill (const MWWorld::Ptr& ptr) const @@ -169,7 +168,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Armor::registerSelf() @@ -245,10 +247,11 @@ namespace MWClass + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -281,20 +284,25 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); + + if (ptr.getCellRef().mCharge == 0) + return std::make_pair(0, "#{sInventoryMessage1}"); // slots that this item can be equipped in - std::pair, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); - std::string npcRace = npc.get()->mBase->mRace; + if (slots_.first.empty()) + return std::make_pair(0, ""); - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + if (npc.getClass().isNpc()) { + std::string npcRace = npc.get()->mBase->mRace; // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); @@ -302,29 +310,20 @@ namespace MWClass { std::vector parts = ptr.get()->mBase->mParts.mParts; - if(*slot == MWWorld::InventoryStore::Slot_Helmet) + for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_Head) - { - return std::make_pair(0, "#{sNotifyMessage13}"); - } - } - } - - if (*slot == MWWorld::InventoryStore::Slot_Boots) - { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) - { - return std::make_pair(0, "#{sNotifyMessage14}"); - } - } + if((*itr).mPart == ESM::PRT_Head) + return std::make_pair(0, "#{sNotifyMessage13}"); + if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) + return std::make_pair(0, "#{sNotifyMessage14}"); } } + } + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) + { + // If equipping a shield, check if there's a twohanded weapon conflicting with it if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -367,12 +366,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mArmors.insert(*ref), &cell); } - float Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index d8d09d5bb..17cfca453 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -79,7 +79,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index a692b30d8..0e6506514 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -136,10 +136,11 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -171,6 +172,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } boost::shared_ptr Book::use (const MWWorld::Ptr& ptr) const @@ -187,12 +189,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mBooks.insert(*ref), &cell); } - float Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 7fb8a9507..79b823fa9 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -58,7 +58,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; virtual float getWeight (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 2dbb7ee6f..ab98d05ae 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -14,7 +14,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/nullaction.hpp" -#include "../mwworld/player.hpp" #include "../mwgui/tooltips.hpp" @@ -78,12 +77,12 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; if (ref->mBase->mData.mType==ESM::Clothing::Ring) { - slots.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); - slots.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); } else { @@ -105,12 +104,12 @@ namespace MWClass for (int i=0; imBase->mData.mType) { - slots.push_back (int (sMapping[i][1])); + slots_.push_back (int (sMapping[i][1])); break; } } - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Clothing::getEquipmentSkill (const MWWorld::Ptr& ptr) const @@ -191,10 +190,11 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -227,18 +227,20 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in - std::pair, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); - std::string npcRace = npc.get()->mBase->mRace; + if (slots_.first.empty()) + return std::make_pair(0, ""); - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + if (npc.getClass().isNpc()) { + std::string npcRace = npc.get()->mBase->mRace; // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); @@ -246,25 +248,16 @@ namespace MWClass { std::vector parts = ptr.get()->mBase->mParts.mParts; - if(*slot == MWWorld::InventoryStore::Slot_Helmet) + for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_Head) - return std::make_pair(0, "#{sNotifyMessage13}"); - } - } - - if (*slot == MWWorld::InventoryStore::Slot_Boots) - { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) - return std::make_pair(0, "#{sNotifyMessage15}"); - } + if((*itr).mPart == ESM::PRT_Head) + return std::make_pair(0, "#{sNotifyMessage13}"); + if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) + return std::make_pair(0, "#{sNotifyMessage15}"); } } } + return std::make_pair (1, ""); } @@ -286,12 +279,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mClothes.insert(*ref), &cell); } - float Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index e2e1188a1..a73b2c190 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -71,7 +71,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; virtual float getWeight (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 783eabff6..546d6538c 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -2,6 +2,7 @@ #include "container.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -13,10 +14,9 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/actionapply.hpp" #include "../mwworld/actionopen.hpp" +#include "../mwworld/actiontrap.hpp" #include "../mwworld/physicssystem.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwgui/tooltips.hpp" @@ -53,7 +53,7 @@ namespace MWClass ptr.get(); data->mContainerStore.fill( - ref->mBase->mInventory, ptr.getCellRef().mOwner, MWBase::Environment::get().getWorld()->getStore()); + ref->mBase->mInventory, ptr.getCellRef().mOwner, ptr.getCellRef().mFaction, MWBase::Environment::get().getWorld()->getStore()); // store ptr.getRefData().setCustomData (data.release()); @@ -108,7 +108,7 @@ namespace MWClass const std::string lockedSound = "LockedChest"; const std::string trapActivationSound = "Disarm Trap Fail"; - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player); bool needKey = ptr.getCellRef().mLockLevel>0; @@ -147,11 +147,9 @@ namespace MWClass } else { - // Trap activation goes here - std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl; - boost::shared_ptr action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap)); + // Activate trap + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); action->setSound(trapActivationSound); - ptr.getCellRef().mTrap = ""; return action; } } @@ -218,6 +216,7 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -260,4 +259,26 @@ namespace MWClass return MWWorld::Ptr(&cell.mContainers.insert(*ref), &cell); } + + void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::ContainerState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + readState (state2.mInventory); + } + + void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::ContainerState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + writeState (state2.mInventory); + } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 006e4bd22..c97867d35 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -54,6 +54,14 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. + static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 20f95ab0e..6af8373c5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -2,10 +2,12 @@ #include "creature.hpp" #include +#include #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -26,22 +28,30 @@ #include "../mwgui/tooltips.hpp" +#include "../mwworld/inventorystore.hpp" + #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/combat.hpp" namespace { struct CustomData : public MWWorld::CustomData { MWMechanics::CreatureStats mCreatureStats; - MWWorld::ContainerStore mContainerStore; + MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures MWMechanics::Movement mMovement; virtual MWWorld::CustomData *clone() const; + + CustomData() : mContainerStore(0) {} + virtual ~CustomData() { delete mContainerStore; } }; MWWorld::CustomData *CustomData::clone() const { - return new CustomData (*this); + CustomData* cloned = new CustomData (*this); + cloned->mContainerStore = mContainerStore->clone(); + return cloned; } } @@ -61,6 +71,17 @@ namespace MWClass fMinWalkSpeedCreature = gmst.find("fMinWalkSpeedCreature"); fMaxWalkSpeedCreature = gmst.find("fMaxWalkSpeedCreature"); + 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"); + fKnockDownMult = gmst.find("fKnockDownMult"); + iKnockDownOddsMult = gmst.find("iKnockDownOddsMult"); + iKnockDownOddsBase = gmst.find("iKnockDownOddsBase"); inited = true; } @@ -68,14 +89,14 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); // creature stats - data->mCreatureStats.getAttribute(0).set (ref->mBase->mData.mStrength); - data->mCreatureStats.getAttribute(1).set (ref->mBase->mData.mIntelligence); - data->mCreatureStats.getAttribute(2).set (ref->mBase->mData.mWillpower); - data->mCreatureStats.getAttribute(3).set (ref->mBase->mData.mAgility); - data->mCreatureStats.getAttribute(4).set (ref->mBase->mData.mSpeed); - data->mCreatureStats.getAttribute(5).set (ref->mBase->mData.mEndurance); - data->mCreatureStats.getAttribute(6).set (ref->mBase->mData.mPersonality); - data->mCreatureStats.getAttribute(7).set (ref->mBase->mData.mLuck); + data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength); + data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence); + data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower); + data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility); + data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed); + data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance); + data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality); + data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck); data->mCreatureStats.setHealth (ref->mBase->mData.mHealth); data->mCreatureStats.setMagicka (ref->mBase->mData.mMana); data->mCreatureStats.setFatigue (ref->mBase->mData.mFatigue); @@ -84,10 +105,10 @@ namespace MWClass data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage); - data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello); - data->mCreatureStats.setAiSetting (1, ref->mBase->mAiData.mFight); - data->mCreatureStats.setAiSetting (2, ref->mBase->mAiData.mFlee); - data->mCreatureStats.setAiSetting (3, ref->mBase->mAiData.mAlarm); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -95,11 +116,23 @@ namespace MWClass data->mCreatureStats.getSpells().add (*iter); // inventory - data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr), - MWBase::Environment::get().getWorld()->getStore()); + if (ref->mBase->mFlags & ESM::Creature::Weapon) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); // store ptr.getRefData().setCustomData (data.release()); + + getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "", + MWBase::Environment::get().getWorld()->getStore()); + + // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. + // (except for gold you gave him) + getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr); + + if (ref->mBase->mFlags & ESM::Creature::Weapon) + getInventoryStore(ptr).autoEquip(ptr); } } @@ -118,8 +151,10 @@ namespace MWClass void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { + MWWorld::LiveCellRef *ref = ptr.get(); + MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertCreature(ptr); + actors.insertCreature(ptr, ref->mBase->mFlags & ESM::Creature::Weapon); } void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const @@ -161,6 +196,144 @@ namespace MWClass void Creature::hit(const MWWorld::Ptr& ptr, int type) const { + MWWorld::LiveCellRef *ref = + ptr.get(); + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + MWMechanics::CreatureStats &stats = getCreatureStats(ptr); + + // Get the weapon used (if hand-to-hand, weapon = inv.end()) + MWWorld::Ptr weapon; + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name()) + weapon = *weaponslot; + } + + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::DynamicStat fatigue = stats.getFatigue(); + const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon.isEmpty()) + fatigueLoss += weapon.getClass().getWeight(weapon) * stats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + stats.setFatigue(fatigue); + + // TODO: where is the distance defined? + float dist = 200.f; + if (!weapon.isEmpty()) + { + const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); + dist = fCombatDistance * weapon.get()->mBase->mData.mReach; + } + std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist); + if (result.first.isEmpty()) + return; // Didn't hit anything + + MWWorld::Ptr victim = result.first; + + if (!victim.getClass().isActor()) + return; // Can't hit non-actors + + Ogre::Vector3 hitPosition = result.second; + + MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); + const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); + float hitchance = ref->mBase->mData.mCombat + + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + hitchance *= stats.getFatigueTerm(); + hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - + mageffects.get(ESM::MagicEffect::Blind).mMagnitude; + hitchance -= otherstats.getEvasion(); + + if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) + { + victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); + return; + } + + int min,max; + switch (type) + { + case 0: + min = ref->mBase->mData.mAttack[0]; + max = ref->mBase->mData.mAttack[1]; + break; + case 1: + min = ref->mBase->mData.mAttack[2]; + max = ref->mBase->mData.mAttack[3]; + break; + case 2: + default: + min = ref->mBase->mData.mAttack[4]; + max = ref->mBase->mData.mAttack[5]; + break; + } + + // I think this should be random, since attack1-3 animations don't have an attack strength like NPCs do + float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0); + + if (!weapon.isEmpty()) + { + const bool weaphashealth = get(weapon).hasItemHealth(weapon); + const unsigned char *attack = NULL; + if(type == ESM::Weapon::AT_Chop) + attack = weapon.get()->mBase->mData.mChop; + else if(type == ESM::Weapon::AT_Slash) + attack = weapon.get()->mBase->mData.mSlash; + else if(type == ESM::Weapon::AT_Thrust) + attack = weapon.get()->mBase->mData.mThrust; + if(attack) + { + float weaponDamage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); + weaponDamage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); + if(weaphashealth) + { + int weapmaxhealth = weapon.get()->mBase->mData.mHealth; + if(weapon.getCellRef().mCharge == -1) + weapon.getCellRef().mCharge = weapmaxhealth; + weaponDamage *= float(weapon.getCellRef().mCharge) / weapmaxhealth; + } + + if (!MWBase::Environment::get().getWorld()->getGodModeState()) + weapon.getCellRef().mCharge -= std::min(std::max(1, + (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge); + + // Weapon broken? unequip it + if (weapon.getCellRef().mCharge == 0) + weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); + + damage += weaponDamage; + } + + // Apply "On hit" enchanted weapons + std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(ptr, victim); + cast.mHitPosition = hitPosition; + cast.cast(weapon); + } + } + } + + if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) + damage = 0; + + if (damage > 0) + MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + + victim.getClass().onHit(victim, damage, true, weapon, ptr, true); } void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const @@ -187,18 +360,60 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } - if(ishealth) + if (damage > 0.0f && !object.isEmpty()) + MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); + + if (damage > 0.f) { - if(damage > 0.0f) + // Check for knockdown + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); + float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() + * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + { + getCreatureStats(ptr).setKnockedDown(true); + + } + else + getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? + + if(ishealth) + { MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); - float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; - setActorHealth(ptr, health, attacker); + float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; + setActorHealth(ptr, health, attacker); + } + else + { + MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); + fatigue.setCurrent(fatigue.getCurrent() - damage, true); + getCreatureStats(ptr).setFatigue(fatigue); + } } - else + } + + void Creature::block(const MWWorld::Ptr &ptr) const + { + MWWorld::InventoryStore& inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield == inv.end()) + return; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + switch(shield->getClass().getEquipmentSkill(*shield)) { - MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); - fatigue.setCurrent(fatigue.getCurrent() - damage); - getCreatureStats(ptr).setFatigue(fatigue); + case ESM::Skill::LightArmor: + sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::MediumArmor: + sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::HeavyArmor: + sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); + break; + default: + return; } } @@ -241,18 +456,33 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); } - MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) - const + MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return *dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + } + + MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + if (ref->mBase->mFlags & ESM::Creature::Weapon) + return dynamic_cast(getContainerStore(ptr)); + else + throw std::runtime_error("this creature has no inventory store"); + } + + bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + return (ref->mBase->mFlags & ESM::Creature::Weapon); } std::string Creature::getScript (const MWWorld::Ptr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -282,10 +512,51 @@ namespace MWClass float Creature::getSpeed(const MWWorld::Ptr &ptr) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + float walkSpeed = fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified() * (fMaxWalkSpeedCreature->getFloat() - fMinWalkSpeedCreature->getFloat()); - /// \todo what about the rest? - return walkSpeed; + + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); + + const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr); + + bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); + + float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) * + fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat()); + + float moveSpeed; + if(normalizedEncumbrance >= 1.0f) + moveSpeed = 0.0f; + else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + world->isLevitationEnabled())) + { + float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + + mageffects.get(ESM::MagicEffect::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(running) + swimSpeed = runSpeed; + swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; + swimSpeed *= fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * + fSwimRunAthleticsMult->getFloat(); + moveSpeed = swimSpeed; + } + else if(running) + moveSpeed = runSpeed; + else + moveSpeed = walkSpeed; + if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) + moveSpeed *= 0.75f; + + return moveSpeed; } MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const @@ -411,6 +682,14 @@ namespace MWClass return MWWorld::Ptr(&cell.mCreatures.insert(*ref), &cell); } + bool Creature::isFlying(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Flies; + } + int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) { if(name == "left") @@ -449,6 +728,71 @@ namespace MWClass throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } + int Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get().find(skill); + + switch (skillRecord->mData.mSpecialization) + { + case ESM::Class::Combat: + return ref->mBase->mData.mCombat; + case ESM::Class::Magic: + return ref->mBase->mData.mMagic; + case ESM::Class::Stealth: + return ref->mBase->mData.mStealth; + default: + throw std::runtime_error("invalid specialisation"); + } + } + + int Creature::getBloodTexture(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + if (ref->mBase->mFlags & ESM::Creature::Skeleton) + return 1; + if (ref->mBase->mFlags & ESM::Creature::Metal) + return 2; + return 0; + } + + void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::CreatureState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore-> + readState (state2.mInventory); + } + + void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::CreatureState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore-> + writeState (state2.mInventory); + } + const ESM::GameSetting* Creature::fMinWalkSpeedCreature; const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; + const ESM::GameSetting *Creature::fEncumberedMoveEffect; + const ESM::GameSetting *Creature::fSneakSpeedMultiplier; + const ESM::GameSetting *Creature::fAthleticsRunBonus; + const ESM::GameSetting *Creature::fBaseRunMultiplier; + const ESM::GameSetting *Creature::fMinFlySpeed; + const ESM::GameSetting *Creature::fMaxFlySpeed; + const ESM::GameSetting *Creature::fSwimRunBase; + const ESM::GameSetting *Creature::fSwimRunAthleticsMult; + const ESM::GameSetting *Creature::fKnockDownMult; + const ESM::GameSetting *Creature::iKnockDownOddsMult; + const ESM::GameSetting *Creature::iKnockDownOddsBase; + } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 0d8694ff8..adaf62a96 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -16,6 +16,18 @@ namespace MWClass static const ESM::GameSetting *fMinWalkSpeedCreature; static const ESM::GameSetting *fMaxWalkSpeedCreature; + 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 *fKnockDownMult; + static const ESM::GameSetting *iKnockDownOddsMult; + static const ESM::GameSetting *iKnockDownOddsBase; + public: @@ -44,6 +56,8 @@ namespace MWClass virtual void hit(const MWWorld::Ptr& ptr, int type) const; + virtual void block(const MWWorld::Ptr &ptr) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; @@ -56,6 +70,11 @@ namespace MWClass const MWWorld::Ptr& ptr) const; ///< Return container store + virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; + ///< Return inventory store + + virtual bool hasInventoryStore (const MWWorld::Ptr &ptr) const; + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -72,7 +91,7 @@ namespace MWClass virtual bool isEssential (const MWWorld::Ptr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - + virtual int getServices (const MWWorld::Ptr& actor) const; virtual bool isPersistent (const MWWorld::Ptr& ptr) const; @@ -99,6 +118,21 @@ namespace MWClass isActor() const { return true; } + + virtual bool isFlying (const MWWorld::Ptr &ptr) const; + + virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; + + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) + virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 53dd34bb4..ea586e5b6 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -3,6 +3,24 @@ #include +#include "../mwmechanics/levelledlist.hpp" + +#include "../mwworld/customdata.hpp" + +namespace +{ + struct CustomData : public MWWorld::CustomData + { + // TODO: save the creature we spawned here + virtual MWWorld::CustomData *clone() const; + }; + + MWWorld::CustomData *CustomData::clone() const + { + return new CustomData (*this); + } +} + namespace MWClass { std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const @@ -16,4 +34,33 @@ namespace MWClass registerClass (typeid (ESM::CreatureLevList).name(), instance); } + + void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const + { + ensureCustomData(ptr); + } + + void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const + { + if (!ptr.getRefData().getCustomData()) + { + std::auto_ptr data (new CustomData); + + MWWorld::LiveCellRef *ref = + ptr.get(); + + std::string id = MWMechanics::getLevelledItem(ref->mBase, true); + + if (!id.empty()) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::ManualRef ref(store, id); + ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; + // TODO: hold on to this for respawn purposes later + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos); + } + + ptr.getRefData().setCustomData(data.release()); + } + } } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 81965efd5..d2c02043e 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -7,6 +7,8 @@ namespace MWClass { class CreatureLevList : public MWWorld::Class { + void ensureCustomData (const MWWorld::Ptr& ptr) const; + public: virtual std::string getName (const MWWorld::Ptr& ptr) const; @@ -14,6 +16,9 @@ namespace MWClass /// can return an empty string. static void registerSelf(); + + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + ///< Add reference into a cell for rendering }; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 3a0e4d0ba..f99cffe04 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -8,16 +8,15 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/failedaction.hpp" -#include "../mwworld/actionapply.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/actiondoor.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/actiontrap.hpp" #include "../mwgui/tooltips.hpp" @@ -97,7 +96,7 @@ namespace MWClass if (needKey && hasKey) { - if(actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); ptr.getCellRef().mLockLevel = 0; // using a key disarms the trap @@ -109,12 +108,8 @@ namespace MWClass if(!ptr.getCellRef().mTrap.empty()) { // Trap activation - std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl; - - boost::shared_ptr action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap)); + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); action->setSound(trapActivationSound); - ptr.getCellRef().mTrap = ""; - return action; } @@ -122,7 +117,7 @@ namespace MWClass { // teleport door /// \todo remove this if clause once ActionTeleport can also support other actors - if (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()==actor) + if (MWBase::Environment::get().getWorld()->getPlayerPtr()==actor) { boost::shared_ptr action(new MWWorld::ActionTeleport (ref->mRef.mDestCell, ref->mRef.mDoorDest)); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index f629cc15d..faf29bc83 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -12,7 +12,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/actioneat.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/nullaction.hpp" #include "../mwmechanics/npcstats.hpp" @@ -145,17 +144,21 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + MWGui::Widgets::SpellEffectList list; for (int i=0; i<4; ++i) { @@ -166,10 +169,10 @@ namespace MWClass 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)); + params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue*2) + || (i == 2 && alchemySkill >= fWortChanceValue*3) + || (i == 3 && alchemySkill >= fWortChanceValue*4)); list.push_back(params); } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 7eefc6167..ddb2c16d6 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -2,6 +2,7 @@ #include "light.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -16,12 +17,34 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/customdata.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +namespace +{ + struct CustomData : public MWWorld::CustomData + { + float mTime; + ///< Time remaining + + CustomData(MWWorld::Ptr ptr) + { + MWWorld::LiveCellRef *ref = ptr.get(); + mTime = ref->mBase->mData.mTime; + } + ///< Constructs this CustomData from the base values for Ptr. + + virtual MWWorld::CustomData *clone() const + { + return new CustomData (*this); + } + }; +} + namespace MWClass { void Light::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -99,12 +122,12 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; if (ref->mBase->mData.mFlags & ESM::Light::Carry) - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Light::getValue (const MWWorld::Ptr& ptr) const @@ -161,10 +184,11 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -182,6 +206,21 @@ namespace MWClass return action; } + void Light::setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const + { + ensureCustomData(ptr); + + float &timeRemaining = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + timeRemaining = duration; + } + + float Light::getRemainingUsageTime (const MWWorld::Ptr& ptr) const + { + ensureCustomData(ptr); + + return dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + } + MWWorld::Ptr Light::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { @@ -191,6 +230,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mLights.insert(*ref), &cell); } + void Light::ensureCustomData (const MWWorld::Ptr& ptr) const + { + if (!ptr.getRefData().getCustomData()) + ptr.getRefData().setCustomData(new CustomData(ptr)); + } + bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const { return npcServices & ESM::NPC::Lights; @@ -225,4 +270,24 @@ namespace MWClass } return std::make_pair(1,""); } + + void Light::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::LightState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mTime = state2.mTime; + } + + void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::LightState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + state2.mTime = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + } } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 79d662763..5568e1727 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -10,6 +10,8 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + void ensureCustomData (const MWWorld::Ptr& ptr) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; @@ -56,6 +58,12 @@ namespace MWClass const; ///< Generate action for using via inventory menu + virtual void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const; + ///< Sets the remaining duration of the object. + + virtual float getRemainingUsageTime (const MWWorld::Ptr& ptr) const; + ///< Returns the remaining duration of the object. + virtual std::string getModel(const MWWorld::Ptr &ptr) const; virtual float getWeight (const MWWorld::Ptr& ptr) const; @@ -63,6 +71,14 @@ namespace MWClass virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 5931a0102..795b66052 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -74,11 +74,11 @@ namespace MWClass std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const { - std::vector slots; + std::vector slots_; - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Lockpick::getValue (const MWWorld::Ptr& ptr) const @@ -86,7 +86,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Lockpick::registerSelf() @@ -138,10 +141,11 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 6247191a9..e58716f1c 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -93,7 +93,9 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - int value = (ptr.getCellRef().mGoldValue == 1) ? ref->mBase->mData.mValue : ptr.getCellRef().mGoldValue; + int value = ref->mBase->mData.mValue; + if (ptr.getCellRef().mGoldValue > 1 && ptr.getRefData().getCount() == 1) + value = ptr.getCellRef().mGoldValue; if (ptr.getCellRef().mSoul != "") { @@ -153,11 +155,8 @@ namespace MWClass int count = ptr.getRefData().getCount(); bool gold = isGold(ptr); - - if (gold && ptr.getCellRef().mGoldValue != 1) - count = ptr.getCellRef().mGoldValue; - else if (gold) - count *= ref->mBase->mData.mValue; + if (gold) + count *= getValue(ptr); std::string countString; if (!gold) @@ -184,6 +183,7 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -201,7 +201,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore(); if (isGold(ptr)) { - int goldAmount = ptr.getRefData().getCount(); + int goldAmount = getValue(ptr) * ptr.getRefData().getCount(); std::string base = "Gold_001"; if (goldAmount >= 100) @@ -219,8 +219,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = newRef.getPtr().get(); newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell); - newPtr.getRefData ().setCount(1); newPtr.getCellRef().mGoldValue = goldAmount; + newPtr.getRefData().setCount(1); } else { MWWorld::LiveCellRef *ref = ptr.get(); @@ -242,7 +242,12 @@ namespace MWClass MWWorld::LiveCellRef *ref = item.get(); - return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc); + return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_005") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_010") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_025") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_100"); } float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const @@ -252,4 +257,11 @@ namespace MWClass return ref->mBase->mData.mWeight; } + bool Miscellaneous::isKey(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mIsKey; + } + } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 16a8e8c05..16e9ca10b 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -57,6 +57,8 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + + virtual bool isKey (const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 01a0c0a6f..34ea515ba 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -3,12 +3,11 @@ #include -#include - #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -20,6 +19,9 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/disease.hpp" +#include "../mwmechanics/combat.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -36,9 +38,6 @@ namespace { - const Ogre::Radian kOgrePi (Ogre::Math::PI); - const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0)); - struct CustomData : public MWWorld::CustomData { MWMechanics::NpcStats mNpcStats; @@ -62,11 +61,10 @@ namespace bool male = (npc->mFlags & ESM::NPC::Female) == 0; int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; - creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale); + creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); } // class bonus @@ -78,13 +76,13 @@ namespace int attribute = class_->mData.mAttribute[i]; if (attribute>=0 && attribute<8) { - creatureStats.getAttribute(attribute).setBase ( + creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10); } } // skill bonus - for (int attribute=0; attribute((level-1) * modifierSum+0.5), 100) ); } @@ -130,6 +128,101 @@ namespace creatureStats.setHealth(static_cast (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); } + + /** + * @brief autoCalculateSkills + * + * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): + * + * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) + * + * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. + * + * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, + * zero for other Skills. + * + * and by adding class, race, specialization bonus. + */ + void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) + { + const ESM::Class *class_ = + MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + + unsigned int level = npcStats.getLevel(); + + const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + + + for (int i = 0; i < 2; ++i) + { + int bonus = (i==0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_->mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); + } + } + } + + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + if (class_->mData.mSkills[k][1] == skillIndex) + { + // Major skill -> add starting spells for this skill if existing + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::Store::iterator it = store.get().begin(); + for (; it != store.get().end(); ++it) + { + if (it->mData.mFlags & ESM::Spell::F_Autocalc + && MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex) + npcStats.getSpells().add(it->mId); + } + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); + if (skill->mData.mSpecialization == class_->mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + npcStats.getSkill(skillIndex).setBase( + std::min( + npcStats.getSkill(skillIndex).getBase() + + 5 + + raceBonus + + specBonus + + static_cast((level-1) * (majorMultiplier + specMultiplier)), 100)); + } + } } namespace MWClass @@ -158,6 +251,11 @@ namespace MWClass fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier"); fJumpRunMultiplier = gmst.find("fJumpRunMultiplier"); fWereWolfRunMult = gmst.find("fWereWolfRunMult"); + fKnockDownMult = gmst.find("fKnockDownMult"); + iKnockDownOddsMult = gmst.find("iKnockDownOddsMult"); + iKnockDownOddsBase = gmst.find("iKnockDownOddsBase"); + fDamageStrengthBase = gmst.find("fDamageStrengthBase"); + fDamageStrengthMult = gmst.find("fDamageStrengthMult"); inited = true; } @@ -172,7 +270,7 @@ namespace MWClass { std::string faction = ref->mBase->mFaction; Misc::StringUtils::toLower(faction); - if(ref->mBase->mNpdt52.mGold != -10) + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank; } @@ -183,19 +281,23 @@ namespace MWClass } // creature stats - if(ref->mBase->mNpdt52.mGold != -10) + int gold=0; + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - for (int i=0; i<27; ++i) + gold = ref->mBase->mNpdt52.mGold; + + for (unsigned int i=0; i< ESM::Skill::Length; ++i) data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); - data->mNpcStats.getAttribute(0).set (ref->mBase->mNpdt52.mStrength); - data->mNpcStats.getAttribute(1).set (ref->mBase->mNpdt52.mIntelligence); - data->mNpcStats.getAttribute(2).set (ref->mBase->mNpdt52.mWillpower); - data->mNpcStats.getAttribute(3).set (ref->mBase->mNpdt52.mAgility); - data->mNpcStats.getAttribute(4).set (ref->mBase->mNpdt52.mSpeed); - data->mNpcStats.getAttribute(5).set (ref->mBase->mNpdt52.mEndurance); - data->mNpcStats.getAttribute(6).set (ref->mBase->mNpdt52.mPersonality); - data->mNpcStats.getAttribute(7).set (ref->mBase->mNpdt52.mLuck); + data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt52.mStrength); + data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt52.mIntelligence); + data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower); + data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility); + data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed); + data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance); + data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality); + data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck); + data->mNpcStats.setHealth (ref->mBase->mNpdt52.mHealth); data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana); data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue); @@ -206,6 +308,8 @@ namespace MWClass } else { + gold = ref->mBase->mNpdt12.mGold; + for (int i=0; i<3; ++i) data->mNpcStats.setDynamic (i, 10); @@ -214,14 +318,34 @@ namespace MWClass data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation); autoCalculateAttributes(ref->mBase, data->mNpcStats); + autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); + } + + // race powers + const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); + for (std::vector::const_iterator iter (race->mPowers.mList.begin()); + iter!=race->mPowers.mList.end(); ++iter) + { + data->mNpcStats.getSpells().add (*iter); + } + + if (data->mNpcStats.getFactionRanks().size()) + { + static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get() + .find("iAutoRepFacMod")->getInt(); + static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get() + .find("iAutoRepLevMod")->getInt(); + int rank = data->mNpcStats.getFactionRanks().begin()->second; + + data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1)); } data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage); - data->mNpcStats.setAiSetting (0, ref->mBase->mAiData.mHello); - data->mNpcStats.setAiSetting (1, ref->mBase->mAiData.mFight); - data->mNpcStats.setAiSetting (2, ref->mBase->mAiData.mFlee); - data->mNpcStats.setAiSetting (3, ref->mBase->mAiData.mAlarm); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -229,12 +353,16 @@ namespace MWClass data->mNpcStats.getSpells().add (*iter); // inventory - data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), + data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "", MWBase::Environment::get().getWorld()->getStore()); // store ptr.getRefData().setCustomData (data.release()); + // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. + // (except for gold you gave him) + getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr); + getInventoryStore(ptr).autoEquip(ptr); } } @@ -254,7 +382,7 @@ namespace MWClass void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - renderingInterface.getActors().insertNPC(ptr, getInventoryStore(ptr)); + renderingInterface.getActors().insertNPC(ptr); } void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const @@ -330,11 +458,28 @@ namespace MWClass if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) weapon = MWWorld::Ptr(); - float dist = 100.0f * (!weapon.isEmpty() ? + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::DynamicStat fatigue = getCreatureStats(ptr).getFatigue(); + const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon.isEmpty()) + fatigueLoss += weapon.getClass().getWeight(weapon) * getNpcStats(ptr).getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + getCreatureStats(ptr).setFatigue(fatigue); + + const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); + float dist = fCombatDistance * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : gmst.find("fHandToHandReach")->getFloat()); - // TODO: Use second to work out the hit angle and where to spawn the blood effect - MWWorld::Ptr victim = world->getHitContact(ptr, dist).first; + + // TODO: Use second to work out the hit angle + std::pair result = world->getHitContact(ptr, dist); + MWWorld::Ptr victim = result.first; + Ogre::Vector3 hitPosition = result.second; if(victim.isEmpty()) // Didn't hit anything return; @@ -358,8 +503,8 @@ namespace MWClass (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude - - mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; + hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - + mageffects.get(ESM::MagicEffect::Blind).mMagnitude; hitchance -= otherstats.getEvasion(); if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) @@ -374,16 +519,17 @@ namespace MWClass { const bool weaphashealth = get(weapon).hasItemHealth(weapon); const unsigned char *attack = NULL; - if(type == MWMechanics::CreatureStats::AT_Chop) + if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; - else if(type == MWMechanics::CreatureStats::AT_Slash) + else if(type == ESM::Weapon::AT_Slash) attack = weapon.get()->mBase->mData.mSlash; - else if(type == MWMechanics::CreatureStats::AT_Thrust) + else if(type == ESM::Weapon::AT_Thrust) attack = weapon.get()->mBase->mData.mThrust; if(attack) { damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); + damage *= fDamageStrengthBase->getFloat() + + (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1); if(weaphashealth) { int weapmaxhealth = weapon.get()->mBase->mData.mHealth; @@ -391,16 +537,15 @@ namespace MWClass weapon.getCellRef().mCharge = weapmaxhealth; damage *= float(weapon.getCellRef().mCharge) / weapmaxhealth; } - if(!othercls.hasDetected(victim, ptr)) - { - damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); - } - + if (!MWBase::Environment::get().getWorld()->getGodModeState()) weapon.getCellRef().mCharge -= std::min(std::max(1, (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge); + + // Weapon broken? unequip it + if (weapon.getCellRef().mCharge == 0) + weapon = *inv.unequipItem(weapon, ptr); + } healthdmg = true; } @@ -413,14 +558,9 @@ namespace MWClass float maxstrike = gmst.find("fMaxHandToHandMult")->getFloat(); damage = stats.getSkill(weapskill).getModified(); damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength()); - if(!othercls.hasDetected(victim, ptr)) - { - damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); - } - healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f); + healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f) + || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); if(stats.isWerewolf()) { healthdmg = true; @@ -444,6 +584,36 @@ namespace MWClass if(ptr.getRefData().getHandle() == "player") skillUsageSucceeded(ptr, weapskill, 0); + bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); + if(!detected) + { + damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } + if (othercls.getCreatureStats(victim).getKnockedDown()) + damage *= gmst.find("fCombatKODamageMult")->getFloat(); + + // Apply "On hit" enchanted weapons + std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(ptr, victim); + cast.mHitPosition = hitPosition; + cast.cast(weapon); + } + } + + if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) + damage = 0; + + if (healthdmg && damage > 0) + MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, true); } @@ -453,6 +623,10 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. + // Attacking peaceful NPCs is a crime + if (!attacker.isEmpty() && ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30) + MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + if(!successful) { // TODO: Handle HitAttemptOnMe script function @@ -467,18 +641,44 @@ namespace MWClass if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") { - const std::string &script = ptr.get()->mBase->mScript; + const std::string &script = ptr.getClass().getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ if(!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } + if (!attacker.isEmpty()) + MWMechanics::diseaseContact(ptr, attacker); + + if (damage > 0.0f && !object.isEmpty()) + MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); + if(damage > 0.0f) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. - MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + int chance = store.get().find("iVoiceHitOdds")->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + { + MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); + } + getCreatureStats(ptr).setAttacked(true); + + // Check for knockdown + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); + float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() + * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt(); + roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + { + getCreatureStats(ptr).setKnockedDown(true); + + } + else + getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? if(object.isEmpty()) { @@ -519,6 +719,14 @@ namespace MWClass armorref.mCharge = armor.get()->mBase->mData.mHealth; armorref.mCharge -= std::min(std::max(1, (int)damagediff), armorref.mCharge); + + // Armor broken? unequip it + if (armorref.mCharge == 0) + inv.unequipItem(armor, ptr); + + if (ptr.getRefData().getHandle() == "player") + skillUsageSucceeded(ptr, get(armor).getEquipmentSkill(armor), 0); + switch(get(armor).getEquipmentSkill(armor)) { case ESM::Skill::LightArmor: @@ -532,6 +740,8 @@ namespace MWClass break; } } + else if(ptr.getRefData().getHandle() == "player") + skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); } } @@ -545,11 +755,35 @@ namespace MWClass else { MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); - fatigue.setCurrent(fatigue.getCurrent() - damage); + fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } } + void Npc::block(const MWWorld::Ptr &ptr) const + { + MWWorld::InventoryStore& inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield == inv.end()) + return; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + switch(shield->getClass().getEquipmentSkill(*shield)) + { + case ESM::Skill::LightArmor: + sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::MediumArmor: + sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::HeavyArmor: + sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); + break; + default: + return; + } + } + void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const { MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); @@ -585,7 +819,9 @@ namespace MWClass } if(getCreatureStats(ptr).isDead()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); - if(get(actor).getStance(actor, MWWorld::Class::Sneak)) + if(get(ptr).getCreatureStats(ptr).isHostile()) + return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); + if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); } @@ -614,80 +850,6 @@ namespace MWClass return ref->mBase->mScript; } - void Npc::setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const - { - MWMechanics::NpcStats& stats = getNpcStats (ptr); - - switch (stance) - { - case Run: - - stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceRun, force); - break; - - case Sneak: - - stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak, force); - break; - - case Combat: - - throw std::runtime_error ("combat stance not enforcable for NPCs"); - } - } - - void Npc::setStance (const MWWorld::Ptr& ptr, Stance stance, bool set) const - { - MWMechanics::NpcStats& stats = getNpcStats (ptr); - - switch (stance) - { - case Run: - - stats.setMovementFlag (MWMechanics::NpcStats::Flag_Run, set); - break; - - case Sneak: - - stats.setMovementFlag (MWMechanics::NpcStats::Flag_Sneak, set); - break; - - case Combat: - - // Combat stance ignored for now; need to be determined based on draw state instead of - // being maunally set. - break; - } - } - - bool Npc::getStance (const MWWorld::Ptr& ptr, Stance stance, bool ignoreForce) const - { - MWMechanics::NpcStats& stats = getNpcStats (ptr); - - switch (stance) - { - case Run: - - if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)) - return true; - - return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Run); - - case Sneak: - - if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)) - return true; - - return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Sneak); - - case Combat: - - return false; - } - - return false; - } - float Npc::getSpeed(const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -696,11 +858,14 @@ namespace MWClass const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr); + bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); + float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.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)) + if(sneaking) walkSpeed *= fSneakSpeedMultiplier->getFloat(); float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * @@ -711,10 +876,11 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0) + else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + world->isLevitationEnabled()) { float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + - mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude); + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); @@ -723,14 +889,14 @@ namespace MWClass else if(world->isSwimming(ptr)) { float swimSpeed = walkSpeed; - if(Npc::getStance(ptr, Run, false)) + if(running) swimSpeed = runSpeed; - swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude; + swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).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)) + else if(running && !sneaking) moveSpeed = runSpeed; else moveSpeed = walkSpeed; @@ -759,10 +925,10 @@ namespace MWClass float x = fJumpAcrobaticsBase->getFloat() + std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat()); x += 3.0f * b * fJumpAcroMultiplier->getFloat(); - x += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude * 64; + x += mageffects.get(ESM::MagicEffect::Jump).mMagnitude * 64; x *= encumbranceTerm; - if(Npc::getStance(ptr, Run, false)) + if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) x *= fJumpRunMultiplier->getFloat(); x *= npcdata->mNpcStats.getFatigueTerm(); x -= -627.2f;/*gravity constant*/ @@ -782,7 +948,7 @@ namespace MWClass { const float acrobaticsSkill = MWWorld::Class::get(ptr).getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); - const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude; + const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude; const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat(); const float fallAcroMult = gmst.find("fFallAcroMult")->getFloat(); const float fallDistanceBase = gmst.find("fFallDistanceBase")->getFloat(); @@ -836,7 +1002,7 @@ namespace MWClass return ref->mBase->mFlags & ESM::NPC::Essential; } - + void Npc::registerSelf() { boost::shared_ptr instance (new Npc); @@ -874,7 +1040,8 @@ namespace MWClass float Npc::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - return stats.getAttribute(0).getModified()*5; + static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEncumbranceStrMult")->getFloat(); + return stats.getAttribute(0).getModified()*fEncumbranceStrMult; } float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const @@ -887,8 +1054,8 @@ namespace MWClass if(!stats.isWerewolf()) { weight = getContainerStore(ptr).getWeight(); - weight -= stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).mMagnitude; - weight += stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).mMagnitude; + weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).mMagnitude; + weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).mMagnitude; if(weight < 0.0f) weight = 0.0f; } @@ -899,11 +1066,8 @@ namespace MWClass bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const { - MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - - /// \todo consider instant effects - - return stats.getActiveSpells().addSpell (id, actor); + MWMechanics::CastSpell cast(ptr, ptr); + return cast.cast(id); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const @@ -1091,6 +1255,44 @@ namespace MWClass return MWWorld::Ptr(&cell.mNpcs.insert(*ref), &cell); } + int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const + { + return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified(); + } + + int Npc::getBloodTexture(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + if (ref->mBase->mFlags & ESM::NPC::Skeleton) + return 1; + if (ref->mBase->mFlags & ESM::NPC::Metal) + return 2; + return 0; + } + + void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::NpcState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore. + readState (state2.mInventory); + } + + void Npc::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::NpcState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore. + writeState (state2.mInventory); + } + const ESM::GameSetting *Npc::fMinWalkSpeed; const ESM::GameSetting *Npc::fMaxWalkSpeed; const ESM::GameSetting *Npc::fEncumberedMoveEffect; @@ -1107,4 +1309,10 @@ namespace MWClass const ESM::GameSetting *Npc::fJumpAcroMultiplier; const ESM::GameSetting *Npc::fJumpRunMultiplier; const ESM::GameSetting *Npc::fWereWolfRunMult; + const ESM::GameSetting *Npc::fKnockDownMult; + const ESM::GameSetting *Npc::iKnockDownOddsMult; + const ESM::GameSetting *Npc::iKnockDownOddsBase; + const ESM::GameSetting *Npc::fDamageStrengthBase; + const ESM::GameSetting *Npc::fDamageStrengthMult; + } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index c39ca42ef..c54dd339a 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -33,6 +33,11 @@ namespace MWClass static const ESM::GameSetting *fJumpAcroMultiplier; static const ESM::GameSetting *fJumpRunMultiplier; static const ESM::GameSetting *fWereWolfRunMult; + static const ESM::GameSetting *fKnockDownMult; + static const ESM::GameSetting *iKnockDownOddsMult; + static const ESM::GameSetting *iKnockDownOddsBase; + static const ESM::GameSetting *fDamageStrengthBase; + static const ESM::GameSetting *fDamageStrengthMult; public: @@ -68,10 +73,14 @@ namespace MWClass virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; ///< Return inventory store + virtual bool hasInventoryStore(const MWWorld::Ptr &ptr) const { return true; } + virtual void hit(const MWWorld::Ptr& ptr, int type) const; virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + virtual void block(const MWWorld::Ptr &ptr) const; + virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, @@ -81,16 +90,6 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr - virtual void setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const; - ///< Force or unforce a stance. - - virtual void setStance (const MWWorld::Ptr& ptr, Stance stance, bool set) const; - ///< Set or unset a stance. - - virtual bool getStance (const MWWorld::Ptr& ptr, Stance stance, bool ignoreForce = false) - const; - ///< Check if a stance is active or not. - virtual float getSpeed (const MWWorld::Ptr& ptr) const; ///< Return movement speed. @@ -138,7 +137,7 @@ namespace MWClass ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) virtual int getServices (const MWWorld::Ptr& actor) const; - + virtual bool isPersistent (const MWWorld::Ptr& ptr) const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; @@ -147,6 +146,11 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; + + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) + virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual bool isActor() const { return true; } @@ -154,6 +158,14 @@ namespace MWClass virtual bool isNpc() const { return true; } + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 08683a668..d68db4e45 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -11,8 +11,8 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/physicssystem.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -127,22 +127,23 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{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(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); int i=0; + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); 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)); + it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue*2) + || (i == 2 && alchemySkill >= fWortChanceValue*3) + || (i == 3 && alchemySkill >= fWortChanceValue*4)); ++i; } @@ -151,6 +152,7 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -164,9 +166,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - ptr.getRefData().setCount (ptr.getRefData().getCount()-1); + MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + // remove used potion (assume it is present in inventory) + ptr.getContainerStore()->remove(ptr, 1, actor); boost::shared_ptr action ( new MWWorld::ActionApply (actor, ref->mBase->mId)); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 951265f40..4209c1431 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -73,11 +73,11 @@ namespace MWClass std::pair, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const { - std::vector slots; + std::vector slots_; - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Probe::getValue (const MWWorld::Ptr& ptr) const @@ -85,7 +85,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Probe::registerSelf() @@ -137,10 +140,11 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 38c15ac92..5f2065c3c 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -76,7 +76,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Repair::registerSelf() @@ -141,10 +144,11 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 4cb090328..af0234cd5 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -100,23 +100,23 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; bool stack = false; if (ref->mBase->mData.mType==ESM::Weapon::Arrow || ref->mBase->mData.mType==ESM::Weapon::Bolt) { - slots.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); stack = true; } else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown) { - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); stack = true; } else - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots, stack); + return std::make_pair (slots_, stack); } int Weapon::getEquipmentSkill (const MWWorld::Ptr& ptr) const @@ -154,7 +154,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Weapon::registerSelf() @@ -343,7 +346,7 @@ namespace MWClass } text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.enchant = ref->mBase->mEnchant; @@ -352,6 +355,7 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -370,42 +374,41 @@ namespace MWClass void Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - ESM::Weapon newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; - const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; + MWWorld::LiveCellRef *ref = + ptr.get(); + + ESM::Weapon newItem = *ref->mBase; + newItem.mId=""; + newItem.mName=newName; + newItem.mData.mEnchant=enchCharge; + newItem.mEnchant=enchId; + const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { - std::pair, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + if (ptr.getCellRef().mCharge == 0) + return std::make_pair(0, "#{sInventoryMessage1}"); + + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); - // equip the item in the first free slot - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + if (slots_.first.empty()) + return std::make_pair (0, ""); + + if(ptr.get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || + ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || + ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || + ptr.get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || + ptr.get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || + ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || + ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { - if(*slot == MWWorld::InventoryStore::Slot_CarriedRight) - { - if(ptr.get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) - { - return std::make_pair (2, ""); - } - } - return std::make_pair(1, ""); + return std::make_pair (2, ""); } - return std::make_pair (0, ""); + + return std::make_pair(1, ""); } boost::shared_ptr Weapon::use (const MWWorld::Ptr& ptr) const @@ -426,12 +429,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mWeapons.insert(*ref), &cell); } - float Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 181c637db..db44cd2b7 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -84,7 +84,7 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 52493bf76..845c3c07b 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -30,7 +30,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/player.hpp" #include "../mwgui/dialogue.hpp" @@ -126,6 +125,8 @@ namespace MWDialogue void DialogueManager::startDialogue (const MWWorld::Ptr& actor) { mLastTopic = ""; + mPermanentDispositionChange = 0; + mTemporaryDispositionChange = 0; mChoice = -1; mIsInChoice = false; @@ -142,6 +143,7 @@ namespace MWDialogue //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); + updateGlobals(); //greeting const MWWorld::Store &dialogs = @@ -251,7 +253,7 @@ namespace MWDialogue } } - void DialogueManager::executeTopic (const std::string& topic, bool randomResponse) + void DialogueManager::executeTopic (const std::string& topic) { Filter filter (mActor, mChoice, mTalkedTo); @@ -262,12 +264,9 @@ namespace MWDialogue MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - std::vector infos = filter.list (dialogue, true, true); - - if (!infos.empty()) + const ESM::DialInfo* info = filter.search(dialogue, true); + if (info) { - const ESM::DialInfo* info = infos[randomResponse ? std::rand() % infos.size() : 0]; - parseText (info->mResponse); std::string title; @@ -287,7 +286,7 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId); + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); executeScript (info->mResultScript); @@ -300,6 +299,11 @@ namespace MWDialogue } } + void DialogueManager::updateGlobals() + { + MWBase::Environment::get().getWorld()->updateDialogueGlobals(); + } + void DialogueManager::updateTopics() { std::list keywordList; @@ -413,14 +417,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 - if (mActor.getTypeName() == typeid(ESM::NPC).name()) + if (mActor.getClass().isNpc()) { MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor); npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange); @@ -451,7 +451,7 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext)); - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); executeScript (info->mResultScript); } } @@ -474,6 +474,8 @@ namespace MWDialogue void DialogueManager::goodbye() { + mIsInChoice = true; + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->goodbye(); @@ -496,7 +498,7 @@ namespace MWDialogue else if (curDisp + mTemporaryDispositionChange > 100) mTemporaryDispositionChange = 100 - curDisp; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); std::string text; @@ -511,7 +513,7 @@ namespace MWDialogue text = "Bribe"; } - executeTopic (text + (success ? " Success" : " Fail"), true); + executeTopic (text + (success ? " Success" : " Fail")); } int DialogueManager::getTemporaryDispositionChange() const @@ -519,9 +521,19 @@ namespace MWDialogue return mTemporaryDispositionChange; } - void DialogueManager::applyTemporaryDispositionChange(int delta) + void DialogueManager::applyDispositionChange(int delta) { + int oldTemp = mTemporaryDispositionChange; mTemporaryDispositionChange += delta; + // don't allow increasing beyond 100 or decreasing below 0 + int curDisp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor); + if (curDisp + mTemporaryDispositionChange < 0) + mTemporaryDispositionChange = -curDisp; + else if (curDisp + mTemporaryDispositionChange > 100) + mTemporaryDispositionChange = 100 - curDisp; + + int diff = mTemporaryDispositionChange - oldTemp; + mPermanentDispositionChange += diff; } bool DialogueManager::checkServiceRefused() @@ -574,7 +586,8 @@ namespace MWDialogue MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); if(winMgr->getSubtitlesEnabled()) winMgr->messageBox(info->mResponse); - sndMgr->say(actor, info->mSound); + if (!info->mSound.empty()) + sndMgr->say(actor, info->mSound); } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 1b7abed45..c32a5dbd8 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -40,11 +40,12 @@ namespace MWDialogue void parseText (const std::string& text); void updateTopics(); + void updateGlobals(); bool compile (const std::string& cmd,std::vector& code); void executeScript (const std::string& script); - void executeTopic (const std::string& topic, bool randomResponse=false); + void executeTopic (const std::string& topic); public: @@ -76,7 +77,7 @@ namespace MWDialogue virtual void persuade (int type); virtual int getTemporaryDispositionChange () const; - virtual void applyTemporaryDispositionChange (int delta); + virtual void applyDispositionChange (int delta); }; diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index a7b0f1924..3d45b85ce 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -5,9 +5,9 @@ #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -24,7 +24,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const // actor id if (!info.mActor.empty()) { - if ( Misc::StringUtils::lowerCase (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor)) + if ( !Misc::StringUtils::ciEqual(info.mActor, MWWorld::Class::get (mActor).getId (mActor))) return false; } else if (isCreature) @@ -41,7 +41,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const MWWorld::LiveCellRef *cellRef = mActor.get(); - if (Misc::StringUtils::lowerCase (info.mRace)!= Misc::StringUtils::lowerCase (cellRef->mBase->mRace)) + if (!Misc::StringUtils::ciEqual(info.mRace, cellRef->mBase->mRace)) return false; } @@ -53,18 +53,18 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const MWWorld::LiveCellRef *cellRef = mActor.get(); - if ( Misc::StringUtils::lowerCase (info.mClass)!= Misc::StringUtils::lowerCase (cellRef->mBase->mClass)) + if ( !Misc::StringUtils::ciEqual(info.mClass, cellRef->mBase->mClass)) return false; } // NPC faction - if (!info.mNpcFaction.empty()) + if (!info.mFaction.empty()) { if (isCreature) return false; MWMechanics::NpcStats& stats = MWWorld::Class::get (mActor).getNpcStats (mActor); - std::map::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mNpcFaction)); + std::map::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mFaction)); if (iter==stats.getFactionRanks().end()) return false; @@ -92,7 +92,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const { - const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // check player faction if (!info.mPcFaction.empty()) @@ -110,7 +110,7 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const // check cell if (!info.mCell.empty()) - if (Misc::StringUtils::lowerCase (player.getCell()->mCell->mName) != Misc::StringUtils::lowerCase (info.mCell)) + if (!Misc::StringUtils::ciEqual(player.getCell()->mCell->mName, info.mCell)) return false; return true; @@ -133,7 +133,8 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert if (isCreature) return true; - int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor); + int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor) + + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(); // For service refusal, the disposition check is inverted. However, a value of 0 still means "always succeed". return invert ? (info.mData.mDisposition == 0 || actorDisposition < info.mData.mDisposition) : (actorDisposition >= info.mData.mDisposition); @@ -170,7 +171,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c // internally all globals are float :( return select.selectCompare ( - MWBase::Environment::get().getWorld()->getGlobalVariable (select.getName()).mFloat); + MWBase::Environment::get().getWorld()->getGlobalFloat (select.getName())); case SelectWrapper::Function_Local: { @@ -187,7 +188,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c int i = 0; for (; i (script->mVarNames.size()); ++i) - if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name) + if (Misc::StringUtils::ciEqual(script->mVarNames[i], name)) break; if (i>=static_cast (script->mVarNames.size())) @@ -210,17 +211,17 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() / MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified(); - return select.selectCompare (ratio); + return select.selectCompare (static_cast(ratio*100)); } case SelectWrapper::Function_PcDynamicStat: { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); float value = MWWorld::Class::get (player).getCreatureStats (player). getDynamic (select.getArgument()).getCurrent(); @@ -244,7 +245,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); switch (select.getFunction()) { @@ -261,7 +262,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 (Misc::StringUtils::lowerCase(iter->getCellRef().mRefID) == name) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, name)) sum += iter->getRefData().getCount(); return sum; @@ -277,7 +278,8 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting (select.getArgument()); + return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting ( + (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); case SelectWrapper::Function_PcAttribute: @@ -417,7 +419,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); switch (select.getFunction()) { @@ -427,23 +429,23 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotId: - return select.getName()!=Misc::StringUtils::lowerCase (MWWorld::Class::get (mActor).getId (mActor)); + return !Misc::StringUtils::ciEqual(MWWorld::Class::get (mActor).getId (mActor), select.getName()); case SelectWrapper::Function_NotFaction: - return Misc::StringUtils::lowerCase (mActor.get()->mBase->mFaction)!=select.getName(); + return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mFaction, select.getName()); case SelectWrapper::Function_NotClass: - return Misc::StringUtils::lowerCase (mActor.get()->mBase->mClass)!=select.getName(); + return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mClass, select.getName()); case SelectWrapper::Function_NotRace: - return Misc::StringUtils::lowerCase (mActor.get()->mBase->mRace)!=select.getName(); + return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, select.getName()); case SelectWrapper::Function_NotCell: - return Misc::StringUtils::lowerCase (mActor.getCell()->mCell->mName)!=select.getName(); + return !Misc::StringUtils::ciEqual(mActor.getCell()->mCell->mName, select.getName()); case SelectWrapper::Function_NotLocal: { @@ -460,7 +462,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co int i = 0; for (; i < static_cast (script->mVarNames.size()); ++i) - if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name) + if (Misc::StringUtils::ciEqual(script->mVarNames[i], name)) break; if (i >= static_cast (script->mVarNames.size())) @@ -476,8 +478,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_SameRace: - return Misc::StringUtils::lowerCase (mActor.get()->mBase->mRace)!= - Misc::StringUtils::lowerCase (player.get()->mBase->mRace); + return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); case SelectWrapper::Function_SameFaction: @@ -505,14 +506,13 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co std::string faction = MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first; - std::set& expelled = MWWorld::Class::get (player).getNpcStats (player).getExpelled(); - - return expelled.find (faction)!=expelled.end(); + return player.getClass().getNpcStats(player).getExpelled(faction); } case SelectWrapper::Function_PcVampire: - return MWWorld::Class::get (player).getNpcStats (player).isVampire(); + return MWWorld::Class::get (player).getCreatureStats(player).getMagicEffects(). + get(ESM::MagicEffect::Vampirism).mMagnitude > 0; case SelectWrapper::Function_TalkedToPc: @@ -524,7 +524,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Detected: - return MWWorld::Class::get (mActor).hasDetected (mActor, player); + return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor); case SelectWrapper::Function_Attacked: @@ -536,7 +536,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_CreatureTargetted: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).getCreatureTargetted(); + return mActor.getClass().getCreatureStats (mActor).getCreatureTargetted(); case SelectWrapper::Function_Werewolf: diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 5ffde5499..9463e4c45 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -3,6 +3,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -10,23 +12,55 @@ namespace MWDialogue { - JournalEntry::JournalEntry() {} - - JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) - : mTopic (topic), mInfoId (infoId) - {} + Entry::Entry() {} - std::string JournalEntry::getText (const MWWorld::ESMStore& store) const + Entry::Entry (const std::string& topic, const std::string& infoId) + : mInfoId (infoId) { const ESM::Dialogue *dialogue = - store.get().find (mTopic); + MWBase::Environment::get().getWorld()->getStore().get().find (topic); for (std::vector::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) - return iter->mResponse; + { + /// \todo text replacement + mText = iter->mResponse; + return; + } + + throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + topic); + } + + Entry::Entry (const ESM::JournalEntry& record) : mInfoId (record.mInfo), mText (record.mText), mActorName(record.mActorName) {} + + std::string Entry::getText() const + { + return mText; + } + + void Entry::write (ESM::JournalEntry& entry) const + { + entry.mInfo = mInfoId; + entry.mText = mText; + entry.mActorName = mActorName; + } + + + JournalEntry::JournalEntry() {} + + JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) + : Entry (topic, infoId), mTopic (topic) + {} + + JournalEntry::JournalEntry (const ESM::JournalEntry& record) + : Entry (record), mTopic (record.mTopic) + {} - throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + mTopic); + void JournalEntry::write (ESM::JournalEntry& entry) const + { + Entry::write (entry); + entry.mTopic = mTopic; } JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) @@ -49,6 +83,7 @@ namespace MWDialogue throw std::runtime_error ("unknown journal index for topic " + topic); } + StampedJournalEntry::StampedJournalEntry() : mDay (0), mMonth (0), mDayOfMonth (0) {} @@ -58,11 +93,24 @@ namespace MWDialogue : JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} + StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) + : JournalEntry (record), mDay (record.mDay), mMonth (record.mMonth), + mDayOfMonth (record.mDayOfMonth) + {} + + void StampedJournalEntry::write (ESM::JournalEntry& entry) const + { + JournalEntry::write (entry); + entry.mDay = mDay; + entry.mMonth = mMonth; + entry.mDayOfMonth = mDayOfMonth; + } + 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 ("month").mLong; - int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong; + int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed"); + int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month"); + int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day"); return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth); } diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index 9d009b48b..a77ba4f7c 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -3,24 +3,45 @@ #include -namespace MWWorld +namespace ESM { - struct ESMStore; + struct JournalEntry; } namespace MWDialogue { - /// \brief A quest or dialogue entry - struct JournalEntry + /// \brief Basic quest/dialogue/topic entry + struct Entry { - std::string mTopic; std::string mInfoId; + std::string mText; + std::string mActorName; // optional + + Entry(); + + Entry (const std::string& topic, const std::string& infoId); + + Entry (const ESM::JournalEntry& record); + + std::string getText() const; + + void write (ESM::JournalEntry& entry) const; + }; + + /// \brief A dialogue entry + /// + /// Same as entry, but store TopicID + struct JournalEntry : public Entry + { + std::string mTopic; JournalEntry(); JournalEntry (const std::string& topic, const std::string& infoId); - std::string getText (const MWWorld::ESMStore& store) const; + JournalEntry (const ESM::JournalEntry& record); + + void write (ESM::JournalEntry& entry) const; static JournalEntry makeFromQuest (const std::string& topic, int index); @@ -39,6 +60,10 @@ namespace MWDialogue StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth); + StampedJournalEntry (const ESM::JournalEntry& record); + + void write (ESM::JournalEntry& entry) const; + static StampedJournalEntry makeFromQuest (const std::string& topic, int index); }; } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 23cfb5fdd..26383b3a7 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -1,6 +1,13 @@ #include "journalimp.hpp" +#include + +#include +#include +#include +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -26,6 +33,38 @@ namespace MWDialogue return iter->second; } + Topic& Journal::getTopic (const std::string& id) + { + TTopicContainer::iterator iter = mTopics.find (id); + + if (iter==mTopics.end()) + { + std::pair result + = mTopics.insert (std::make_pair (id, Topic (id))); + + iter = result.first; + } + + return iter->second; + } + + bool Journal::isThere (const std::string& topicId, const std::string& infoId) const + { + if (const ESM::Dialogue *dialogue = + MWBase::Environment::get().getWorld()->getStore().get().search (topicId)) + { + if (infoId.empty()) + return true; + + for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + iter!=dialogue->mInfo.end(); ++iter) + if (iter->mId == infoId) + return true; + } + + return false; + } + Journal::Journal() {} @@ -64,19 +103,13 @@ namespace MWDialogue quest.setIndex (index); } - void Journal::addTopic (const std::string& topicId, const std::string& infoId) + void Journal::addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) { - TTopicContainer::iterator iter = mTopics.find (topicId); - - if (iter==mTopics.end()) - { - std::pair result - = mTopics.insert (std::make_pair (topicId, Topic (topicId))); - - iter = result.first; - } + Topic& topic = getTopic (topicId); - iter->second.addEntry (JournalEntry (topicId, infoId)); + JournalEntry entry(topicId, infoId); + entry.mActorName = actorName; + topic.addEntry (entry); } int Journal::getJournalIndex (const std::string& id) const @@ -118,4 +151,106 @@ namespace MWDialogue { return mTopics.end(); } + + int Journal::countSavedGameRecords() const + { + int count = static_cast (mQuests.size()); + + for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) + count += std::distance (iter->second.begin(), iter->second.end()); + + count += std::distance (mJournal.begin(), mJournal.end()); + + for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) + count += std::distance (iter->second.begin(), iter->second.end()); + + return count; + } + + void Journal::write (ESM::ESMWriter& writer) const + { + for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) + { + const Quest& quest = iter->second; + + ESM::QuestState state; + quest.write (state); + writer.startRecord (ESM::REC_QUES); + state.save (writer); + writer.endRecord (ESM::REC_QUES); + + for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter) + { + ESM::JournalEntry entry; + entry.mType = ESM::JournalEntry::Type_Quest; + entry.mTopic = quest.getTopic(); + iter->write (entry); + writer.startRecord (ESM::REC_JOUR); + entry.save (writer); + writer.endRecord (ESM::REC_JOUR); + } + } + + for (TEntryIter iter (mJournal.begin()); iter!=mJournal.end(); ++iter) + { + ESM::JournalEntry entry; + entry.mType = ESM::JournalEntry::Type_Journal; + iter->write (entry); + writer.startRecord (ESM::REC_JOUR); + entry.save (writer); + writer.endRecord (ESM::REC_JOUR); + } + + for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) + { + const Topic& topic = iter->second; + + for (Topic::TEntryIter iter (topic.begin()); iter!=topic.end(); ++iter) + { + ESM::JournalEntry entry; + entry.mType = ESM::JournalEntry::Type_Topic; + entry.mTopic = topic.getTopic(); + iter->write (entry); + writer.startRecord (ESM::REC_JOUR); + entry.save (writer); + writer.endRecord (ESM::REC_JOUR); + } + } + } + + void Journal::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type==ESM::REC_JOUR) + { + ESM::JournalEntry record; + record.load (reader); + + if (isThere (record.mTopic, record.mInfo)) + switch (record.mType) + { + case ESM::JournalEntry::Type_Quest: + + getQuest (record.mTopic).insertEntry (record); + break; + + case ESM::JournalEntry::Type_Journal: + + mJournal.push_back (record); + break; + + case ESM::JournalEntry::Type_Topic: + + getTopic (record.mTopic).insertEntry (record); + break; + } + } + else if (type==ESM::REC_QUES) + { + ESM::QuestState record; + record.load (reader); + + if (isThere (record.mTopic)) + mQuests.insert (std::make_pair (record.mTopic, record)); + } + } } diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index f4f8eb1c2..1b4803ba2 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -15,8 +15,14 @@ namespace MWDialogue TQuestContainer mQuests; TTopicContainer mTopics; + private: + Quest& getQuest (const std::string& id); + Topic& getTopic (const std::string& id); + + bool isThere (const std::string& topicId, const std::string& infoId = "") const; + public: Journal(); @@ -32,7 +38,7 @@ namespace MWDialogue virtual int getJournalIndex (const std::string& id) const; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId); + virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName); virtual TEntryIter begin() const; ///< Iterator pointing to the begin of the main journal. @@ -55,6 +61,12 @@ namespace MWDialogue virtual TTopicIter topicEnd() const; ///< Iterator pointing past the last topic. + + virtual int countSavedGameRecords() const; + + virtual void write (ESM::ESMWriter& writer) const; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type); }; } diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 5e2739be1..a5411b747 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,6 +1,8 @@ #include "quest.hpp" +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -16,7 +18,11 @@ namespace MWDialogue : Topic (topic), mIndex (0), mFinished (false) {} - const std::string Quest::getName() const + Quest::Quest (const ESM::QuestState& state) + : Topic (state.mTopic), mIndex (state.mState), mFinished (state.mFinished!=0) + {} + + std::string Quest::getName() const { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); @@ -39,21 +45,24 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); + bool found=false; for (std::vector::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name) { - mIndex = index; - if (iter->mQuestStatus==ESM::DialInfo::QS_Finished) mFinished = true; else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart) mFinished = false; - return; + found = true; + // Don't return here. Quest status may actually be in a different info record, since we don't merge these (yet?) } - throw std::runtime_error ("unknown journal index for topic " + mTopic); + if (found) + mIndex = index; + else + throw std::runtime_error ("unknown journal index for topic " + mTopic); } bool Quest::isFinished() const @@ -79,12 +88,20 @@ namespace MWDialogue if (index==-1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); - setIndex (index); + if (index > mIndex) + setIndex (index); for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) - if (*iter==entry.mInfoId) + if (iter->mInfoId==entry.mInfoId) return; - mEntries.push_back (entry.mInfoId); + mEntries.push_back (entry); // we want slicing here + } + + void Quest::write (ESM::QuestState& state) const + { + state.mTopic = getTopic(); + state.mState = mIndex; + state.mFinished = mFinished; } } diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp index 3afa81fac..40824f398 100644 --- a/apps/openmw/mwdialogue/quest.hpp +++ b/apps/openmw/mwdialogue/quest.hpp @@ -3,9 +3,14 @@ #include "topic.hpp" +namespace ESM +{ + struct QuestState; +} + namespace MWDialogue { - /// \brief A quest in progress or a compelted quest + /// \brief A quest in progress or a completed quest class Quest : public Topic { int mIndex; @@ -17,13 +22,15 @@ namespace MWDialogue Quest (const std::string& topic); - const std::string getName() const; + Quest (const ESM::QuestState& state); + + virtual std::string getName() const; ///< May be an empty string int getIndex() const; void setIndex (int index); - ///< Calling this function with a non-existant index while throw an exception. + ///< Calling this function with a non-existent index will throw an exception. bool isFinished() const; @@ -31,6 +38,8 @@ namespace MWDialogue ///< Add entry and adjust index accordingly. /// /// \note Redundant entries are ignored, but the index is still adjusted. + + void write (ESM::QuestState& state) const; }; } diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index 3253b20d6..f7df305c7 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -1,6 +1,9 @@ #include "topic.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "../mwworld/esmstore.hpp" namespace MWDialogue @@ -9,7 +12,8 @@ namespace MWDialogue {} Topic::Topic (const std::string& topic) - : mTopic (topic) + : mTopic (topic), mName ( + MWBase::Environment::get().getWorld()->getStore().get().find (topic)->mId) {} Topic::~Topic() @@ -20,11 +24,29 @@ namespace MWDialogue if (entry.mTopic!=mTopic) throw std::runtime_error ("topic does not match: " + mTopic); - for (TEntryIter iter = begin(); iter!=end(); ++iter) - if (*iter==entry.mInfoId) + // bail out if we already have heard this + for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it) + { + if (it->mInfoId == entry.mInfoId) return; + } + + mEntries.push_back (entry); // we want slicing here + } + + void Topic::insertEntry (const ESM::JournalEntry& entry) + { + mEntries.push_back (entry); + } - mEntries.push_back (entry.mInfoId); + std::string Topic::getTopic() const + { + return mTopic; + } + + std::string Topic::getName() const + { + return mName; } Topic::TEntryIter Topic::begin() const diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index c3f0baabc..02fa6d524 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -6,6 +6,11 @@ #include "journalentry.hpp" +namespace ESM +{ + struct JournalEntry; +} + namespace MWDialogue { /// \brief Collection of seen responses for a topic @@ -13,13 +18,14 @@ namespace MWDialogue { public: - typedef std::vector TEntryContainer; + typedef std::vector TEntryContainer; typedef TEntryContainer::const_iterator TEntryIter; protected: std::string mTopic; - TEntryContainer mEntries; // info-IDs + std::string mName; + TEntryContainer mEntries; public: @@ -34,7 +40,13 @@ namespace MWDialogue /// /// \note Redundant entries are ignored. - std::string const & getName () const { return mTopic; } + void insertEntry (const ESM::JournalEntry& entry); + ///< Add entry without checking for redundant entries or modifying the state of the + /// topic otherwise + + std::string getTopic() const; + + virtual std::string getName() const; TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 1e203dcd0..ddbd3f120 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -8,7 +8,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "inventoryitemmodel.hpp" @@ -47,8 +46,6 @@ namespace MWGui , mIngredients (4) , mSortModel(NULL) { - mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mIngredients[0], "Ingredient1"); @@ -145,7 +142,9 @@ namespace MWGui void AlchemyWindow::open() { - InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); + + InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr()); mSortModel = new SortFilterItemModel(model); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mItemView->setModel (mSortModel); @@ -154,7 +153,7 @@ namespace MWGui int index = 0; - mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools()); iter!=mAlchemy.endTools() && index (mApparatus.size()); ++iter, ++index) diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 965606709..7f58309ba 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -1,6 +1,5 @@ #include "birth.hpp" -#include #include #include "../mwbase/environment.hpp" @@ -34,8 +33,7 @@ namespace MWGui getWidget(mBirthList, "BirthsignList"); mBirthList->setScrollVisible(true); - mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); - mBirthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); + mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onAccept); mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); MyGUI::Button* backButton; @@ -77,7 +75,7 @@ namespace MWGui size_t count = mBirthList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (boost::iequals(*mBirthList->getItemDataAt(i), birthId)) + if (Misc::StringUtils::ciEqual(*mBirthList->getItemDataAt(i), birthId)) { mBirthList->setIndexSelected(i); MyGUI::Button* okButton; @@ -98,6 +96,14 @@ namespace MWGui eventDone(this); } + void BirthDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) + { + onSelectBirth(_sender, _index); + if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) + return; + eventDone(this); + } + void BirthDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); @@ -112,7 +118,7 @@ namespace MWGui getWidget(okButton, "OKButton"); const std::string *birthId = mBirthList->getItemDataAt(_index); - if (boost::iequals(mCurrentBirthId, *birthId)) + if (Misc::StringUtils::ciEqual(mCurrentBirthId, *birthId)) return; mCurrentBirthId = *birthId; @@ -148,7 +154,7 @@ namespace MWGui mBirthList->setIndexSelected(index); mCurrentBirthId = it2->first; } - else if (boost::iequals(it2->first, mCurrentBirthId)) + else if (Misc::StringUtils::ciEqual(it2->first, mCurrentBirthId)) { mBirthList->setIndexSelected(index); } diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index cc958ddca..20a64c78c 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -38,6 +38,7 @@ namespace MWGui protected: void onSelectBirth(MyGUI::ListBox* _sender, size_t _index); + void onAccept(MyGUI::ListBox* _sender, size_t index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index efe089689..98d963b22 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -8,7 +8,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/actiontake.hpp" -#include "../mwworld/player.hpp" #include "formatting.hpp" @@ -44,6 +43,11 @@ namespace MWGui adjustButton(mNextPageButton); adjustButton(mPrevPageButton); + mLeftPage->setNeedMouseFocus(true); + mLeftPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); + mRightPage->setNeedMouseFocus(true); + mRightPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); + if (mNextPageButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge @@ -54,6 +58,14 @@ namespace MWGui center(); } + void BookWindow::onMouseWheel(MyGUI::Widget *_sender, int _rel) + { + if (_rel < 0) + nextPage(); + else + prevPage(); + } + void BookWindow::clearPages() { for (std::vector::iterator it=mPages.begin(); @@ -89,6 +101,7 @@ namespace MWGui parent = mRightPage; MyGUI::Widget* pageWidget = parent->createWidgetReal("", MyGUI::FloatCoord(0.0,0.0,1.0,1.0), MyGUI::Align::Default, "BookPage" + boost::lexical_cast(i)); + pageWidget->setNeedMouseFocus(false); parser.parsePage(*it, pageWidget, mLeftPage->getSize().width); mPages.push_back(pageWidget); ++i; @@ -124,7 +137,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0); MWWorld::ActionTake take(mBook); - take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + take.execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index ef87dd9c4..f8821ab50 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -25,6 +25,7 @@ namespace MWGui void onPrevPageButtonClicked (MyGUI::Widget* sender); void onCloseButtonClicked (MyGUI::Widget* sender); void onTakeButtonClicked (MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); void updatePages(); void clearPages(); diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 816f42e3d..5526bd26d 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -10,10 +10,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" -#include "../mwworld/player.hpp" namespace { @@ -47,10 +46,9 @@ namespace void updatePlayerHealth() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats(player); - - creatureStats.updateHealth(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player); + npcStats.updateHealth(); } } @@ -75,7 +73,7 @@ namespace MWGui mGenerateClassSpecializations[2] = 0; } - void CharacterCreation::setValue (const std::string& id, const MWMechanics::Stat& value) + void CharacterCreation::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { if (mReviewDialog) { @@ -113,7 +111,7 @@ namespace MWGui } } - void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value) + void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { if (mReviewDialog) mReviewDialog->setSkillValue(parSkill, value); @@ -219,13 +217,18 @@ namespace MWGui mReviewDialog->setClass(mPlayerClass); mReviewDialog->setBirthSign(mPlayerBirthSignId); - mReviewDialog->setHealth(mPlayerHealth); - mReviewDialog->setMagicka(mPlayerMagicka); - mReviewDialog->setFatigue(mPlayerFatigue); + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + + mReviewDialog->setHealth ( stats.getHealth() ); + mReviewDialog->setMagicka( stats.getMagicka() ); + mReviewDialog->setFatigue( stats.getFatigue() ); + } { - std::map > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); - for (std::map >::iterator it = attributes.begin(); + std::map attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); + for (std::map::iterator it = attributes.begin(); it != attributes.end(); ++it) { mReviewDialog->setAttribute(static_cast (it->first), it->second); @@ -233,8 +236,8 @@ namespace MWGui } { - std::map > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); - for (std::map >::iterator it = skills.begin(); + std::map skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); + for (std::map::iterator it = skills.begin(); it != skills.end(); ++it) { mReviewDialog->setSkillValue(static_cast (it->first), it->second); @@ -258,21 +261,6 @@ namespace MWGui mRaceDialog->doRenderUpdate(); } - void CharacterCreation::setPlayerHealth (const MWMechanics::DynamicStat& value) - { - mPlayerHealth = value; - } - - void CharacterCreation::setPlayerMagicka (const MWMechanics::DynamicStat& value) - { - mPlayerMagicka = value; - } - - void CharacterCreation::setPlayerFatigue (const MWMechanics::DynamicStat& value) - { - mPlayerFatigue = value; - } - void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); @@ -285,7 +273,9 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); mReviewDialog = 0; + mCreationStage = CSE_ReviewBack; + MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } @@ -332,24 +322,22 @@ namespace MWGui mPickClassDialog = 0; } + updatePlayerHealth(); + //TODO This bit gets repeated a few times; wrap it in a function + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_ClassChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } else { mCreationStage = CSE_ClassChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onPickClassDialogBack() @@ -403,20 +391,18 @@ namespace MWGui mNameDialog = 0; } + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_NameChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race); } else { mCreationStage = CSE_NameChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } } @@ -462,23 +448,21 @@ namespace MWGui mRaceDialog = 0; } + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_RaceChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } else { mCreationStage = CSE_RaceChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) @@ -492,18 +476,17 @@ namespace MWGui mBirthSignDialog = 0; } + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage >= CSE_BirthSignChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else { mCreationStage = CSE_BirthSignChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogBack() @@ -552,23 +535,21 @@ namespace MWGui mCreateClassDialog = 0; } + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_ClassChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } else { mCreationStage = CSE_ClassChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onCreateClassDialogBack() @@ -722,23 +703,21 @@ namespace MWGui mPlayerClass = *klass; MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass); + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_ClassChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } else { mCreationStage = CSE_ClassChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } CharacterCreation::~CharacterCreation() diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index bd8826677..924f40c28 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -31,15 +31,9 @@ namespace MWGui //Show a dialog void spawnDialog(const char id); - void setPlayerHealth (const MWMechanics::DynamicStat& value); - - void setPlayerMagicka (const MWMechanics::DynamicStat& value); - - void setPlayerFatigue (const MWMechanics::DynamicStat& value); - - void setValue (const std::string& id, const MWMechanics::Stat& value); + void setValue (const std::string& id, const MWMechanics::AttributeValue& value); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); + void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void configureSkills (const SkillList& major, const SkillList& minor); void doRenderUpdate(); @@ -60,9 +54,6 @@ namespace MWGui std::string mPlayerRaceId; std::string mPlayerBirthSignId; ESM::Class mPlayerClass; - MWMechanics::DynamicStat mPlayerHealth; - MWMechanics::DynamicStat mPlayerMagicka; - MWMechanics::DynamicStat mPlayerFatigue; //Class generation vars unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog @@ -104,6 +95,7 @@ namespace MWGui CSE_RaceChosen, CSE_ClassChosen, CSE_BirthSignChosen, + CSE_ReviewBack, CSE_ReviewNext }; diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index c33e54d6b..1c8cc7840 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -1,7 +1,5 @@ #include "class.hpp" -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -29,11 +27,12 @@ namespace MWGui MyGUI::Button* backButton; getWidget(backButton, "BackButton"); + backButton->setCaptionWithReplacing("#{sMessageQuestionAnswer3}"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaptionWithReplacing("#{sMessageQuestionAnswer2}"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); } @@ -83,8 +82,7 @@ namespace MWGui getWidget(mClassList, "ClassList"); mClassList->setScrollVisible(true); - mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); - mClassList->eventListMouseItemActivate += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); + mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onAccept); mClassList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); getWidget(mClassImage, "ClassImage"); @@ -127,7 +125,7 @@ namespace MWGui size_t count = mClassList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (boost::iequals(*mClassList->getItemDataAt(i), classId)) + if (Misc::StringUtils::ciEqual(*mClassList->getItemDataAt(i), classId)) { mClassList->setIndexSelected(i); MyGUI::Button* okButton; @@ -153,6 +151,14 @@ namespace MWGui eventBack(); } + void PickClassDialog::onAccept(MyGUI::ListBox* _sender, size_t _index) + { + onSelectClass(_sender, _index); + if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE) + return; + eventDone(this); + } + void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index) { if (_index == MyGUI::ITEM_NONE) @@ -162,7 +168,7 @@ namespace MWGui getWidget(okButton, "OKButton"); const std::string *classId = mClassList->getItemDataAt(_index); - if (boost::iequals(mCurrentClassId, *classId)) + if (Misc::StringUtils::ciEqual(mCurrentClassId, *classId)) return; mCurrentClassId = *classId; @@ -192,7 +198,7 @@ namespace MWGui mCurrentClassId = id; mClassList->setIndexSelected(index); } - else if (boost::iequals(id, mCurrentClassId)) + else if (Misc::StringUtils::ciEqual(id, mCurrentClassId)) { mClassList->setIndexSelected(index); } @@ -466,7 +472,7 @@ namespace MWGui std::string CreateClassDialog::getName() const { - return mEditName->getOnlyText(); + return mEditName->getCaption(); } std::string CreateClassDialog::getDescription() const diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index 15fc89658..f78f7541b 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -111,6 +111,7 @@ namespace MWGui protected: void onSelectClass(MyGUI::ListBox* _sender, size_t _index); + void onAccept(MyGUI::ListBox* _sender, size_t _index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); @@ -228,8 +229,8 @@ namespace MWGui DescriptionDialog(); ~DescriptionDialog(); - std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; } - void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); } + std::string getTextInput() const { return mTextEdit->getCaption(); } + void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } protected: void onOkClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 3212ed701..bb6cf2800 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -12,7 +12,7 @@ namespace MWGui void CompanionItemModel::copyItem (const ItemStack& item, size_t count) { - if (mActor.getTypeName() == typeid(ESM::NPC).name()) + if (mActor.getClass().isNpc()) { MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); @@ -23,7 +23,7 @@ namespace MWGui void CompanionItemModel::removeItem (const ItemStack& item, size_t count) { - if (mActor.getTypeName() == typeid(ESM::NPC).name()) + if (mActor.getClass().isNpc()) { MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 9698608d6..a0a34108e 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -86,12 +86,13 @@ void CompanionWindow::onBackgroundSelected() void CompanionWindow::open(const MWWorld::Ptr& npc) { mPtr = npc; - setTitle(MWWorld::Class::get(npc).getName(npc)); updateEncumbranceBar(); mModel = new CompanionItemModel(npc); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); + + setTitle(MWWorld::Class::get(npc).getName(npc)); } void CompanionWindow::onFrame() diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index a1e3fb738..f3805b255 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -119,8 +119,6 @@ namespace MWGui // Set up the log window mHistory->setOverflowToTheLeft(true); - mHistory->setEditStatic(true); - mHistory->setVisibleVScroll(true); // compiler Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts); @@ -215,7 +213,7 @@ namespace MWGui { std::vector matches; listNames(); - mCommandLine->setCaption(complete( mCommandLine->getCaption(), matches )); + mCommandLine->setCaption(complete( mCommandLine->getOnlyText(), matches )); #if 0 int i = 0; for(std::vector::iterator it=matches.begin(); it < matches.end(); ++it,++i ) @@ -234,7 +232,7 @@ namespace MWGui { // If the user was editing a string, store it for later if(mCurrent == mCommandHistory.end()) - mEditString = mCommandLine->getCaption(); + mEditString = mCommandLine->getOnlyText(); if(mCurrent != mCommandHistory.begin()) { @@ -246,7 +244,7 @@ namespace MWGui { if(mCurrent != mCommandHistory.end()) { - --mCurrent; + ++mCurrent; if(mCurrent != mCommandHistory.end()) mCommandLine->setCaption(*mCurrent); @@ -259,7 +257,7 @@ namespace MWGui void Console::acceptCommand(MyGUI::EditBox* _sender) { - const std::string &cm = mCommandLine->getCaption(); + const std::string &cm = mCommandLine->getOnlyText(); if(cm.empty()) return; // Add the command to the history, and set the current pointer to @@ -406,13 +404,14 @@ namespace MWGui setTitle("#{sConsoleTitle} (" + object.getCellRef().mRefID + ")"); mPtr = object; } + // User clicked on an object. Restore focus to the console command line. + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else { setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); } - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } void Console::onReferenceUnavailable() diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index bc869e5fe..7e7ad5ec2 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -6,9 +6,13 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" + +#include "../mwmechanics/pickpocket.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" @@ -61,8 +65,9 @@ namespace MWGui mDraggedWidget = baseWidget; MyGUI::ImageBox* image = baseWidget->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - int pos = path.rfind("."); - path.erase(pos); + size_t pos = path.rfind("."); + if (pos != std::string::npos) + path.erase(pos); path.append(".dds"); image->setImageTexture(path); image->setNeedMouseFocus(false); @@ -100,6 +105,9 @@ namespace MWGui finish(); targetView->update(); + + // We need to update the view since an other item could be auto-equipped. + mSourceView->update(); } void DragAndDrop::finish() @@ -119,6 +127,7 @@ namespace MWGui , mSelectedItem(-1) , mModel(NULL) , mSortModel(NULL) + , mPickpocketDetected(false) { getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); getWidget(mTakeButton, "TakeButton"); @@ -130,6 +139,7 @@ namespace MWGui mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); + mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ContainerWindow::onKeyPressed); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); setCoord(200,0,600,300); @@ -167,6 +177,9 @@ namespace MWGui void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) { + if (!onTakeItem(mModel->getItem(mSelectedItem), count)) + return; + mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); } @@ -204,12 +217,13 @@ namespace MWGui void ContainerWindow::open(const MWWorld::Ptr& container, bool loot) { + mPickpocketDetected = false; mPtr = container; if (mPtr.getTypeName() == typeid(ESM::NPC).name() && !loot) { // we are stealing stuff - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); mModel = new PickpocketItemModel(player, new InventoryItemModel(container)); } else @@ -217,11 +231,48 @@ namespace MWGui mDisposeCorpseButton->setVisible(loot); - setTitle(MWWorld::Class::get(container).getName(container)); - mSortModel = new SortFilterItemModel(mModel); mItemView->setModel (mSortModel); + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + + // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last + // or we end up using a possibly invalid model. + setTitle(MWWorld::Class::get(container).getName(container)); + } + + void ContainerWindow::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) + { + if (_key == MyGUI::KeyCode::Space) + onCloseButtonClicked(mCloseButton); + if (_key == MyGUI::KeyCode::Return || _key == MyGUI::KeyCode::NumpadEnter) + onTakeAllButtonClicked(mTakeButton); + } + + void ContainerWindow::close() + { + WindowBase::close(); + + if (dynamic_cast(mModel) + // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened) + && !MWBase::Environment::get().getWindowManager()->containsMode(GM_Container) + // If it was already detected while taking an item, no need to check now + && !mPickpocketDetected + ) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::Pickpocket pickpocket(player, mPtr); + if (pickpocket.finish()) + { + MWBase::Environment::get().getMechanicsManager()->reportCrime( + player, mPtr, MWBase::MechanicsManager::OT_Pickpocket); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); + MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief"); + mPickpocketDetected = true; + return; + } + } } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) @@ -249,8 +300,13 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); } - playerModel->copyItem(mModel->getItem(i), mModel->getItem(i).mCount); - mModel->removeItem(mModel->getItem(i), mModel->getItem(i).mCount); + const ItemStack& item = mModel->getItem(i); + + if (!onTakeItem(item, item.mCount)) + break; + + playerModel->copyItem(item, item.mCount); + mModel->removeItem(item, item.mCount); } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); @@ -277,4 +333,30 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); } + bool ContainerWindow::onTakeItem(const ItemStack &item, int count) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (dynamic_cast(mModel)) + { + MWMechanics::Pickpocket pickpocket(player, mPtr); + if (pickpocket.pick(item.mBase, count)) + { + int value = item.mBase.getClass().getValue(item.mBase) * count; + MWBase::Environment::get().getMechanicsManager()->reportCrime( + player, MWWorld::Ptr(), MWBase::MechanicsManager::OT_Theft, value); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); + MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief"); + mPickpocketDetected = true; + return false; + } + else + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); + } + else + { + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count); + } + return true; + } + } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 243f77aa5..ce4707af6 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -52,10 +52,13 @@ namespace MWGui ContainerWindow(DragAndDrop* dragAndDrop); void open(const MWWorld::Ptr& container, bool loot=false); + virtual void close(); private: DragAndDrop* mDragAndDrop; + bool mPickpocketDetected; + MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; ItemModel* mModel; @@ -72,6 +75,10 @@ namespace MWGui void onCloseButtonClicked(MyGUI::Widget* _sender); void onTakeAllButtonClicked(MyGUI::Widget* _sender); void onDisposeCorpseButtonClicked(MyGUI::Widget* sender); + void onKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char); + + /// @return is taking the item allowed? + bool onTakeItem(const ItemStack& item, int count); virtual void onReferenceUnavailable(); }; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index eff8fbcc1..bcb8440bf 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -76,10 +76,7 @@ void ContainerItemModel::copyItem (const ItemStack& item, size_t count) const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) throw std::runtime_error("Item to copy needs to be from a different container!"); - int origCount = item.mBase.getRefData().getCount(); - item.mBase.getRefData().setCount(count); - source.getClass().getContainerStore(source).add(item.mBase, source); - item.mBase.getRefData().setCount(origCount); + source.getClass().getContainerStore(source).add(item.mBase, count, source); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) @@ -94,9 +91,7 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) { if (stacks(*it, item.mBase)) { - int refCount = it->getRefData().getCount(); - it->getRefData().setCount(std::max(0, refCount - toRemove)); - toRemove -= refCount; + toRemove -= store.remove(*it, toRemove, *source); if (toRemove <= 0) return; } diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index c069eca15..9c64f94ca 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -73,57 +73,4 @@ namespace MWGui 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 index badf82262..4e3eb9097 100644 --- a/apps/openmw/mwgui/cursor.hpp +++ b/apps/openmw/mwgui/cursor.hpp @@ -39,23 +39,6 @@ namespace MWGui 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/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index c9a780691..481c98314 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -12,6 +12,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" @@ -19,7 +20,6 @@ #include "list.hpp" #include "tradewindow.hpp" #include "spellbuyingwindow.hpp" -#include "inventorywindow.hpp" #include "travelwindow.hpp" #include "bookpage.hpp" @@ -67,23 +67,24 @@ namespace MWGui void PersuasionDialog::onPersuade(MyGUI::Widget *sender) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWBase::MechanicsManager::PersuasionType type; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-10); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player); type = MWBase::MechanicsManager::PT_Bribe10; } else if (sender == mBribe100Button) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-100); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player); type = MWBase::MechanicsManager::PT_Bribe100; } else /*if (sender == mBribe1000Button)*/ { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-1000); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player); type = MWBase::MechanicsManager::PT_Bribe1000; } @@ -97,7 +98,8 @@ namespace MWGui WindowModal::open(); center(); - int playerGold = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mBribe10Button->setEnabled (playerGold >= 10); mBribe100Button->setEnabled (playerGold >= 100); @@ -299,6 +301,8 @@ namespace MWGui void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) { + if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) + return; MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); } @@ -541,7 +545,7 @@ namespace MWGui for (size_t i=0; igetItemCount(); ++i) { std::string item = mTopicsList->getItemNameAt(i); - if (Misc::StringUtils::lowerCase(item) == title) + if (Misc::StringUtils::ciEqual(item, title)) { realTitle = item; break; diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 98ba8ec2f..92205c3e9 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -4,13 +4,14 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/manualref.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "itemselection.hpp" #include "container.hpp" -#include "inventorywindow.hpp" #include "sortfilteritemmodel.hpp" @@ -105,7 +106,7 @@ namespace MWGui void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(player); @@ -148,7 +149,7 @@ namespace MWGui mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); } @@ -235,7 +236,7 @@ namespace MWGui mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); @@ -258,7 +259,7 @@ namespace MWGui { if (mEffects.size() <= 0) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}"); + MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu11}"); return; } @@ -289,18 +290,48 @@ namespace MWGui mEnchanting.setNewItemName(mName->getCaption()); mEnchanting.setEffect(mEffectList); - if (mEnchanting.getEnchantPrice() > MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + if (mEnchanting.getEnchantPrice() > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; } + // check if the player is attempting to use a soulstone or item that was stolen from this actor + if (mPtr != player) + { + for (int i=0; i<2; ++i) + { + MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); + if (Misc::StringUtils::ciEqual(item.getCellRef().mOwner, mPtr.getCellRef().mRefID)) + { + std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->getString(); + if (msg.find("%s") != std::string::npos) + msg.replace(msg.find("%s"), 2, item.getClass().getName(item)); + MWBase::Environment::get().getWindowManager()->messageBox(msg); + MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief"); + MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, + item.getClass().getValue(item)); + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + return; + } + } + } + int result = mEnchanting.create(); if(result==1) + { + MWBase::Environment::get().getSoundManager()->playSound("enchant success", 1.f, 1.f); MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}"); + } else + { + MWBase::Environment::get().getSoundManager()->playSound("enchant fail", 1.f, 1.f); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}"); + } MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); } diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index bd75c078c..4d3d04ced 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -12,6 +12,8 @@ #include #include +#include + namespace { int convertFromHex(std::string hex) @@ -288,6 +290,16 @@ namespace MWGui MyGUI::ImageBox* box = mParent->createWidget ("ImageBox", MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top, mParent->getName() + boost::lexical_cast(mParent->getChildCount())); + + // Apparently a bug with some morrowind versions, they reference the image without the size suffix. + // So if the image isn't found, try appending the size. + if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("bookart\\"+image)) + { + std::stringstream str; + str << image.substr(0, image.rfind(".")) << "_" << width << "_" << height << image.substr(image.rfind(".")); + image = str.str(); + } + box->setImageTexture("bookart\\" + image); box->setProperty("NeedMouse", "false"); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index edcd49738..a6ad43ce5 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -6,7 +6,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -28,6 +27,7 @@ namespace MWGui , mStamina(NULL) , mDrowning(NULL) , mDrowningFrame(NULL) + , mDrowningFlash(NULL) , mWeapImage(NULL) , mSpellImage(NULL) , mWeapStatus(NULL) @@ -53,6 +53,9 @@ namespace MWGui , mSpellVisible(true) , mWorldMouseOver(false) , mEnemyHealthTimer(0) + , mIsDrowning(false) + , mWeaponSpellTimer(0.f) + , mDrowningFlashTheta(0.f) { setCoord(0,0, width, height); @@ -75,6 +78,7 @@ namespace MWGui //Drowning bar getWidget(mDrowningFrame, "DrowningFrame"); getWidget(mDrowning, "Drowning"); + getWidget(mDrowningFlash, "Flash"); mDrowning->setProgressRange(200); const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); @@ -171,43 +175,45 @@ namespace MWGui void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { - static const char *ids[] = - { - "HBar", "MBar", "FBar", 0 - }; + int current = std::max(0, static_cast(value.getCurrent())); + int modified = static_cast(value.getModified()); - for (int i=0; ids[i]; ++i) - if (ids[i]==id) - { - MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - switch (i) - { - case 0: - mHealth->setProgressRange (value.getModified()); - mHealth->setProgressPosition (value.getCurrent()); - getWidget(w, "HealthFrame"); - w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); - break; - case 1: - mMagicka->setProgressRange (value.getModified()); - mMagicka->setProgressPosition (value.getCurrent()); - getWidget(w, "MagickaFrame"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); - break; - case 2: - mStamina->setProgressRange (value.getModified()); - mStamina->setProgressPosition (value.getCurrent()); - getWidget(w, "FatigueFrame"); - w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); - break; - } - } + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + if (id == "HBar") + { + mHealth->setProgressRange(modified); + mHealth->setProgressPosition(current); + getWidget(w, "HealthFrame"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (id == "MBar") + { + mMagicka->setProgressRange (modified); + mMagicka->setProgressPosition (current); + getWidget(w, "MagickaFrame"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (id == "FBar") + { + mStamina->setProgressRange (modified); + mStamina->setProgressPosition (current); + getWidget(w, "FatigueFrame"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); + } } void HUD::setDrowningTimeLeft(float time) { - mDrowning->setProgressPosition(time/20.0*200.0); + size_t progress = time/20.0*200.0; + mDrowning->setProgressPosition(progress); + + bool isDrowning = (progress == 0); + if (isDrowning && !mIsDrowning) // Just started drowning + mDrowningFlashTheta = 0.0f; // Start out on bright red every time. + + mDrowningFlash->setVisible(isDrowning); + mIsDrowning = isDrowning; } void HUD::setDrowningBarVisible(bool visible) @@ -232,21 +238,16 @@ namespace MWGui float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); - int origCount = object.getRefData().getCount(); - object.getRefData().setCount(mDragAndDrop->mDraggedCount); - if (world->canPlaceObject(mouseX, mouseY)) - world->placeObject(object, mouseX, mouseY); + world->placeObject(object, mouseX, mouseY, mDragAndDrop->mDraggedCount); else - world->dropObjectOnGround(world->getPlayer().getPlayer(), object); + world->dropObjectOnGround(world->getPlayerPtr(), object, mDragAndDrop->mDraggedCount); 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); - object.getRefData().setCount(origCount); - // remove object from the container it was coming from mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); mDragAndDrop->finish(); @@ -266,7 +267,8 @@ namespace MWGui else if ((mode == GM_Container) || (mode == GM_Inventory)) { // pick up object - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object); + if (!object.isEmpty()) + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object); } } } @@ -318,7 +320,7 @@ namespace MWGui void HUD::onWeaponClicked(MyGUI::Widget* _sender) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); @@ -330,7 +332,7 @@ namespace MWGui void HUD::onMagicClicked(MyGUI::Widget* _sender) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); @@ -367,6 +369,9 @@ namespace MWGui mEnemyHealth->setVisible(false); mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); } + + if (mIsDrowning) + mDrowningFlashTheta += dt * Ogre::Math::TWO_PI; } void HUD::onResChange(int width, int height) @@ -512,7 +517,7 @@ namespace MWGui mWeapStatus->setProgressPosition(0); MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf()) mWeapImage->setImageTexture("icons\\k\\tx_werewolf_hand.dds"); else @@ -609,6 +614,12 @@ namespace MWGui mEnemyHealth->setProgressRange(100); mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); } + + if (mIsDrowning) + { + float intensity = (cos(mDrowningFlashTheta) + 1.0f) / 2.0f; + mDrowningFlash->setColour(MyGUI::Colour(intensity, intensity, intensity)); + } } void HUD::setEnemy(const MWWorld::Ptr &enemy) diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index c40742a60..04206fbc8 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -67,7 +67,7 @@ namespace MWGui MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; - MyGUI::Widget* mDrowningFrame; + MyGUI::Widget *mDrowningFrame, *mDrowningFlash; MyGUI::Widget* mDummy; @@ -101,6 +101,9 @@ namespace MWGui MWWorld::Ptr mEnemy; float mEnemyHealthTimer; + bool mIsDrowning; + float mDrowningFlashTheta; + 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 index 98f05373b..f2565f5c0 100644 --- a/apps/openmw/mwgui/imagebutton.cpp +++ b/apps/openmw/mwgui/imagebutton.cpp @@ -42,12 +42,13 @@ namespace MWGui ImageBox::onMouseButtonPressed(_left, _top, _id); } - MyGUI::IntSize ImageButton::getRequestedSize() + MyGUI::IntSize ImageButton::getRequestedSize(bool logError) { Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName(mImageNormal); if (texture.isNull()) { - std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; + if (logError) + std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; return MyGUI::IntSize(0,0); } return MyGUI::IntSize (texture->getWidth(), texture->getHeight()); diff --git a/apps/openmw/mwgui/imagebutton.hpp b/apps/openmw/mwgui/imagebutton.hpp index f531e2246..f4191a3a5 100644 --- a/apps/openmw/mwgui/imagebutton.hpp +++ b/apps/openmw/mwgui/imagebutton.hpp @@ -14,7 +14,7 @@ namespace MWGui MYGUI_RTTI_DERIVED(ImageButton) public: - MyGUI::IntSize getRequestedSize(); + MyGUI::IntSize getRequestedSize(bool logError = true); protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 62a5a75f0..97e1e9a2b 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -42,28 +42,19 @@ void InventoryItemModel::copyItem (const ItemStack& item, size_t count) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); - int origCount = item.mBase.getRefData().getCount(); - item.mBase.getRefData().setCount(count); - mActor.getClass().getContainerStore(mActor).add(item.mBase, mActor); - item.mBase.getRefData().setCount(origCount); + mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor); } void InventoryItemModel::removeItem (const ItemStack& item, size_t count) { MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); + int removed = store.remove(item.mBase, count, mActor); - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - if (*it == item.mBase) - { - if (it->getRefData().getCount() < static_cast(count)) - throw std::runtime_error("Not enough items in the stack to remove"); - it->getRefData().setCount(it->getRefData().getCount() - count); - return; - } - } - throw std::runtime_error("Item to remove not found in container store"); + if (removed == 0) + throw std::runtime_error("Item to remove not found in container store"); + else if (removed < static_cast(count)) + throw std::runtime_error("Not enough items in the stack to remove"); } void InventoryItemModel::update() @@ -78,14 +69,14 @@ void InventoryItemModel::update() // NOTE: Don't show WerewolfRobe objects in the inventory, or allow them to be taken. // Vanilla likely uses a hack like this since there's no other way to prevent it from // being shown or taken. - if(item.getCellRef().mRefID == "WerewolfRobe") + if(item.getCellRef().mRefID == "werewolfrobe") continue; ItemStack newItem (item, this, item.getRefData().getCount()); - if (mActor.getTypeName() == typeid(ESM::NPC).name()) + if (mActor.getClass().hasInventoryStore(mActor)) { - MWWorld::InventoryStore& store = MWWorld::Class::get(mActor).getInventoryStore(mActor); + MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); for (int slot=0; slotgetPlayer ().getPlayer ()) + , mPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr()) , mPreviewDirty(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) - , mPositionInventory(0, 342, 498, 258) - , mPositionContainer(0, 342, 498, 258) - , mPositionCompanion(0, 342, 498, 258) - , mPositionBarter(0, 342, 498, 258) , mGuiMode(GM_Inventory) { static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); @@ -57,14 +55,12 @@ namespace MWGui getWidget(mRightPane, "RightPane"); getWidget(mArmorRating, "ArmorRating"); - mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); + mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &InventoryWindow::onBackgroundSelected); - updatePlayer(); - mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); @@ -73,15 +69,25 @@ namespace MWGui mFilterAll->setStateSelected(true); - setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height); - onWindowResize(static_cast(mMainWidget)); + setGuiMode(mGuiMode); - mPreview.setup(); + adjustPanes(); + } + + void InventoryWindow::adjustPanes() + { + const float aspect = 0.5; // fixed aspect ratio for the avatar image + float leftPaneWidth = (mMainWidget->getSize().height-44-mArmorRating->getHeight()) * aspect; + mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 ); + mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4, + mRightPane->getPosition().top, + mMainWidget->getSize().width - 12 - leftPaneWidth - 15, + mMainWidget->getSize().height-44 ); } void InventoryWindow::updatePlayer() { - mPtr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel(mSortModel); @@ -91,27 +97,39 @@ namespace MWGui void InventoryWindow::setGuiMode(GuiMode mode) { + std::string setting = "inventory"; mGuiMode = mode; switch(mode) { case GM_Container: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionContainer); + setting += " container"; break; case GM_Companion: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionCompanion); + setting += " companion"; break; case GM_Barter: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionBarter); + setting += " barter"; break; case GM_Inventory: default: setPinButtonVisible(true); - mMainWidget->setCoord(mPositionInventory); break; } - onWindowResize(static_cast(mMainWidget)); + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint pos (Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width, + Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height); + MyGUI::IntSize size (Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width, + Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); + + if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) + mPreviewDirty = true; + + mMainWidget->setPosition(pos); + mMainWidget->setSize(size); + adjustPanes(); } TradeItemModel* InventoryWindow::getTradeModel() @@ -145,10 +163,46 @@ namespace MWGui const ItemStack& item = mTradeModel->getItem(index); - unequipItem(item.mBase); - MWWorld::Ptr object = item.mBase; int count = item.mCount; + + // Bound items may not be moved + if (item.mBase.getCellRef().mRefID.size() > 6 + && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } + + if (item.mType == ItemStack::Type_Equipped) + { + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr); + + // The unequipped item was re-stacked. We have to update the index + // since the item pointed does not exist anymore. + if (item.mBase != newStack) + { + // newIndex will store the index of the ItemStack the item was stacked on + int newIndex = -1; + for (size_t i=0; i < mTradeModel->getItemCount(); ++i) + { + if (mTradeModel->getItem(i).mBase == newStack) + { + newIndex = i; + break; + } + } + + if (newIndex == -1) + throw std::runtime_error("Can't find restacked item"); + + index = newIndex; + object = mTradeModel->getItem(index).mBase; + } + + } + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; @@ -225,37 +279,44 @@ namespace MWGui void InventoryWindow::open() { + mPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + updateEncumbranceBar(); mItemView->update(); notifyContentChanged(); + adjustPanes(); } void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { - const float aspect = 0.5; // fixed aspect ratio for the left pane - mLeftPane->setSize( (_sender->getSize().height-44) * aspect, _sender->getSize().height-44 ); - mRightPane->setCoord( mLeftPane->getPosition().left + (_sender->getSize().height-44) * aspect + 4, - mRightPane->getPosition().top, - _sender->getSize().width - 12 - (_sender->getSize().height-44) * aspect - 15, - _sender->getSize().height-44 ); - + adjustPanes(); + std::string setting = "inventory"; switch(mGuiMode) { case GM_Container: - mPositionContainer = _sender->getCoord(); + setting += " container"; break; case GM_Companion: - mPositionCompanion = _sender->getCoord(); + setting += " companion"; break; case GM_Barter: - mPositionBarter = _sender->getCoord(); + setting += " barter"; break; - case GM_Inventory: default: - mPositionInventory = _sender->getCoord(); + break; } + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = _sender->getPosition().left / float(viewSize.width); + float y = _sender->getPosition().top / float(viewSize.height); + float w = _sender->getSize().width / float(viewSize.width); + float h = _sender->getSize().height / float(viewSize.height); + Settings::Manager::setFloat(setting + " x", "Windows", x); + Settings::Manager::setFloat(setting + " y", "Windows", y); + Settings::Manager::setFloat(setting + " w", "Windows", w); + Settings::Manager::setFloat(setting + " h", "Windows", h); + if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { mLastXSize = mMainWidget->getSize().width; @@ -293,6 +354,48 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned); } + void InventoryWindow::useItem(const MWWorld::Ptr &ptr) + { + const std::string& script = ptr.getClass().getScript(ptr); + + // If the item has a script, set its OnPcEquip to 1 + if (!script.empty() + // Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards, + // the next time it is equipped will work normally, but will not set onpcequip + && (ptr != mSkippedToEquip || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1)) + ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); + + // Give the script a chance to run once before we do anything else + // this is important when setting pcskipequip as a reaction to onpcequip being set (bk_treasuryreport does this) + if (!script.empty()) + { + MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); + MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + } + + if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) + { + boost::shared_ptr action = MWWorld::Class::get(ptr).use(ptr); + + action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); + + // this is necessary for books/scrolls: if they are already in the player's inventory, + // 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 + MWBase::Environment::get().getWindowManager()->getBookWindow()->setTakeButtonShow(false); + MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false); + + mSkippedToEquip = MWWorld::Ptr(); + } + else + mSkippedToEquip = ptr; + + mItemView->update(); + + notifyContentChanged(); + } + void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender) { if (mDragAndDrop->mIsOnDragAndDrop) @@ -306,36 +409,19 @@ namespace MWGui MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); MWWorld::ContainerStoreIterator it = invStore.begin(); - int origCount = ptr.getRefData().getCount(); - ptr.getRefData().setCount(mDragAndDrop->mDraggedCount); - it = invStore.add(ptr, mPtr); - ptr.getRefData().setCount(origCount); + it = invStore.add(ptr, mDragAndDrop->mDraggedCount, mPtr); mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); ptr = *it; } - - boost::shared_ptr action = MWWorld::Class::get(ptr).use(ptr); - - action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - - // this is necessary for books/scrolls: if they are already in the player's inventory, - // 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 - MWBase::Environment::get().getWindowManager()->getBookWindow()->setTakeButtonShow(false); - MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false); - - mItemView->update(); - - notifyContentChanged(); + useItem(ptr); } else { MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left); - MyGUI::IntPoint relPos = mousePos - mAvatar->getAbsolutePosition (); - int realX = int(float(relPos.left) / float(mAvatar->getSize().width) * 512.f ); - int realY = int(float(relPos.top) / float(mAvatar->getSize().height) * 1024.f ); + MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition (); + int realX = int(float(relPos.left) / float(mAvatarImage->getSize().width) * 512.f ); + int realY = int(float(relPos.top) / float(mAvatarImage->getSize().height) * 1024.f ); MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY); if (itemSelected.isEmpty ()) @@ -367,7 +453,7 @@ namespace MWGui // NOTE: Don't allow users to select WerewolfRobe objects in the inventory. Vanilla // likely uses a hack like this since there's no other way to prevent it from being // taken. - if(item.getCellRef().mRefID == "WerewolfRobe") + if(item.getCellRef().mRefID == "werewolfrobe") return MWWorld::Ptr(); return item; } @@ -375,30 +461,9 @@ namespace MWGui return MWWorld::Ptr(); } - void InventoryWindow::unequipItem(const MWWorld::Ptr& item) - { - 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() && *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).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); - - return; - } - } - } - void InventoryWindow::updateEncumbranceBar() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); float capacity = MWWorld::Class::get(player).getCapacity(player); float encumbrance = MWWorld::Class::get(player).getEncumbrance(player); @@ -413,19 +478,6 @@ namespace MWGui updateEncumbranceBar(); } - int InventoryWindow::getPlayerGold() - { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - - for (MWWorld::ContainerStoreIterator it = invStore.begin(); - it != invStore.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) - return it->getRefData().getCount(); - } - return 0; - } - void InventoryWindow::setTrading(bool trading) { mTrading = trading; @@ -436,11 +488,18 @@ namespace MWGui if (mPreviewDirty) { mPreviewDirty = false; - MyGUI::IntSize size = mAvatar->getSize(); + MyGUI::IntSize size = mAvatarImage->getSize(); mPreview.update (size.width, size.height); - mAvatarImage->setSize(MyGUI::IntSize(std::max(mAvatar->getSize().width, 512), std::max(mAvatar->getSize().height, 1024))); + mAvatarImage->setImageTexture("CharacterPreview"); + mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); + mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); + + mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + + boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); + if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) + mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); } } @@ -450,18 +509,7 @@ namespace MWGui if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); - // update selected weapon icon - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot == invStore.end()) - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - else - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot); - mPreviewDirty = true; - - mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); } void InventoryWindow::pickUpObject (MWWorld::Ptr object) @@ -486,13 +534,11 @@ namespace MWGui return; int count = object.getRefData().getCount(); - if (object.getCellRef().mGoldValue > 1) - count = object.getCellRef().mGoldValue; // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::Ptr newObject = *MWWorld::Class::get (player).getContainerStore (player).add (object, player); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player); // remove from world MWBase::Environment::get().getWorld()->deleteObject (object); @@ -507,10 +553,7 @@ namespace MWGui if (i == mTradeModel->getItemCount()) throw std::runtime_error("Added item not found"); mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count); - } - MyGUI::IntCoord InventoryWindow::getAvatarScreenCoord () - { - return mAvatar->getAbsoluteCoord (); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count); } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 35140437d..7ef168e98 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -31,10 +31,6 @@ namespace MWGui void pickUpObject (MWWorld::Ptr object); - int getPlayerGold(); - - MyGUI::IntCoord getAvatarScreenCoord(); - MWWorld::Ptr getAvatarSelectedItem(int x, int y); void rebuildAvatar() { @@ -48,6 +44,8 @@ namespace MWGui void updatePlayer(); + void useItem(const MWWorld::Ptr& ptr); + void setGuiMode(GuiMode mode); private: @@ -76,10 +74,7 @@ namespace MWGui MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; - MyGUI::IntCoord mPositionInventory; - MyGUI::IntCoord mPositionContainer; - MyGUI::IntCoord mPositionCompanion; - MyGUI::IntCoord mPositionBarter; + MWWorld::Ptr mSkippedToEquip; GuiMode mGuiMode; @@ -103,9 +98,10 @@ namespace MWGui void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); - void unequipItem(const MWWorld::Ptr& item); void updateEncumbranceBar(); void notifyContentChanged(); + + void adjustPanes(); }; } diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index dbea10e77..8caea770e 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -196,34 +196,6 @@ book JournalBooks::createEmptyJournalBook () typesetter->lineBreak (); typesetter->write (body, to_utf8_span ("You should have gone though the starting quest and got an initial quest.")); - BookTypesetter::Style* big = typesetter->createStyle ("", MyGUI::Colour::Black); - BookTypesetter::Style* test = typesetter->createStyle ("MonoFont", MyGUI::Colour::Blue); - - typesetter->sectionBreak (20); - typesetter->write (body, to_utf8_span ( - "The layout engine doesn't currently support aligning fonts to " - "their baseline within a single line so the following text looks " - "funny. In order to properly implement it, a stupidly simple " - "change is needed in MyGUI to report the where the baseline is for " - "a particular font" - )); - - typesetter->sectionBreak (20); - typesetter->write (big, to_utf8_span ("big text g")); - typesetter->write (body, to_utf8_span (" проверяем только в дебаге")); - typesetter->write (body, to_utf8_span (" normal g")); - typesetter->write (big, to_utf8_span (" done g")); - - typesetter->sectionBreak (20); - typesetter->write (test, to_utf8_span ( - "int main (int argc,\n" - " char ** argv)\n" - "{\n" - " print (\"hello world!\\n\");\n" - " return 0;\n" - "}\n" - )); - return typesetter->complete (); } diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 79a77070a..a0d67b025 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -20,8 +20,6 @@ namespace MWGui { struct JournalViewModelImpl; -static void injectMonthName (std::ostream & os, int month); - struct JournalViewModelImpl : JournalViewModel { typedef KeywordSearch KeywordSearchT; @@ -206,10 +204,12 @@ struct JournalViewModelImpl : JournalViewModel if (active_only && i->second.isFinished ()) continue; - /// \todo quest.getName() is broken? returns empty string - //const MWDialogue::Quest& quest = i->second; - - visitor (reinterpret_cast (&i->second), toUtf8Span (i->first)); + const MWDialogue::Quest& quest = i->second; + // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal. + if (quest.getName().empty()) + visitor (reinterpret_cast (&i->second), toUtf8Span (i->first)); + else + visitor (reinterpret_cast (&i->second), toUtf8Span (quest.getName())); } } @@ -235,21 +235,21 @@ struct JournalViewModelImpl : JournalViewModel std::string getText () const { - return itr->getText(MWBase::Environment::get().getWorld()->getStore()); + return itr->getText(); } Utf8Span timestamp () const { if (timestamp_buffer.empty ()) { - std::ostringstream os; + std::string dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); - os << itr->mDayOfMonth << ' '; - - injectMonthName (os, itr->mMonth); + std::ostringstream os; - const std::string& dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); - os << " (" << dayStr << " " << (itr->mDay + 1) << ')'; + os + << itr->mDayOfMonth << ' ' + << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth) + << " (" << dayStr << " " << (itr->mDay + 1) << ')'; timestamp_buffer = os.str (); } @@ -270,7 +270,7 @@ struct JournalViewModelImpl : JournalViewModel { for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j) { - if (i->mInfoId == *j) + if (i->mInfoId == j->mInfoId) visitor (JournalEntryImpl (this, i)); } } @@ -290,9 +290,7 @@ struct JournalViewModelImpl : JournalViewModel void visitTopicName (TopicId topicId, boost::function visitor) const { MWDialogue::Topic const & topic = * reinterpret_cast (topicId); - // This is to get the correct case for the topic - const std::string& name = MWBase::Environment::get().getWorld()->getStore().get().find(topic.getName())->mId; - visitor (toUtf8Span (name)); + visitor (toUtf8Span (topic.getName())); } void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const @@ -304,10 +302,7 @@ struct JournalViewModelImpl : JournalViewModel if (i->first [0] != std::tolower (character, mLocale)) continue; - // This is to get the correct case for the topic - const std::string& name = MWBase::Environment::get().getWorld()->getStore().get().find(i->first)->mId; - - visitor (TopicId (&i->second), toUtf8Span (name)); + visitor (TopicId (&i->second), toUtf8Span (i->second.getName())); } } @@ -316,24 +311,18 @@ struct JournalViewModelImpl : JournalViewModel { MWDialogue::Topic const & mTopic; - mutable std::string source_buffer; - TopicEntryImpl (JournalViewModelImpl const * model, MWDialogue::Topic const & topic, iterator_t itr) : BaseEntry (model, itr), mTopic (topic) {} std::string getText () const { - /// \todo defines are not replaced (%PCName etc). should probably be done elsewhere though since we need the actor - return mTopic.getEntry (*itr).getText(MWBase::Environment::get().getWorld()->getStore()); - + return itr->getText(); } Utf8Span source () const { - if (source_buffer.empty ()) - source_buffer = "someone"; - return toUtf8Span (source_buffer); + return toUtf8Span (itr->mActorName); } }; @@ -349,38 +338,6 @@ struct JournalViewModelImpl : JournalViewModel } }; -static void injectMonthName (std::ostream & os, int month) -{ - MyGUI::LanguageManager& lm = MyGUI::LanguageManager::getInstance(); - - if (month == 0) - os << lm.replaceTags ("#{sMonthMorningstar}"); - else if (month == 1) - os << lm.replaceTags ("#{sMonthSunsdawn}"); - else if (month == 2) - os << lm.replaceTags ("#{sMonthFirstseed}"); - else if (month == 3) - os << lm.replaceTags ("#{sMonthRainshand}"); - else if (month == 4) - os << lm.replaceTags ("#{sMonthSecondseed}"); - else if (month == 5) - os << lm.replaceTags ("#{sMonthMidyear}"); - else if (month == 6) - os << lm.replaceTags ("#{sMonthSunsheight}"); - else if (month == 7) - os << lm.replaceTags ("#{sMonthLastseed}"); - else if (month == 8) - os << lm.replaceTags ("#{sMonthHeartfire}"); - else if (month == 9) - os << lm.replaceTags ("#{sMonthFrostfall}"); - else if (month == 10) - os << lm.replaceTags ("#{sMonthSunsdusk}"); - else if (month == 11) - os << lm.replaceTags ("#{sMonthEveningstar}"); - else - os << month; -} - JournalViewModel::Ptr JournalViewModel::create () { return boost::make_shared (); diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index ab8dc1584..f3c9e9c73 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -137,15 +137,28 @@ namespace getPage (QuestsPage)->adviseLinkClicked (callback); } - adjustButton(OptionsBTN); + adjustButton(OptionsBTN, true); adjustButton(PrevPageBTN); adjustButton(NextPageBTN); adjustButton(CloseBTN); adjustButton(CancelBTN); - adjustButton(ShowAllBTN); - adjustButton(ShowActiveBTN); + adjustButton(ShowAllBTN, true); + adjustButton(ShowActiveBTN, true); adjustButton(JournalBTN); + MWGui::ImageButton* optionsButton = getWidget(OptionsBTN); + if (optionsButton->getWidth() == 0) + { + // If tribunal is not installed (-> no options button), we still want the Topics button available, + // so place it where the options button would have been + MWGui::ImageButton* topicsButton = getWidget(TopicsBTN); + topicsButton->detachFromWidget(); + topicsButton->attachToWidget(optionsButton->getParent()); + topicsButton->setPosition(optionsButton->getPosition()); + topicsButton->eventMouseButtonClick.clear(); + topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); + } + MWGui::ImageButton* nextButton = getWidget(NextPageBTN); if (nextButton->getSize().width == 64) { @@ -155,7 +168,7 @@ namespace } adjustButton(TopicsBTN); - adjustButton(QuestsBTN); + adjustButton(QuestsBTN, true); int width = getWidget(TopicsBTN)->getSize().width + getWidget(QuestsBTN)->getSize().width; int topicsWidth = getWidget(TopicsBTN)->getSize().width; int pageWidth = getWidget(RightBookPage)->getSize().width; @@ -167,12 +180,12 @@ namespace mAllQuests = false; } - void adjustButton (char const * name) + void adjustButton (char const * name, bool optional = false) { MWGui::ImageButton* button = getWidget(name); - MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(); - button->setSize(button->getRequestedSize()); + MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(!optional); + button->setSize(button->getRequestedSize(!optional)); if (button->getAlign().isRight()) button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 7c0191d49..f56d80883 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -6,7 +6,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" @@ -59,7 +58,7 @@ namespace MWGui void LevelupDialog::setAttributeValues() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player); MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); @@ -115,7 +114,7 @@ namespace MWGui void LevelupDialog::open() { MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player); MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); @@ -155,8 +154,7 @@ namespace MWGui void LevelupDialog::onOkButtonClicked (MyGUI::Widget* sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); if (mSpentAttributes.size() < 3) @@ -166,14 +164,14 @@ namespace MWGui // increase attributes for (int i=0; i<3; ++i) { - MWMechanics::Stat& attribute = creatureStats.getAttribute(mSpentAttributes[i]); + MWMechanics::AttributeValue attribute = pcStats.getAttribute(mSpentAttributes[i]); attribute.setBase (attribute.getBase () + pcStats.getLevelupAttributeMultiplier (mSpentAttributes[i])); if (attribute.getBase() >= 100) attribute.setBase(100); + pcStats.setAttribute(mSpentAttributes[i], attribute); } - creatureStats.levelUp(); pcStats.levelUp (); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Levelup); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 4bd383c2f..868b58209 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,8 +1,6 @@ #include "loadingscreen.hpp" #include -#include -#include #include @@ -199,28 +197,7 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true); - Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain(mWindow->getViewport(0)); - - bool hasCompositor = chain->getCompositor ("gbufferFinalizer"); - - - if (!hasCompositor) - { - mWindow->getViewport(0)->setClearEveryFrame(false); - } - else - { - if (!mFirstLoad) - { - mBackgroundMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(chain->getCompositor ("gbufferFinalizer")->getTextureInstance ("no_mrt_output", 0)->getName()); - mRectangle->setVisible(true); - } - - for (unsigned int i = 0; igetNumCompositors(); ++i) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), false); - } - } + mWindow->getViewport(0)->setClearEveryFrame(false); // First, swap buffers from last draw, then, queue an update of the // window contents, but don't swap buffers (which would have @@ -231,15 +208,8 @@ namespace MWGui mWindow->update(false); - if (!hasCompositor) - mWindow->getViewport(0)->setClearEveryFrame(true); - else - { - for (unsigned int i = 0; igetNumCompositors(); ++i) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), true); - } - } + mWindow->getViewport(0)->setClearEveryFrame(true); + mRectangle->setVisible(false); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 1db6e9ecd..da1992474 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,90 +1,148 @@ #include "mainmenu.hpp" -#include - #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/statemanager.hpp" + +#include "../mwstate/character.hpp" + +#include "savegamedialog.hpp" namespace MWGui { MainMenu::MainMenu(int w, int h) : OEngine::GUI::Layout("openmw_mainmenu.layout") - , mButtonBox(0) + , mButtonBox(0), mWidth (w), mHeight (h) + , mSaveGameDialog(NULL) + { + updateMenu(); + } + + MainMenu::~MainMenu() { - onResChange(w,h); + delete mSaveGameDialog; } void MainMenu::onResChange(int w, int h) { - setCoord(0,0,w,h); + mWidth = w; + mHeight = h; + + updateMenu(); + } + void MainMenu::setVisible (bool visible) + { + if (visible) + updateMenu(); - if (mButtonBox) - MyGUI::Gui::getInstance ().destroyWidget(mButtonBox); + OEngine::GUI::Layout::setVisible (visible); + } + + void MainMenu::onButtonClicked(MyGUI::Widget *sender) + { + std::string name = *sender->getUserData(); + MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); + if (name == "return") + { + MWBase::Environment::get().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); + MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu); + } + else if (name == "options") + MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); + else if (name == "exitgame") + MWBase::Environment::get().getStateManager()->requestQuit(); + else if (name == "newgame") + { + MWBase::Environment::get().getStateManager()->newGame(); + } + + else + { + if (!mSaveGameDialog) + mSaveGameDialog = new SaveGameDialog(); + if (name == "loadgame") + mSaveGameDialog->setLoadOrSave(true); + else if (name == "savegame") + mSaveGameDialog->setLoadOrSave(false); + mSaveGameDialog->setVisible(true); + } + } + + void MainMenu::updateMenu() + { + setCoord(0,0, mWidth, mHeight); + + + if (!mButtonBox) + mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); - mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); int curH = 0; + MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); + std::vector buttons; - buttons.push_back("return"); + + if (state==MWBase::StateManager::State_Running) + buttons.push_back("return"); + buttons.push_back("newgame"); - //buttons.push_back("loadgame"); - //buttons.push_back("savegame"); + + if (MWBase::Environment::get().getStateManager()->characterBegin()!= + MWBase::Environment::get().getStateManager()->characterEnd()) + buttons.push_back("loadgame"); + + if (state==MWBase::StateManager::State_Running && + MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) + buttons.push_back("savegame"); + buttons.push_back("options"); //buttons.push_back("credits"); buttons.push_back("exitgame"); - int maxwidth = 0; - - mButtons.clear(); + // Create new buttons if needed 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; - - if (requested.width > maxwidth) - maxwidth = requested.width; + if (mButtons.find(*it) == mButtons.end()) + { + 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"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); + button->setUserData(std::string(*it)); + mButtons[*it] = button; + } } + + // Start by hiding all buttons + int maxwidth = 0; for (std::map::iterator it = mButtons.begin(); it != mButtons.end(); ++it) { + it->second->setVisible(false); MyGUI::IntSize requested = it->second->getRequestedSize(); - it->second->setCoord((maxwidth-requested.width) / 2, it->second->getTop(), requested.width, requested.height); + if (requested.width > maxwidth) + maxwidth = requested.width; } - mButtonBox->setCoord (w/2 - maxwidth/2, h/2 - curH/2, maxwidth, curH); - } - - void MainMenu::onButtonClicked(MyGUI::Widget *sender) - { - MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); - if (sender == mButtons["return"]) - { - MWBase::Environment::get().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); - 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 (); - else if (sender == mButtons["newgame"]) + // Now show and position the ones we want + for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) { - MWBase::Environment::get().getWorld()->startNewGame(); - MWBase::Environment::get().getWindowManager()->setNewGame(true); - MWBase::Environment::get().getDialogueManager()->clear(); - MWBase::Environment::get().getJournal()->clear(); + assert(mButtons.find(*it) != mButtons.end()); + MWGui::ImageButton* button = mButtons[*it]; + button->setVisible(true); + MyGUI::IntSize requested = button->getRequestedSize(); + button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, requested.height); + curH += requested.height; } - } + mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH); + + } } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 4e76a64df..6d52f26d5 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -5,19 +5,33 @@ namespace MWGui { + class SaveGameDialog; + class MainMenu : public OEngine::GUI::Layout { - public: - MainMenu(int w, int h); + int mWidth; + int mHeight; + + public: + + MainMenu(int w, int h); + ~MainMenu(); + + void onResChange(int w, int h); + + virtual void setVisible (bool visible); + + private: + + MyGUI::Widget* mButtonBox; - void onResChange(int w, int h); + std::map mButtons; - private: - MyGUI::Widget* mButtonBox; + void onButtonClicked (MyGUI::Widget* sender); - std::map mButtons; + void updateMenu(); - void onButtonClicked (MyGUI::Widget* sender); + SaveGameDialog* mSaveGameDialog; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 5ed002d7b..1b4af6d82 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -103,27 +103,80 @@ namespace MWGui void LocalMapBase::onMarkerFocused (MyGUI::Widget* w1, MyGUI::Widget* w2) { + // Workaround to not make the marker visible if it's under fog of war applyFogOfWar (); } void LocalMapBase::onMarkerUnfocused (MyGUI::Widget* w1, MyGUI::Widget* w2) { + // Workaround to not make the marker visible if it's under fog of war applyFogOfWar (); } + MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerPosition& markerPos) + { + MyGUI::IntPoint widgetPos; + // normalized cell coordinates + float nX,nY; + + markerPos.interior = mInterior; + + if (!mInterior) + { + int cellX, cellY; + MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); + const int cellSize = 8192; + nX = (worldX - cellSize * cellX) / cellSize; + // Image space is -Y up, cells are Y up + nY = 1 - (worldY - cellSize * cellY) / cellSize; + + float cellDx = cellX - mCurX; + float cellDy = cellY - mCurY; + + markerPos.cellX = cellX; + markerPos.cellY = cellY; + + widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellDx) * 512, + nY * 512 - (cellDy-1) * 512); + } + else + { + int cellX, cellY; + Ogre::Vector2 worldPos (worldX, worldY); + MWBase::Environment::get().getWorld ()->getInteriorMapPosition (worldPos, nX, nY, cellX, cellY); + + markerPos.cellX = cellX; + markerPos.cellY = cellY; + + widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellX-mCurX) * 512, + nY * 512 + (1+cellY-mCurY) * 512); + } + + markerPos.nX = nX; + markerPos.nY = nY; + return widgetPos; + } + void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { - if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell + if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) + return; // don't do anything if we're still in the same cell + + mCurX = x; + mCurY = y; + mInterior = interior; + mChanged = false; // clear all previous markers for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i) { - if (mLocalMap->getChildAt(i)->getName ().substr (0, 6) == "Marker") + if (mLocalMap->getChildAt(i)->getName ().substr (0, 4) == "Door") { MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i)); } } + // Update the map textures for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) @@ -138,78 +191,57 @@ namespace MWGui box->setImageTexture(image); else box->setImageTexture("black.png"); + } + } + MWBase::World* world = MWBase::Environment::get().getWorld(); - // door markers - - // interior map only consists of one cell, so handle the markers only once - if (interior && (mx != 2 || my != 2)) - continue; - - MWWorld::CellStore* cell; - if (interior) - cell = MWBase::Environment::get().getWorld ()->getInterior (mPrefix); - else - cell = MWBase::Environment::get().getWorld ()->getExterior (x+mx-1, y-(my-1)); - - std::vector doors = MWBase::Environment::get().getWorld ()->getDoorMarkers (cell); - - for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) + // Retrieve the door markers we want to show + std::vector doors; + if (interior) + { + MWWorld::CellStore* cell = world->getInterior (mPrefix); + world->getDoorMarkers(cell, doors); + } + else + { + for (int dX=-1; dX<2; ++dX) + { + for (int dY=-1; dY<2; ++dY) { - MWBase::World::DoorMarker marker = *it; - - // convert world coordinates to normalized cell coordinates - MyGUI::IntCoord widgetCoord; - float nX,nY; - int cellDx, cellDy; - if (!interior) - { - const int cellSize = 8192; - - nX = (marker.x - cellSize * (x+mx-1)) / cellSize; - nY = 1 - (marker.y - cellSize * (y-(my-1))) / cellSize; - - widgetCoord = MyGUI::IntCoord(nX * 512 - 4 + mx * 512, nY * 512 - 4 + my * 512, 8, 8); - } - else - { - 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); - } - - static int counter = 0; - ++counter; - MyGUI::Button* markerWidget = mLocalMap->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(counter)); - markerWidget->setImageResource("DoorMarker"); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", marker.name); - markerWidget->setUserString("IsMarker", "true"); - markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused); - markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused); - - MarkerPosition markerPos; - markerPos.interior = interior; - markerPos.cellX = interior ? cellDx : x + mx - 1; - markerPos.cellY = interior ? cellDy : y + ((my - 1)*-1); - markerPos.nX = nX; - markerPos.nY = nY; - - markerWidget->setUserData(markerPos); + MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); + world->getDoorMarkers(cell, doors); } - - } } - mInterior = interior; - mCurX = x; - mCurY = y; - mChanged = false; - // fog of war + // Create a widget for each marker + int counter = 0; + for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) + { + MWBase::World::DoorMarker marker = *it; + + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, markerPos); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + ++counter; + MyGUI::Button* markerWidget = mLocalMap->createWidget("ButtonImage", + widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(counter)); + markerWidget->setImageResource("DoorMarker"); + markerWidget->setUserString("ToolTipType", "Layout"); + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + markerWidget->setUserString("Caption_TextOneLine", marker.name); + markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused); + markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused); + // Used by tooltips to not show the tooltip if marker is hidden by fog of war + markerWidget->setUserString("IsMarker", "true"); + markerWidget->setUserData(markerPos); + } + + updateMarkers(); + applyFogOfWar(); // set the compass texture again, because MyGUI determines sorting of ImageBox widgets @@ -222,6 +254,8 @@ namespace MWGui void LocalMapBase::setPlayerPos(const float x, const float y) { + updateMarkers(); + if (x == mLastPositionX && y == mLastPositionY) return; @@ -255,12 +289,96 @@ namespace MWGui mLastDirectionY = y; } + void LocalMapBase::addDetectionMarkers(int type) + { + std::vector markers; + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->listDetectedReferences( + world->getPlayerPtr(), + markers, MWBase::World::DetectionType(type)); + if (markers.empty()) + return; + + std::string markerTexture; + MyGUI::Colour markerColour; + if (type == MWBase::World::Detect_Creature) + { + markerTexture = "textures\\menu_map_dcreature.dds"; + markerColour = MyGUI::Colour(1,0,0,1); + } + if (type == MWBase::World::Detect_Key) + { + markerTexture = "textures\\menu_map_dkey.dds"; + markerColour = MyGUI::Colour(0,1,0,1); + } + if (type == MWBase::World::Detect_Enchantment) + { + markerTexture = "textures\\menu_map_dmagic.dds"; + markerColour = MyGUI::Colour(0,0,1,1); + } + + int counter = 0; + for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) + { + const ESM::Position& worldPos = it->getRefData().getPosition(); + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + ++counter; + MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", + widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(counter)); + markerWidget->setImageTexture(markerTexture); + markerWidget->setUserString("IsMarker", "true"); + markerWidget->setUserData(markerPos); + markerWidget->setColour(markerColour); + } + } + + void LocalMapBase::updateMarkers() + { + // clear all previous markers + for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i) + { + if (mLocalMap->getChildAt(i)->getName ().substr (0, 6) == "Marker") + { + MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i)); + } + } + + addDetectionMarkers(MWBase::World::Detect_Creature); + addDetectionMarkers(MWBase::World::Detect_Key); + addDetectionMarkers(MWBase::World::Detect_Enchantment); + + // Add marker for the spot marked with Mark magic effect + MWWorld::CellStore* markedCell = NULL; + ESM::Position markedPosition; + MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell && markedCell->isExterior() == !mInterior + && (!mInterior || Misc::StringUtils::ciEqual(markedCell->mCell->mName, mPrefix))) + { + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", + widgetCoord, MyGUI::Align::Default, "MarkerMarked"); + markerWidget->setImageTexture("textures\\menu_map_smark.dds"); + markerWidget->setUserString("IsMarker", "true"); + markerWidget->setUserData(markerPos); + } + } + // ------------------------------------------------------------------------------------------ - MapWindow::MapWindow(const std::string& cacheDir) - : MWGui::WindowPinnableBase("openmw_map_window.layout") + MapWindow::MapWindow(DragAndDrop* drag, const std::string& cacheDir) + : WindowPinnableBase("openmw_map_window.layout") + , NoDrop(drag, mMainWidget) , mGlobal(false) , mGlobalMap(0) + , mGlobalMapRender(0) { setCoord(500,0,320,300); @@ -317,8 +435,8 @@ namespace MWGui static int _counter=0; - MyGUI::Button* markerWidget = mGlobalMapImage->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(_counter)); + MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget("ButtonImage", + widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(_counter)); markerWidget->setImageResource("DoorMarker"); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); @@ -382,10 +500,11 @@ namespace MWGui mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - for (unsigned int i=0; igetChildCount (); ++i) + // force markers to foreground + for (unsigned int i=0; igetChildCount (); ++i) { - if (mGlobalMapImage->getChildAt (i)->getName().substr(0,6) == "Marker") - mGlobalMapImage->getChildAt (i)->castType()->setImageResource("DoorMarker"); + if (mGlobalMapOverlay->getChildAt (i)->getName().substr(0,4) == "Door") + mGlobalMapOverlay->getChildAt (i)->castType()->setImageResource("DoorMarker"); } globalMapUpdatePlayer(); @@ -395,20 +514,18 @@ namespace MWGui 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().y); - - float worldX, worldY; - mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY); - worldX *= mGlobalMapRender->getWidth(); - worldY *= mGlobalMapRender->getHeight(); - - - // for interiors, we have no choice other than using the last position & direction. - /// \todo save this last position in the savegame? + // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition if (MWBase::Environment::get().getWorld ()->isCellExterior ()) { + Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedPosition (); + Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedOrientation (); + Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y); + + float worldX, worldY; + mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY); + worldX *= mGlobalMapRender->getWidth(); + worldY *= mGlobalMapRender->getHeight(); + mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(worldX - 16, worldY - 16)); MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); @@ -443,4 +560,46 @@ namespace MWGui "#{sWorld}"); } + void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) + { + float x, y; + mGlobalMapRender->worldPosToImageSpace (worldX, worldY, x, y); + x *= mGlobalMapRender->getWidth(); + y *= mGlobalMapRender->getHeight(); + + mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(x - 16, y - 16)); + + // set the view offset so that player is in the center + MyGUI::IntSize viewsize = mGlobalMap->getSize(); + MyGUI::IntPoint viewoffs(0.5*viewsize.width - x, 0.5*viewsize.height - y); + mGlobalMap->setViewOffset(viewoffs); + } + + void MapWindow::clear() + { + mGlobalMapRender->clear(); + + while (mEventBoxGlobal->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mEventBoxGlobal->getChildAt(0)); + while (mGlobalMapOverlay->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0)); + } + + void MapWindow::write(ESM::ESMWriter &writer) + { + mGlobalMapRender->write(writer); + } + + void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type) + { + std::vector > exploredCells; + mGlobalMapRender->readRecord(reader, type, exploredCells); + + for (std::vector >::iterator it = exploredCells.begin(); it != exploredCells.end(); ++it) + { + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); + if (cell && !cell->mName.empty()) + addVisitedLocation(cell->mName, it->first, it->second); + } + } } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 5518ab4a8..1e52ff26a 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H +#include + #include "windowpinnablebase.hpp" namespace MWRender @@ -8,6 +10,12 @@ namespace MWRender class GlobalMap; } +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + namespace Loading { class Listener; @@ -55,9 +63,16 @@ namespace MWGui void onMarkerFocused(MyGUI::Widget* w1, MyGUI::Widget* w2); void onMarkerUnfocused(MyGUI::Widget* w1, MyGUI::Widget* w2); + MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerPosition& markerPos); + virtual void notifyPlayerUpdate() {} virtual void notifyMapChanged() {} + // Update markers (Detect X effects, Mark/Recall effects) + // Note, door markers handled in setActiveCell + void updateMarkers(); + void addDetectionMarkers(int type); + OEngine::GUI::Layout* mLayout; bool mMapDragAndDrop; @@ -68,10 +83,10 @@ namespace MWGui float mLastDirectionY; }; - class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase + class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: - MapWindow(const std::string& cacheDir); + MapWindow(DragAndDrop* drag, const std::string& cacheDir); virtual ~MapWindow(); void setCellName(const std::string& cellName); @@ -81,8 +96,18 @@ namespace MWGui void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map void cellExplored(int x, int y); + void setGlobalMapPlayerPosition (float worldX, float worldY); + virtual void open(); + void onFrame(float dt) { NoDrop::onFrame(dt); } + + /// Clear all savegame-specific data + void clear(); + + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type); + private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 530594dda..3c3335d8b 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -8,13 +8,9 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "inventorywindow.hpp" -#include "tradewindow.hpp" - namespace MWGui { @@ -37,7 +33,9 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); @@ -70,8 +68,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) MyGUI::Button* button = - mList->createWidget( - (price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton", + mList->createWidget("SandTextButton", 0, currentY, 0, @@ -81,7 +78,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) currentY += 18; - button->setEnabled(price<=MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()); + button->setEnabled(price<=playerGold); button->setUserString("Price", boost::lexical_cast(price)); button->setUserData(*iter); button->setCaptionWithReplacing(name); @@ -94,7 +91,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); mGoldLabel->setCaptionWithReplacing("#{sGold}: " - + boost::lexical_cast(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())); + + boost::lexical_cast(playerGold)); } void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) @@ -119,7 +116,9 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); int price = boost::lexical_cast(sender->getUserString("Price")); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); startRepair(mActor); } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 48d7ec171..1ce167c33 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -8,58 +8,45 @@ namespace MWGui { - MessageBoxManager::MessageBoxManager () + MessageBoxManager::MessageBoxManager (float timePerChar) { - // defines - mMessageBoxSpeed = 0.1; mInterMessageBoxe = NULL; mStaticMessageBox = NULL; mLastButtonPressed = -1; + mMessageBoxSpeed = timePerChar; } - void MessageBoxManager::onFrame (float frameDuration) + MessageBoxManager::~MessageBoxManager () { - std::vector::iterator it; - for(it = mTimers.begin(); it != mTimers.end();) + std::vector::iterator it(mMessageBoxes.begin()); + for (; it != mMessageBoxes.end(); ++it) { - // if this messagebox is already deleted, remove the timer and move on - if (std::find(mMessageBoxes.begin(), mMessageBoxes.end(), it->messageBox) == mMessageBoxes.end()) - { - it = mTimers.erase(it); - continue; - } + delete *it; + } + } - it->current += frameDuration; - if(it->current >= it->max) + void MessageBoxManager::onFrame (float frameDuration) + { + std::vector::iterator it; + for(it = mMessageBoxes.begin(); it != mMessageBoxes.end();) + { + (*it)->mCurrentTime += frameDuration; + if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox) { - it->messageBox->mMarkedToDelete = true; - - if(*mMessageBoxes.begin() == it->messageBox) // if this box is the last one - { - // collect all with mMarkedToDelete and delete them. - // and place the other messageboxes on the right position - int height = 0; - std::vector::iterator it2 = mMessageBoxes.begin(); - while(it2 != mMessageBoxes.end()) - { - if((*it2)->mMarkedToDelete) - { - delete (*it2); - it2 = mMessageBoxes.erase(it2); - } - else { - (*it2)->update(height); - height += (*it2)->getHeight(); - ++it2; - } - } - } - it = mTimers.erase(it); + delete *it; + it = mMessageBoxes.erase(it); } else - { ++it; - } + } + + float height = 0; + it = mMessageBoxes.begin(); + while(it != mMessageBoxes.end()) + { + (*it)->update(height); + height += (*it)->getHeight(); + ++it; } if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) { @@ -74,14 +61,14 @@ namespace MWGui void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { MessageBox *box = new MessageBox(*this, message); + box->mCurrentTime = 0; + std::string realMessage = MyGUI::LanguageManager::getInstance().replaceTags(message); + box->mMaxTime = realMessage.length()*mMessageBoxSpeed; if(stat) mStaticMessageBox = box; - else - removeMessageBox(message.length()*mMessageBoxSpeed, box); mMessageBoxes.push_back(box); - std::vector::iterator it; if(mMessageBoxes.size() > 3) { delete *mMessageBoxes.begin(); @@ -89,7 +76,7 @@ namespace MWGui } int height = 0; - for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) + for(std::vector::iterator it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) { (*it)->update(height); height += (*it)->getHeight(); @@ -119,15 +106,6 @@ namespace MWGui return mInterMessageBoxe != NULL; } - void MessageBoxManager::removeMessageBox (float time, MessageBox *msgbox) - { - MessageBoxManagerTimer timer; - timer.current = 0; - timer.max = time; - timer.messageBox = msgbox; - - mTimers.insert(mTimers.end(), timer); - } bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) { @@ -149,12 +127,6 @@ namespace MWGui mMessageBoxSpeed = speed; } - void MessageBoxManager::okayPressed () - { - if(mInterMessageBoxe != NULL) - mInterMessageBoxe->okayPressed(); - } - int MessageBoxManager::readPressedButton () { int pressed = mLastButtonPressed; @@ -169,56 +141,32 @@ namespace MWGui : Layout("openmw_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mMessage(message) + , mCurrentTime(0) + , mMaxTime(0) { // defines - mFixedWidth = 300; mBottomPadding = 20; mNextBoxPadding = 20; - mMarkedToDelete = false; getWidget(mMessageWidget, "message"); mMessageWidget->setOverflowToTheLeft(true); mMessageWidget->setCaptionWithReplacing(mMessage); - - MyGUI::IntSize size; - size.width = mFixedWidth; - size.height = 100; // dummy - - MyGUI::IntCoord coord; - coord.left = 10; // dummy - coord.top = 10; // dummy - - mMessageWidget->setSize(size); - - MyGUI::IntSize textSize = mMessageWidget->getTextSize(); - - size.height = mHeight = textSize.height + 20; // this is the padding between the text and the box - - mMainWidget->setSize(size); - size.width -= 15; // this is to center the text (see messagebox.layout, Widget type="Edit" position="-2 -3 0 0") - mMessageWidget->setSize(size); } void MessageBox::update (int height) { MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); - MyGUI::IntCoord coord; - coord.left = (gameWindowSize.width - mFixedWidth)/2; - coord.top = (gameWindowSize.height - mHeight - height - mBottomPadding); - - MyGUI::IntSize size; - size.width = mFixedWidth; - size.height = mHeight; + MyGUI::IntPoint pos; + pos.left = (gameWindowSize.width - mMainWidget->getWidth())/2; + pos.top = (gameWindowSize.height - mMainWidget->getHeight() - height - mBottomPadding); - mMainWidget->setCoord(coord); - mMainWidget->setSize(size); - mMainWidget->setVisible(true); + mMainWidget->setPosition(pos); } int MessageBox::getHeight () { - return mHeight+mNextBoxPadding; // 20 is the padding between this and the next MessageBox + return mMainWidget->getHeight()+mNextBoxPadding; // 20 is the padding between this and the next MessageBox } @@ -297,11 +245,11 @@ namespace MWGui } mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; - MyGUI::IntCoord absCoord; - absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2; + MyGUI::IntPoint absPos; + absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; + absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; - mMainWidget->setCoord(absCoord); + mMainWidget->setPosition(absPos); mMainWidget->setSize(mainWidgetSize); MyGUI::IntCoord messageWidgetCoord; @@ -339,24 +287,6 @@ namespace MWGui else { mainWidgetSize.width = textSize.width + 3*textPadding; } - mainWidgetSize.height = textSize.height + 2*textPadding + textButtonPadding + buttonHeight * buttons.size() + buttonMainPadding; - - mMainWidget->setSize(mainWidgetSize); - - MyGUI::IntCoord absCoord; - absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2; - - mMainWidget->setCoord(absCoord); - mMainWidget->setSize(mainWidgetSize); - - - MyGUI::IntCoord messageWidgetCoord; - messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; - messageWidgetCoord.top = textPadding; - mMessageWidget->setCoord(messageWidgetCoord); - - mMessageWidget->setSize(textSize); MyGUI::IntCoord buttonCord; MyGUI::IntSize buttonSize(0, buttonHeight); @@ -378,24 +308,41 @@ namespace MWGui top += buttonSize.height + 2*buttonTopPadding; } - } - } + mainWidgetSize.height = top + buttonMainPadding; + mMainWidget->setSize(mainWidgetSize); - void InteractiveMessageBox::okayPressed() - { + MyGUI::IntPoint absPos; + absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; + absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; + mMainWidget->setPosition(absPos); + + MyGUI::IntCoord messageWidgetCoord; + messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; + messageWidgetCoord.top = textPadding; + messageWidgetCoord.width = textSize.width; + messageWidgetCoord.height = textSize.height; + mMessageWidget->setCoord(messageWidgetCoord); + } + + // Set key focus to "Ok" button 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) + if(Misc::StringUtils::ciEqual((*button)->getCaption(), ok)) { - buttonActivated(*button); - MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button); + (*button)->eventKeyButtonPressed += MyGUI::newDelegate(this, &InteractiveMessageBox::onKeyPressed); break; } } + } + void InteractiveMessageBox::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) + { + if (_key == MyGUI::KeyCode::Return || _key == MyGUI::KeyCode::NumpadEnter || _key == MyGUI::KeyCode::Space) + buttonActivated(_sender); } void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 63840cfe2..caa37008c 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -19,28 +19,20 @@ namespace MWGui class InteractiveMessageBox; class MessageBoxManager; class MessageBox; - - struct MessageBoxManagerTimer { - float current; - float max; - MessageBox *messageBox; - }; - class MessageBoxManager { public: - MessageBoxManager (); + MessageBoxManager (float timePerChar); + ~MessageBoxManager (); void onFrame (float frameDuration); void createMessageBox (const std::string& message, bool stat = false); void removeStaticMessageBox (); bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); - void removeMessageBox (float time, MessageBox *msgbox); bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); - void okayPressed(); int readPressedButton (); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; @@ -54,7 +46,6 @@ namespace MWGui std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; MessageBox* mStaticMessageBox; - std::vector mTimers; float mMessageBoxSpeed; int mLastButtonPressed; }; @@ -67,14 +58,13 @@ namespace MWGui int getHeight (); void update (int height); - bool mMarkedToDelete; + float mCurrentTime; + float mMaxTime; protected: MessageBoxManager& mMessageBoxManager; - int mHeight; const std::string& mMessage; MyGUI::EditBox* mMessageWidget; - int mFixedWidth; int mBottomPadding; int mNextBoxPadding; }; @@ -83,7 +73,6 @@ namespace MWGui { public: InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons); - void okayPressed (); void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); @@ -91,6 +80,7 @@ namespace MWGui private: void buttonActivated (MyGUI::Widget* _widget); + void onKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char); MessageBoxManager& mMessageBoxManager; MyGUI::EditBox* mMessageWidget; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 879fcb483..50d53abac 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -28,6 +28,7 @@ namespace MWGui GM_Travel, GM_SpellCreation, GM_Enchanting, + GM_Recharge, GM_Training, GM_MerchantRepair, diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 16be5f6cc..0196bf02d 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -9,7 +9,7 @@ namespace MWGui PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel) { mSourceModel = sourceModel; - int chance = MWWorld::Class::get(thief).getNpcStats(thief).getSkill(ESM::Skill::Sneak).getModified(); + int chance = thief.getClass().getSkill(thief, ESM::Skill::Sneak); mSourceModel->update(); for (size_t i = 0; igetItemCount(); ++i) @@ -40,6 +40,14 @@ namespace MWGui for (size_t i = 0; igetItemCount(); ++i) { const ItemStack& item = mSourceModel->getItem(i); + + // Bound items may not be stolen + if (item.mBase.getCellRef().mRefID.size() > 6 + && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") + { + continue; + } + if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end() && item.mType != ItemStack::Type_Equipped) mItems.push_back(item); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 5f749d3d3..ff13ae1af 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -2,10 +2,16 @@ #include -#include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwworld/class.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwgui/inventorywindow.hpp" #include "../mwgui/bookwindow.hpp" #include "../mwgui/scrollwindow.hpp" @@ -18,8 +24,8 @@ namespace { bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) { - int cmp = MWWorld::Class::get(left).getName(left).compare( - MWWorld::Class::get(right).getName(right)); + int cmp = left.getClass().getName(left).compare( + right.getClass().getName(right)); return cmp < 0; } @@ -126,7 +132,7 @@ namespace MWGui mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel); } mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); mAssignDialog->setVisible (false); } @@ -267,15 +273,47 @@ namespace MWGui QuickKeyType type = *button->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); + + if (type == Type_Item || type == Type_MagicItem) + { + MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); + // make sure the item is available + if (item.getRefData ().getCount() < 1) + { + // Try searching for a compatible replacement + std::string id = item.getCellRef().mRefID; + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, id)) + { + item = *it; + button->getChildAt(0)->setUserData(item); + break; + } + } + + if (item.getRefData().getCount() < 1) + { + // No replacement was found + MWBase::Environment::get().getWindowManager ()->messageBox ( + "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); + return; + } + } + } if (type == Type_Magic) { std::string spellId = button->getChildAt(0)->getUserString("Spell"); - spells.setSelectedSpell(spellId); + + // Make sure the player still has this spell + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); + if (!spells.hasSpell(spellId)) + return; store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); } @@ -283,40 +321,12 @@ namespace MWGui { MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() == 0) - { - MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); - return; - } - - boost::shared_ptr action = MWWorld::Class::get(item).use(item); - - action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - - // this is necessary for books/scrolls: if they are already in the player's inventory, - // 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 - MWBase::Environment::get().getWindowManager()->getBookWindow()->setTakeButtonShow(false); - MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false); - - // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } else if (type == Type_MagicItem) { MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() == 0) - { - MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); - return; - } - // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) @@ -331,17 +341,10 @@ namespace MWGui // equip, if it can be equipped if (!MWWorld::Class::get(item).getEquipmentSlots(item).first.empty()) { - // Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping - - MWWorld::ActionEquip action(item); - action.execute (MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()); - - // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } store.setSelectedEnchantItem(it); - spells.setSelectedSpell(""); MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); } } @@ -424,7 +427,7 @@ namespace MWGui const int spellHeight = 18; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 2c73226e3..3dff1b7e4 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -1,6 +1,5 @@ #include "race.hpp" -#include #include #include @@ -71,8 +70,7 @@ namespace MWGui setText("RaceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu5", "Race")); getWidget(mRaceList, "RaceList"); mRaceList->setScrollVisible(true); - mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); - mRaceList->eventListMouseItemActivate += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); + mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onAccept); mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus")); @@ -140,7 +138,7 @@ namespace MWGui size_t count = mRaceList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (boost::iequals(*mRaceList->getItemDataAt(i), raceId)) + if (Misc::StringUtils::ciEqual(*mRaceList->getItemDataAt(i), raceId)) { mRaceList->setIndexSelected(i); MyGUI::Button* okButton; @@ -230,7 +228,7 @@ namespace MWGui MyGUI::Button* okButton; getWidget(okButton, "OKButton"); const std::string *raceId = mRaceList->getItemDataAt(_index); - if (boost::iequals(mCurrentRaceId, *raceId)) + if (Misc::StringUtils::ciEqual(mCurrentRaceId, *raceId)) return; mCurrentRaceId = *raceId; @@ -242,6 +240,14 @@ namespace MWGui updateSpellPowers(); } + void RaceDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) + { + onSelectRace(_sender, _index); + if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) + return; + eventDone(this); + } + void RaceDialog::getBodyParts (int part, std::vector& out) { out.clear(); @@ -320,7 +326,7 @@ namespace MWGui continue; mRaceList->addItem(it->mName, it->mId); - if (boost::iequals(it->mId, mCurrentRaceId)) + if (Misc::StringUtils::ciEqual(it->mId, mCurrentRaceId)) mRaceList->setIndexSelected(index); ++index; } diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 914ae8096..340dcfa27 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -67,6 +67,7 @@ namespace MWGui void onSelectNextHair(MyGUI::Widget* _sender); void onSelectRace(MyGUI::ListBox* _sender, size_t _index); + void onAccept(MyGUI::ListBox* _sender, size_t _index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp new file mode 100644 index 000000000..683406d9e --- /dev/null +++ b/apps/openmw/mwgui/recharge.cpp @@ -0,0 +1,195 @@ +#include "recharge.hpp" + +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" + +#include "widgets.hpp" + +namespace MWGui +{ + +Recharge::Recharge() + : WindowBase("openmw_recharge_dialog.layout") +{ + getWidget(mBox, "Box"); + getWidget(mView, "View"); + getWidget(mGemBox, "GemBox"); + getWidget(mGemIcon, "GemIcon"); + getWidget(mChargeLabel, "ChargeLabel"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel); + + setVisible(false); +} + +void Recharge::open() +{ + center(); +} + +void Recharge::start (const MWWorld::Ptr &item) +{ + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(item).getInventoryIcon(item); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + mGemIcon->setImageTexture (path); + mGemIcon->setUserString("ToolTipType", "ItemPtr"); + mGemIcon->setUserData(item); + + updateView(); +} + +void Recharge::updateView() +{ + MWWorld::Ptr gem = *mGemIcon->getUserData(); + + std::string soul = gem.getCellRef().mSoul; + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + + mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast(creature->mData.mSoul)); + + bool toolBoxVisible = (gem.getRefData().getCount() != 0); + mGemBox->setVisible(toolBoxVisible); + + bool toolBoxWasVisible = (mBox->getPosition().top != mGemBox->getPosition().top); + + if (toolBoxVisible && !toolBoxWasVisible) + { + // shrink + mBox->setPosition(mBox->getPosition() + MyGUI::IntPoint(0, mGemBox->getSize().height)); + mBox->setSize(mBox->getSize() - MyGUI::IntSize(0,mGemBox->getSize().height)); + } + else if (!toolBoxVisible && toolBoxWasVisible) + { + // expand + mBox->setPosition(MyGUI::IntPoint (mBox->getPosition().left, mGemBox->getPosition().top)); + mBox->setSize(mBox->getSize() + MyGUI::IntSize(0,mGemBox->getSize().height)); + } + + while (mView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mView->getChildAt(0)); + + int currentY = 0; + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + for (MWWorld::ContainerStoreIterator iter (store.begin()); + iter!=store.end(); ++iter) + { + std::string enchantmentName = iter->getClass().getEnchantment(*iter); + if (enchantmentName.empty()) + continue; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge + || iter->getCellRef().mEnchantmentCharge == -1) + continue; + + MyGUI::TextBox* text = mView->createWidget ( + "SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default); + text->setCaption(MWWorld::Class::get(*iter).getName(*iter)); + text->setNeedMouseFocus(false); + currentY += 19; + + MyGUI::ImageBox* icon = mView->createWidget ( + "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + icon->setImageTexture (path); + icon->setUserString("ToolTipType", "ItemPtr"); + icon->setUserData(*iter); + icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked); + icon->eventMouseWheel += MyGUI::newDelegate(this, &Recharge::onMouseWheel); + + Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget + ("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default); + chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge); + chargeWidget->setNeedMouseFocus(false); + + currentY += 32 + 4; + } + mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY))); +} + +void Recharge::onCancel(MyGUI::Widget *sender) +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); +} + +void Recharge::onItemClicked(MyGUI::Widget *sender) +{ + MWWorld::Ptr gem = *mGemIcon->getUserData(); + + if (!gem.getRefData().getCount()) + return; + + MWWorld::Ptr item = *sender->getUserData(); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); + + float luckTerm = 0.1 * stats.getAttribute(ESM::Attribute::Luck).getModified(); + if (luckTerm < 1|| luckTerm > 10) + luckTerm = 1; + + float intelligenceTerm = 0.2 * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + + if (intelligenceTerm > 20) + intelligenceTerm = 20; + if (intelligenceTerm < 1) + intelligenceTerm = 1; + + float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < x) + { + std::string soul = gem.getCellRef().mSoul; + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + + float restored = creature->mData.mSoul * (roll / x); + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + item.getClass().getEnchantment(item)); + item.getCellRef().mEnchantmentCharge = + std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast(enchantment->mData.mCharge)); + + player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); + } + + gem.getContainerStore()->remove(gem, 1, player); + + if (gem.getRefData().getCount() == 0) + { + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->getString(); + message = boost::str(boost::format(message) % gem.getClass().getName(gem)); + MWBase::Environment::get().getWindowManager()->messageBox(message); + } + + updateView(); +} + +void Recharge::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (mView->getViewOffset().top + _rel*0.3 > 0) + mView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mView->setViewOffset(MyGUI::IntPoint(0, mView->getViewOffset().top + _rel*0.3)); +} + +} diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp new file mode 100644 index 000000000..2ffc5e10f --- /dev/null +++ b/apps/openmw/mwgui/recharge.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_MWGUI_RECHARGE_H +#define OPENMW_MWGUI_RECHARGE_H + +#include "windowbase.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + +class Recharge : public WindowBase +{ +public: + Recharge(); + + virtual void open(); + + void start (const MWWorld::Ptr& gem); + +protected: + MyGUI::Widget* mBox; + MyGUI::ScrollView* mView; + + MyGUI::Widget* mGemBox; + + MyGUI::ImageBox* mGemIcon; + + MyGUI::TextBox* mChargeLabel; + + MyGUI::Button* mCancelButton; + + void updateView(); + + void onItemClicked (MyGUI::Widget* sender); + void onCancel (MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + +}; + +} + +#endif diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 86a85be18..2ea0db64a 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -3,8 +3,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/player.hpp" - namespace MWGui { ReferenceInterface::ReferenceInterface() @@ -18,7 +16,7 @@ namespace MWGui void ReferenceInterface::checkReferenceAvailable() { - MWWorld::Ptr::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); + MWWorld::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); // check if player has changed cell, or count of the reference has become 0 if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL) diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 0bd4b0995..d729ee7fa 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -6,7 +6,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" @@ -88,7 +87,7 @@ void Repair::updateRepairView() int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index dfc86a547..e27e40ae6 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -65,7 +65,7 @@ namespace MWGui getWidget(attribute, std::string("Attribute") + boost::lexical_cast(idx)); mAttributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::sAttributeIds[idx]), attribute)); attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]); - attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue(0, 0)); + attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue()); } // Setup skills @@ -74,7 +74,7 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { - mSkillValues.insert(std::make_pair(i, MWMechanics::Stat())); + mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); mSkillWidgetMap.insert(std::make_pair(i, static_cast (0))); } @@ -152,7 +152,7 @@ namespace MWGui mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } - void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat& value) + void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value) { std::map::iterator attr = mAttributeWidgets.find(static_cast(attributeId)); if (attr == mAttributeWidgets.end()) @@ -161,7 +161,7 @@ namespace MWGui attr->second->setAttributeValue(value); } - void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat& value) + void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value) { mSkillValues[skillId] = value; MyGUI::TextBox* widget = mSkillWidgetMap[skillId]; @@ -279,9 +279,9 @@ namespace MWGui continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWMechanics::Stat &stat = mSkillValues.find(skillId)->second; - float base = stat.getBase(); - float modified = stat.getModified(); + const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; + int base = stat.getBase(); + int modified = stat.getModified(); std::string state = "normal"; if (modified > base) diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 1c24fec74..5d0767d21 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -38,10 +38,10 @@ namespace MWGui void setMagicka(const MWMechanics::DynamicStat& value); void setFatigue(const MWMechanics::DynamicStat& value); - void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat& value); + void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value); void configureSkills(const SkillList& major, const SkillList& minor); - void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat& value); + void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value); virtual void open(); @@ -85,7 +85,7 @@ namespace MWGui std::map mAttributeWidgets; SkillList mMajorSkills, mMinorSkills, mMiscSkills; - std::map > mSkillValues; + std::map mSkillValues; std::map mSkillWidgetMap; std::string mName, mRaceId, mBirthSignId; ESM::Class mKlass; diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp new file mode 100644 index 000000000..77ad98121 --- /dev/null +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -0,0 +1,285 @@ +#include "savegamedialog.hpp" +#include "widgets.hpp" + +#include +#include + +#include + +#include + +#include "../mwbase/statemanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwstate/character.hpp" + +#include "confirmationdialog.hpp" + +namespace MWGui +{ + SaveGameDialog::SaveGameDialog() + : WindowModal("openmw_savegame_dialog.layout") + , mSaving(true) + , mCurrentCharacter(NULL) + { + getWidget(mScreenshot, "Screenshot"); + getWidget(mCharacterSelection, "SelectCharacter"); + getWidget(mInfoText, "InfoText"); + getWidget(mOkButton, "OkButton"); + getWidget(mCancelButton, "CancelButton"); + getWidget(mSaveList, "SaveList"); + getWidget(mSaveNameEdit, "SaveNameEdit"); + getWidget(mSpacer, "Spacer"); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); + mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); + mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); + mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); + mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); + mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); + } + + void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos) + { + onSlotSelected(sender, pos); + accept(); + } + + void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) + { + // This might have previously been a save slot from the list. If so, that is no longer the case + mSaveList->setIndexSelected(MyGUI::ITEM_NONE); + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + } + + void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender) + { + accept(); + } + + void SaveGameDialog::open() + { + WindowModal::open(); + + mSaveNameEdit->setCaption (""); + if (mSaving) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit); + + center(); + + MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); + if (mgr->characterBegin() == mgr->characterEnd()) + return; + + mCurrentCharacter = mgr->getCurrentCharacter (false); + + std::string directory = + Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); + + mCharacterSelection->removeAllItems(); + + for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) + { + if (it->begin()!=it->end()) + { + std::stringstream title; + title << it->getSignature().mPlayerName; + title << " (Level " << it->getSignature().mPlayerLevel << " " << it->getSignature().mPlayerClass << ")"; + + mCharacterSelection->addItem (title.str()); + + if (mCurrentCharacter == &*it || + (!mCurrentCharacter && !mSaving && directory==Misc::StringUtils::lowerCase ( + it->begin()->mPath.parent_path().filename().string()))) + { + mCurrentCharacter = &*it; + mCharacterSelection->setIndexSelected(mCharacterSelection->getItemCount()-1); + } + } + } + + fillSaveList(); + + } + + void SaveGameDialog::setLoadOrSave(bool load) + { + mSaving = !load; + mSaveNameEdit->setVisible(!load); + mCharacterSelection->setUserString("Hidden", load ? "false" : "true"); + mCharacterSelection->setVisible(load); + mSpacer->setUserString("Hidden", load ? "false" : "true"); + + if (!load) + { + mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false); + } + + center(); + } + + void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender) + { + setVisible(false); + } + + void SaveGameDialog::onConfirmationGiven() + { + accept(true); + } + + void SaveGameDialog::accept(bool reallySure) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + + // Get the selected slot, if any + unsigned int i=0; + const MWState::Slot* slot = NULL; + + if (mCurrentCharacter) + { + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it,++i) + { + if (i == mSaveList->getIndexSelected()) + slot = &*it; + } + } + + if (mSaving) + { + // If overwriting an existing slot, ask for confirmation first + if (slot != NULL && !reallySure) + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage4}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); + dialog->eventCancelClicked.clear(); + return; + } + if (mSaveNameEdit->getCaption().empty()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}"); + return; + } + MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), slot); + } + else + { + if (mCurrentCharacter && slot) + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); + } + + setVisible(false); + + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_NoGame) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + } + + void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) + { + accept(); + } + + void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox *sender, size_t pos) + { + MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); + + unsigned int i=0; + const MWState::Character* character = NULL; + for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i) + { + if (i == pos) + character = &*it; + } + assert(character && "Can't find selected character"); + + mCurrentCharacter = character; + fillSaveList(); + } + + void SaveGameDialog::fillSaveList() + { + mSaveList->removeAllItems(); + if (!mCurrentCharacter) + return; + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + mSaveList->addItem(it->mProfile.mDescription); + } + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + } + + void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + { + mInfoText->setCaption(""); + mScreenshot->setImageTexture(""); + return; + } + + if (mSaving) + mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); + + const MWState::Slot* slot = NULL; + unsigned int i=0; + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) + { + if (i == pos) + slot = &*it; + } + assert(slot && "Can't find selected slot"); + + std::stringstream text; + time_t time = slot->mTimeStamp; + struct tm* timeinfo; + timeinfo = localtime(&time); + + text << asctime(timeinfo) << "\n"; + text << "Level " << slot->mProfile.mPlayerLevel << "\n"; + text << slot->mProfile.mPlayerCell << "\n"; + // text << "Time played: " << slot->mProfile.mTimePlayed << "\n"; + + int hour = int(slot->mProfile.mInGameTime.mGameHour); + bool pm = hour >= 12; + if (hour >= 13) hour -= 12; + if (hour == 0) hour = 12; + + text + << slot->mProfile.mInGameTime.mDay << " " + << MWBase::Environment::get().getWorld()->getMonthName(slot->mProfile.mInGameTime.mMonth) + << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); + + mInfoText->setCaptionWithReplacing(text.str()); + + // Decode screenshot + std::vector data = slot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :( + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + Ogre::Image image; + image.load(stream, "jpg"); + + const std::string textureName = "@savegame_screenshot"; + Ogre::TexturePtr texture; + texture = Ogre::TextureManager::getSingleton().getByName(textureName); + mScreenshot->setImageTexture(""); + if (texture.isNull()) + { + texture = Ogre::TextureManager::getSingleton().createManual(textureName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + image.getWidth(), image.getHeight(), 0, Ogre::PF_BYTE_RGBA, Ogre::TU_DYNAMIC_WRITE_ONLY); + } + texture->unload(); + texture->setWidth(image.getWidth()); + texture->setHeight(image.getHeight()); + texture->loadImage(image); + + mScreenshot->setImageTexture(textureName); + } +} diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp new file mode 100644 index 000000000..8d09a1cbc --- /dev/null +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -0,0 +1,54 @@ +#ifndef OPENMW_MWGUI_SAVEGAMEDIALOG_H +#define OPENMW_MWGUI_SAVEGAMEDIALOG_H + +#include "windowbase.hpp" + +namespace MWState +{ + class Character; +} + +namespace MWGui +{ + + class SaveGameDialog : public MWGui::WindowModal + { + public: + SaveGameDialog(); + + virtual void open(); + + void setLoadOrSave(bool load); + + private: + void onCancelButtonClicked (MyGUI::Widget* sender); + void onOkButtonClicked (MyGUI::Widget* sender); + void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); + void onSlotSelected (MyGUI::ListBox* sender, size_t pos); + void onSlotActivated (MyGUI::ListBox* sender, size_t pos); + void onEditSelectAccept (MyGUI::EditBox* sender); + void onSaveNameChanged (MyGUI::EditBox* sender); + void onConfirmationGiven(); + + void accept(bool reallySure=false); + + void fillSaveList(); + + MyGUI::ImageBox* mScreenshot; + bool mSaving; + + MyGUI::ComboBox* mCharacterSelection; + MyGUI::EditBox* mInfoText; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; + MyGUI::ListBox* mSaveList; + MyGUI::EditBox* mSaveNameEdit; + MyGUI::Widget* mSpacer; + + const MWState::Character* mCurrentCharacter; + + }; + +} + +#endif diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 48931b18e..e1970004c 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -6,7 +6,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/actiontake.hpp" -#include "../mwworld/player.hpp" #include "formatting.hpp" @@ -90,7 +89,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0); MWWorld::ActionTake take(mScroll); - take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + take.execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 923b9d01d..c99e2d0de 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -31,9 +31,7 @@ namespace std::string textureFilteringToStr(const std::string& val) { - if (val == "none") - return "None"; - else if (val == "anisotropic") + if (val == "anisotropic") return "Anisotropic"; else if (val == "bilinear") return "Bilinear"; @@ -94,6 +92,7 @@ namespace MWGui { getWidget(mOkButton, "OkButton"); getWidget(mBestAttackButton, "BestAttackButton"); + getWidget(mGrabCursorButton, "GrabCursorButton"); getWidget(mSubtitlesButton, "SubtitlesButton"); getWidget(mCrosshairButton, "CrosshairButton"); getWidget(mResolutionList, "ResolutionList"); @@ -135,6 +134,7 @@ namespace MWGui mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mBestAttackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mGrabCursorButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mInvertYButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); @@ -145,7 +145,7 @@ namespace MWGui mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectTerrainButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectActorsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mTextureFilteringButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringToggled); + mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mVSyncButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled); mMenuTransparencySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); @@ -157,7 +157,7 @@ namespace MWGui mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mShadowsTextureSize->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSize); + mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -203,6 +203,7 @@ namespace MWGui mSubtitlesButton->setCaptionWithReplacing(Settings::Manager::getBool("subtitles", "GUI") ? "#{sOn}" : "#{sOff}"); mCrosshairButton->setCaptionWithReplacing(Settings::Manager::getBool("crosshair", "HUD") ? "#{sOn}" : "#{sOff}"); mBestAttackButton->setCaptionWithReplacing(Settings::Manager::getBool("best attack", "Game") ? "#{sOn}" : "#{sOff}"); + mGrabCursorButton->setCaptionWithReplacing(Settings::Manager::getBool("grab cursor", "Input") ? "#{sOn}" : "#{sOff}"); float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin); mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1)); @@ -297,22 +298,9 @@ namespace MWGui mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); } - void SettingsWindow::onShadowTextureSize(MyGUI::Widget* _sender) + void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) { - std::string size = mShadowsTextureSize->getCaption(); - - if (size == "512") - size = "1024"; - else if (size == "1024") - size = "2048"; - else if (size == "2048") - size = "4096"; - else - size = "512"; - - mShadowsTextureSize->setCaption(size); - - Settings::Manager::setString("texture size", "Shadows", size); + Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos)); apply(); } @@ -408,7 +396,8 @@ namespace MWGui Settings::Manager::setBool("subtitles", "GUI", newState); else if (_sender == mBestAttackButton) Settings::Manager::setBool("best attack", "Game", newState); - + else if (_sender == mGrabCursorButton) + Settings::Manager::setBool("grab cursor", "Input", newState); apply(); } } @@ -482,22 +471,9 @@ namespace MWGui apply(); } - void SettingsWindow::onTextureFilteringToggled(MyGUI::Widget* _sender) + void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos) { - std::string current = Settings::Manager::getString("texture filtering", "General"); - std::string next; - if (current == "none") - next = "bilinear"; - else if (current == "bilinear") - next = "trilinear"; - else if (current == "trilinear") - next = "anisotropic"; - else - next = "none"; - - mTextureFilteringButton->setCaption(textureFilteringToStr(next)); - - Settings::Manager::setString("texture filtering", "General", next); + Settings::Manager::setString("texture filtering", "General", Misc::StringUtils::lowerCase(_sender->getItemNameAt(pos))); apply(); } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index a585bda7e..6b9ce414b 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -33,6 +33,7 @@ namespace MWGui MyGUI::Button* mSubtitlesButton; MyGUI::Button* mCrosshairButton; MyGUI::Button* mBestAttackButton; + MyGUI::Button* mGrabCursorButton; // graphics MyGUI::ListBox* mResolutionList; @@ -42,7 +43,7 @@ namespace MWGui MyGUI::ScrollBar* mViewDistanceSlider; MyGUI::ScrollBar* mFOVSlider; MyGUI::ScrollBar* mAnisotropySlider; - MyGUI::Button* mTextureFilteringButton; + MyGUI::ComboBox* mTextureFilteringButton; MyGUI::TextBox* mAnisotropyLabel; MyGUI::Widget* mAnisotropyBox; MyGUI::Button* mWaterShaderButton; @@ -55,7 +56,7 @@ namespace MWGui MyGUI::Button* mShadowsEnabledButton; MyGUI::Button* mShadowsLargeDistance; - MyGUI::Button* mShadowsTextureSize; + MyGUI::ComboBox* mShadowsTextureSize; MyGUI::Button* mActorShadows; MyGUI::Button* mStaticsShadows; MyGUI::Button* mMiscShadows; @@ -76,7 +77,7 @@ namespace MWGui void onOkButtonClicked(MyGUI::Widget* _sender); void onFpsToggled(MyGUI::Widget* _sender); - void onTextureFilteringToggled(MyGUI::Widget* _sender); + void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); void onButtonToggled(MyGUI::Widget* _sender); void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); @@ -85,7 +86,7 @@ namespace MWGui void onShadersToggled(MyGUI::Widget* _sender); void onShaderModeToggled(MyGUI::Widget* _sender); - void onShadowTextureSize(MyGUI::Widget* _sender); + void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp index b95eec0b6..6d70c85d9 100644 --- a/apps/openmw/mwgui/soulgemdialog.cpp +++ b/apps/openmw/mwgui/soulgemdialog.cpp @@ -21,7 +21,8 @@ namespace MWGui { if (button == 0) { - /// \todo show recharge enchanted item dialog here + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge); + MWBase::Environment::get().getWindowManager()->startRecharge(mSoulgem); } else { diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index a7fcfdd02..68aecf28d 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -8,14 +8,11 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "inventorywindow.hpp" -#include "tradewindow.hpp" - namespace MWGui { const int SpellBuyingWindow::sLineHeight = 18; @@ -43,15 +40,19 @@ namespace MWGui int price = spell->mData.mCost*store.get().find("fSpellValueMult")->getFloat(); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + MyGUI::Button* toAdd = mSpellsView->createWidget( - (price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton", + "SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default ); + toAdd->setEnabled(price<=playerGold); mCurrentY += sLineHeight; @@ -103,7 +104,7 @@ namespace MWGui bool SpellBuyingWindow::playerHasSpell(const std::string &id) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::Spells& playerSpells = MWWorld::Class::get (player).getCreatureStats (player).getSpells(); for (MWMechanics::Spells::TIterator it = playerSpells.begin(); it != playerSpells.end(); ++it) { @@ -117,17 +118,14 @@ namespace MWGui { int price = *_sender->getUserData(); - if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()>=price) - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - spells.add (mSpellsWidgetMap.find(_sender)->second); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); - startSpellBuying(mPtr); - - MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); - } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); + spells.add (mSpellsWidgetMap.find(_sender)->second); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + startSpellBuying(mPtr); + + MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); } void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender) @@ -137,7 +135,10 @@ namespace MWGui void SpellBuyingWindow::updateLabels() { - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index dc86fd825..469d7188e 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -3,18 +3,20 @@ #include #include "../mwbase/windowmanager.hpp" - #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "tooltips.hpp" #include "class.hpp" -#include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace { @@ -89,6 +91,8 @@ namespace MWGui mEffect.mMagnMax = 1; mEffect.mDuration = 1; mEffect.mArea = 0; + mEffect.mSkill = -1; + mEffect.mAttribute = -1; eventEffectAdded(mEffect); onRangeButtonClicked(mRangeButton); @@ -332,7 +336,10 @@ namespace MWGui return; } - if (boost::lexical_cast(mPriceLabel->getCaption()) > MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + if (boost::lexical_cast(mPriceLabel->getCaption()) > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; @@ -340,13 +347,12 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-boost::lexical_cast(mPriceLabel->getCaption())); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, boost::lexical_cast(mPriceLabel->getCaption()), player); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (spell->mId); @@ -411,7 +417,7 @@ namespace MWGui mPriceLabel->setCaption(boost::lexical_cast(int(price))); - float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayerPtr()); mSuccessChance->setCaption(boost::lexical_cast(int(chance))); } @@ -438,7 +444,7 @@ namespace MWGui { // get the list of magic effects that are known to the player - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 0c303485a..0cd665a87 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -9,7 +9,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -21,120 +20,42 @@ namespace MWGui { - void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) + void EffectSourceVisitor::visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MagicEffectInfo newEffectSource; + newEffectSource.mKey = key; + newEffectSource.mMagnitude = magnitude; + newEffectSource.mPermanent = mIsPermanent; + newEffectSource.mRemainingTime = remainingTime; + newEffectSource.mSource = sourceName; + + mEffectSources[key.mId].push_back(newEffectSource); + } - 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); - } - } + void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) + { + // TODO: Tracking add/remove/expire would be better than force updating every frame - // 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); - } - } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - // 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(); + EffectSourceVisitor visitor; - 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; + // permanent item enchantments & permanent spells + visitor.mIsPermanent = true; + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + store.visitEffectSources(visitor); + stats.getSpells().visitEffectSources(visitor); - effectInfo.mMagnitude = static_cast (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost)); - } + // now add lasting effects + visitor.mIsPermanent = false; + stats.getActiveSpells().visitEffectSources(visitor); - effects[effectIt->mEffectID].push_back (effectInfo); - } - } + std::map >& effects = visitor.mEffectSources; int w=2; @@ -268,55 +189,4 @@ namespace MWGui } } - - 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 index 818d67b5b..1bb80f3d4 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -2,6 +2,7 @@ #define MWGUI_SPELLICONS_H #include +#include #include "../mwmechanics/magiceffects.hpp" @@ -34,14 +35,24 @@ namespace MWGui bool mPermanent; // the effect is permanent }; + class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor + { + public: + bool mIsPermanent; + + std::map > mEffectSources; + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime = -1); + }; + 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; }; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index d5e3abc11..6b261a799 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -4,12 +4,15 @@ #include #include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/actionequip.hpp" +#include "../mwworld/class.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "spellicons.hpp" #include "inventorywindow.hpp" @@ -39,8 +42,9 @@ namespace namespace MWGui { - SpellWindow::SpellWindow() + SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") + , NoDrop(drag, mMainWidget) , mHeight(0) , mWidth(0) { @@ -51,8 +55,6 @@ namespace MWGui setCoord(498, 300, 302, 300); - updateSpells(); - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); } @@ -83,66 +85,13 @@ namespace MWGui // retrieve all player spells, divide them into Powers and Spells and sort them std::vector spellList; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); - // the following code switches between selected enchanted item and selected spell (only one of these - // can be active at a time) - std::string selectedSpell = spells.getSelectedSpell(); - MWWorld::Ptr selectedItem; - if (store.getSelectedEnchantItem() != store.end()) - { - selectedSpell = ""; - selectedItem = *store.getSelectedEnchantItem(); - - bool allowSelectedItem = true; - - // make sure that the item is still in the player inventory, otherwise it can't be selected - bool found = false; - for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) - { - if (*it == selectedItem) - found = true; - } - if (!found) - allowSelectedItem = false; - - // if the selected item can be equipped, make sure that it actually is equipped - std::pair, bool> slots; - slots = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem); - if (!slots.first.empty()) - { - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (store.getSlot(i) != store.end() && *store.getSlot(i) == selectedItem) - { - equipped = true; - break; - } - } - - if (!equipped) - allowSelectedItem = false; - } - - if (!allowSelectedItem) - { - store.setSelectedEnchantItem(store.end()); - spells.setSelectedSpell(""); - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - selectedItem = MWWorld::Ptr(); - } - } - - - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { spellList.push_back (it->first); - } const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -212,7 +161,7 @@ namespace MWGui t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - if (*it == selectedSpell) + if (*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()) t->setStateSelected(true); mHeight += spellHeight; @@ -231,7 +180,7 @@ namespace MWGui t->setUserString("Spell", *it); t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - t->setStateSelected(*it == selectedSpell); + t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); // cost / success chance MyGUI::Button* costChance = mSpellView->createWidget("SpellText", @@ -241,7 +190,7 @@ namespace MWGui costChance->setCaption(cost + "/" + chance); costChance->setTextAlign(MyGUI::Align::Right); costChance->setNeedMouseFocus(false); - costChance->setStateSelected(*it == selectedSpell); + costChance->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); mHeight += spellHeight; @@ -278,14 +227,23 @@ namespace MWGui t->setUserString("Equipped", equipped ? "true" : "false"); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - t->setStateSelected(item == selectedItem); + if (store.getSelectedEnchantItem() != store.end()) + t->setStateSelected(item == *store.getSelectedEnchantItem()); + // cost / charge MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - std::string cost = boost::lexical_cast(enchant->mData.mCost); - std::string charge = boost::lexical_cast(enchant->mData.mCharge); /// \todo track current charge + float enchantCost = enchant->mData.mCost; + int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant); + int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + + std::string cost = boost::lexical_cast(castCost); + int currentCharge = int(item.getCellRef().mEnchantmentCharge); + if (currentCharge == -1) + currentCharge = enchant->mData.mCharge; + std::string charge = boost::lexical_cast(currentCharge); if (enchant->mData.mType == ESM::Enchantment::CastOnce) { // this is Morrowind behaviour @@ -296,7 +254,8 @@ namespace MWGui costCharge->setCaption(cost + "/" + charge); costCharge->setTextAlign(MyGUI::Align::Right); costCharge->setNeedMouseFocus(false); - costCharge->setStateSelected(item == selectedItem); + if (store.getSelectedEnchantItem() != store.end()) + costCharge->setStateSelected(item == *store.getSelectedEnchantItem()); mHeight += spellHeight; } @@ -342,10 +301,8 @@ namespace MWGui void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::Spells& spells = stats.getSpells(); MWWorld::Ptr item = *_sender->getUserData(); // retrieve ContainerStoreIterator to the item @@ -363,17 +320,10 @@ namespace MWGui if (_sender->getUserString("Equipped") == "false" && !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty()) { - // Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping - - MWWorld::ActionEquip action(item); - action.execute (MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()); - - // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } store.setSelectedEnchantItem(it); - spells.setSelectedSpell(""); MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); updateSpells(); @@ -382,10 +332,8 @@ namespace MWGui void SpellWindow::onSpellSelected(MyGUI::Widget* _sender) { std::string spellId = _sender->getUserString("Spell"); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::Spells& spells = stats.getSpells(); if (MyGUI::InputManager::getInstance().isShiftPressed()) { @@ -413,7 +361,6 @@ namespace MWGui } else { - spells.setSelectedSpell(spellId); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); } @@ -439,15 +386,12 @@ namespace MWGui void SpellWindow::onDeleteSpellAccept() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); - if (spells.getSelectedSpell() == mSpellToDelete) - { - spells.setSelectedSpell(""); + if (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == mSpellToDelete) MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - } spells.remove(mSpellToDelete); diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 521e73d76..38a761931 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -7,14 +7,16 @@ namespace MWGui { class SpellIcons; - class SpellWindow : public WindowPinnableBase + class SpellWindow : public WindowPinnableBase, public NoDrop { public: - SpellWindow(); + SpellWindow(DragAndDrop* drag); virtual ~SpellWindow(); void updateSpells(); + void onFrame(float dt) { NoDrop::onFrame(dt); } + protected: MyGUI::ScrollView* mSpellView; MyGUI::Widget* mEffectBox; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 9facdac40..3d4c741a3 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -6,8 +6,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" @@ -18,8 +18,9 @@ namespace MWGui const int StatsWindow::sLineHeight = 18; - StatsWindow::StatsWindow () + StatsWindow::StatsWindow (DragAndDrop* drag) : WindowPinnableBase("openmw_stats_window.layout") + , NoDrop(drag, mMainWidget) , mSkillView(NULL) , mMajorSkills() , mMinorSkills() @@ -61,7 +62,7 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { - mSkillValues.insert(std::pair >(i, MWMechanics::Stat())); + mSkillValues.insert(std::pair(i, MWMechanics::SkillValue())); mSkillWidgetMap.insert(std::pair(i, (MyGUI::TextBox*)NULL)); } @@ -102,7 +103,7 @@ namespace MWGui adjustWindowCaption(); } - void StatsWindow::setValue (const std::string& id, const MWMechanics::Stat& value) + void StatsWindow::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { static const char *ids[] = { @@ -134,38 +135,28 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { - static const char *ids[] = - { - "HBar", "MBar", "FBar", - 0 - }; + int current = std::max(0, static_cast(value.getCurrent())); + int modified = static_cast(value.getModified()); - for (int i=0; ids[i]; ++i) - { - if (ids[i]==id) - { - std::string id (ids[i]); - setBar (id, id + "T", static_cast(value.getCurrent()), static_cast(value.getModified())); + setBar (id, id + "T", current, modified); - // health, magicka, fatigue tooltip - MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - if (i==0) - { - getWidget(w, "Health"); - w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); - } - else if (i==1) - { - getWidget(w, "Magicka"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); - } - else if (i==2) - { - getWidget(w, "Fatigue"); - w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); - } - } + // health, magicka, fatigue tooltip + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + if (id == "HBar") + { + getWidget(w, "Health"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (id == "MBar") + { + getWidget(w, "Magicka"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (id == "FBar") + { + getWidget(w, "Fatigue"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } } @@ -189,14 +180,14 @@ namespace MWGui } } - void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value) + void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { mSkillValues[parSkill] = value; MyGUI::TextBox* widget = mSkillWidgetMap[(int)parSkill]; if (widget) { - float modified = value.getModified(), base = value.getBase(); - std::string text = boost::lexical_cast(std::floor(modified)); + int modified = value.getModified(), base = value.getBase(); + std::string text = boost::lexical_cast(modified); std::string state = "normal"; if (modified > base) state = "increased"; @@ -229,21 +220,26 @@ namespace MWGui updateSkillArea(); } - void StatsWindow::onFrame () + void StatsWindow::onFrame (float dt) { if (!mMainWidget->getVisible()) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + NoDrop::onFrame(dt); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player); // level progress MyGUI::Widget* levelWidget; for (int i=0; i<2; ++i) { + int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->getInt(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); levelWidget->setUserString("RangePosition_LevelProgress", boost::lexical_cast(PCstats.getLevelProgress())); - levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast(PCstats.getLevelProgress()) + "/10"); + levelWidget->setUserString("Range_LevelProgress", boost::lexical_cast(max)); + levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast(PCstats.getLevelProgress()) + "/" + + boost::lexical_cast(max)); } setFactions(PCstats.getFactionRanks()); @@ -368,22 +364,20 @@ namespace MWGui continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWMechanics::Stat &stat = mSkillValues.find(skillId)->second; - float base = stat.getBase(); - float modified = stat.getModified(); - int progressPercent = (modified - float(static_cast(modified))) * 100; + const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; + int base = stat.getBase(); + int modified = stat.getModified(); + int progressPercent = stat.getProgress() * 100; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Skill* skill = esmStore.get().find(skillId); - assert(skill); std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; const ESM::Attribute* attr = esmStore.get().find(skill->mData.mAttribute); - assert(attr); std::string state = "normal"; if (modified > base) @@ -420,8 +414,6 @@ namespace MWGui } mSkillWidgets.clear(); - mSkillView->setViewOffset (MyGUI::IntPoint(0,0)); - const int valueSize = 40; MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); @@ -438,7 +430,7 @@ namespace MWGui MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ESMStore &store = world->getStore(); const ESM::NPC *player = - world->getPlayer().getPlayer().get()->mBase; + world->getPlayerPtr().get()->mBase; // race tooltip const ESM::Race* playerRace = store.get().find(player->mRace); @@ -466,7 +458,7 @@ namespace MWGui if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player); const std::set &expelled = PCstats.getExpelled(); @@ -496,7 +488,6 @@ namespace MWGui ESM::RankData rankData = faction->mData.mRankData[it->second+1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); - assert(attr1 && attr2); text += "\n#BF9959#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) + ", #{" + attr2->mName + "}: " + boost::lexical_cast(rankData.mAttribute2); diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index ac8319bdc..d90c16be9 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -10,33 +10,35 @@ namespace MWGui { class WindowManager; - class StatsWindow : public WindowPinnableBase + class StatsWindow : public WindowPinnableBase, public NoDrop { public: typedef std::map FactionList; typedef std::vector SkillList; - StatsWindow(); + StatsWindow(DragAndDrop* drag); /// automatically updates all the data in the stats window, but only if it has changed. - void onFrame(); + void onFrame(float dt); void setBar(const std::string& name, const std::string& tname, int val, int max); void setPlayerName(const std::string& playerName); /// Set value for the given ID. - void setValue (const std::string& id, const MWMechanics::Stat& value); + void setValue (const std::string& id, const MWMechanics::AttributeValue& value); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setValue (const std::string& id, const std::string& value); void setValue (const std::string& id, int value); - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); + void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void configureSkills (const SkillList& major, const SkillList& minor); void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; } void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } void updateSkillArea(); + virtual void open() { onWindowResize(static_cast(mMainWidget)); } + private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); @@ -59,7 +61,7 @@ namespace MWGui MyGUI::ScrollView* mSkillView; SkillList mMajorSkills, mMinorSkills, mMiscSkills; - std::map > mSkillValues; + std::map mSkillValues; std::map mSkillWidgetMap; std::map mFactionWidgetMap; FactionList mFactions; ///< Stores a list of factions and the current rank diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 1f53263ec..1ed80fc1e 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -15,8 +15,8 @@ namespace MWGui public: TextInputDialog(); - std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; } - void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); } + std::string getTextInput() const { return mTextEdit->getCaption(); } + void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } void setNextButtonShow(bool shown); void setTextLabel(const std::string &label); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 3a609aa91..8716c4dea 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -110,11 +110,6 @@ namespace MWGui else { - const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); - - if (mousePos == lastPressed) // mouseclick makes tooltip disappear - return; - if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) { mRemainingDelay -= frameDuration; @@ -187,7 +182,7 @@ namespace MWGui } else if (type == "AvatarItemSelection") { - MyGUI::IntCoord avatarPos = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarScreenCoord (); + MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f ); int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f ); @@ -216,6 +211,7 @@ namespace MWGui params.mMagnMin = it->mMagnMin; params.mMagnMax = it->mMagnMax; params.mRange = it->mRange; + params.mArea = it->mArea; params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); params.mNoTarget = false; effects.push_back(params); @@ -467,7 +463,7 @@ namespace MWGui } Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge"); - chargeWidget->setValue(charge, charge); + chargeWidget->setValue(charge, maxCharge); totalSize.height += 24; } } diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index e836355d3..88c14b791 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -149,13 +149,20 @@ namespace MWGui if(!mMerchant.isEmpty()) { MWWorld::Ptr base = item.mBase; - if(Misc::StringUtils::ciEqual(base.getCellRef().mRefID, "gold_001")) + if(Misc::StringUtils::ciEqual(base.getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) continue; if(!MWWorld::Class::get(base).canSell(base, services)) continue; + // Bound items may not be bought + if (item.mBase.getCellRef().mRefID.size() > 6 + && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") + { + continue; + } + // don't show equipped items - if(mMerchant.getTypeName() == typeid(ESM::NPC).name()) + if(mMerchant.getClass().hasInventoryStore(mMerchant)) { bool isEquipped = false; MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 94141b1a0..92ba9470d 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -14,9 +14,6 @@ #include "../mwworld/containerstore.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" - -#include "../mwworld/player.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" @@ -24,6 +21,7 @@ #include "containeritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" +#include "dialogue.hpp" namespace MWGui { @@ -80,9 +78,8 @@ namespace MWGui } void TradeWindow::startTrade(const MWWorld::Ptr& actor) - { + { mPtr = actor; - setTitle(MWWorld::Class::get(actor).getName(actor)); mCurrentBalance = 0; mCurrentMerchantOffer = 0; @@ -99,6 +96,12 @@ namespace MWGui mItemView->setModel (mSortModel); updateLabels(); + + // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last + // or we end up using a possibly invalid model. + setTitle(MWWorld::Class::get(actor).getName(actor)); + + onFilterChanged(mFilterAll); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) @@ -200,32 +203,17 @@ namespace MWGui sellToNpc(item.mBase, count, true); } - void TradeWindow::addOrRemoveGold(int amount) + void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor) { - bool goldFound = false; - MWWorld::Ptr gold; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = MWWorld::Class::get(actor).getContainerStore(actor); - for (MWWorld::ContainerStoreIterator it = playerStore.begin(); - it != playerStore.end(); ++it) + if (amount > 0) { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) - { - goldFound = true; - gold = *it; - } - } - if (goldFound) - { - gold.getRefData().setCount(gold.getRefData().getCount() + amount); + store.add(MWWorld::ContainerStore::sGoldId, amount, actor); } else { - assert(amount > 0); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001"); - ref.getPtr().getRefData().setCount(amount); - playerStore.add(ref.getPtr(), player); + store.remove(MWWorld::ContainerStore::sGoldId, - amount, actor); } } @@ -262,8 +250,11 @@ namespace MWGui return; } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + // check if the player can afford this - if (mCurrentBalance < 0 && MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold() < std::abs(mCurrentBalance)) + if (mCurrentBalance < 0 && playerGold < std::abs(mCurrentBalance)) { // user notification MWBase::Environment::get().getWindowManager()-> @@ -280,6 +271,25 @@ namespace MWGui return; } + // check if the player is attempting to sell back an item stolen from this actor + for (std::vector::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().mOwner, mPtr.getCellRef().mRefID)) + { + std::string msg = gmst.find("sNotifyMessage49")->getString(); + if (msg.find("%s") != std::string::npos) + msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase)); + MWBase::Environment::get().getWindowManager()->messageBox(msg); + MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief"); + MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, + it->mBase.getClass().getValue(it->mBase) + * it->mCount); + onCancelButtonClicked(mCancelButton); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + return; + } + } + if(mCurrentBalance > mCurrentMerchantOffer) { //if npc is a creature: reject (no haggle) @@ -301,14 +311,13 @@ namespace MWGui float clampedDisposition = std::max(0,std::min(int(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr) + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100)); - const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(mPtr).getNpcStats(mPtr); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); + const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); + const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); - float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float a1 = std::min(player.getClass().getSkill(player, ESM::Skill::Mercantile), 100); float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c1 = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d1 = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float d1 = std::min(mPtr.getClass().getSkill(mPtr, ESM::Skill::Mercantile), 100); float e1 = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f1 = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); @@ -327,24 +336,32 @@ namespace MWGui messageBox("#{sNotifyMessage9}"); int iBarterFailDisposition = gmst.find("iBarterFailDisposition")->getInt(); - MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterFailDisposition); + if (mPtr.getClass().isNpc()) + MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterFailDisposition); return; } //skill use! - MWWorld::Class::get(playerPtr).skillUsageSucceeded(playerPtr, ESM::Skill::Mercantile, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0); } int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt(); - MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterSuccessDisposition); + if (mPtr.getClass().isNpc()) + MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterSuccessDisposition); // make the item transfer mTradeModel->transferItems(); playerItemModel->transferItems(); - // add or remove gold from the player. + // transfer the gold if (mCurrentBalance != 0) - addOrRemoveGold(mCurrentBalance); + { + addOrRemoveGold(mCurrentBalance, player); + addOrRemoveGold(-mCurrentBalance, mPtr); + } + + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse( + MWBase::Environment::get().getWorld()->getStore().get().find("sBarterDialog5")->getString()); std::string sound = "Item Gold Up"; MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); @@ -400,7 +417,10 @@ namespace MWGui void TradeWindow::updateLabels() { - mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + boost::lexical_cast(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + boost::lexical_cast(playerGold)); if (mCurrentBalance > 0) { @@ -445,22 +465,13 @@ namespace MWGui 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 + int merchantGold = 0; + MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - MWWorld::LiveCellRef* ref = mPtr.get(); - merchantGold = ref->mBase->mData.mGold; + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) + merchantGold += it->getRefData().getCount(); } - return merchantGold; } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 4e905915a..1a8999e6e 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -28,8 +28,6 @@ namespace MWGui void startTrade(const MWWorld::Ptr& actor); - void addOrRemoveGold(int gold); - void onFrame(float frameDuration); void borrowItem (int index, size_t count); @@ -95,6 +93,8 @@ namespace MWGui void onIncreaseButtonTriggered(); void onDecreaseButtonTriggered(); + void addOrRemoveGold(int gold, const MWWorld::Ptr& actor); + void updateLabels(); virtual void onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 7ddac38f5..bee76992a 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -9,13 +9,11 @@ #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" -#include "inventorywindow.hpp" -#include "tradewindow.hpp" #include "tooltips.hpp" namespace MWGui @@ -41,7 +39,10 @@ namespace MWGui { mPtr = actor; - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); MWMechanics::NpcStats& npcStats = MWWorld::Class::get(actor).getNpcStats (actor); @@ -72,7 +73,6 @@ namespace MWGui MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (widgets); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); const MWWorld::Store &gmst = @@ -83,11 +83,10 @@ namespace MWGui int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer (mPtr,pcStats.getSkill (bestSkills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); - std::string skin = (price > MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getPlayerGold ()) ? "SandTextGreyedOut" : "SandTextButton"; - - MyGUI::Button* button = mTrainingOptions->createWidget(skin, + MyGUI::Button* button = mTrainingOptions->createWidget("SandTextButton", MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default); + button->setEnabled(price <= playerGold); button->setUserData(bestSkills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); @@ -115,7 +114,7 @@ namespace MWGui { int skillId = *sender->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); const MWWorld::ESMStore &store = @@ -124,9 +123,6 @@ namespace MWGui int price = pcStats.getSkill (skillId).getBase() * store.get().find("iTrainingMod")->getInt (); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); - if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()getStore().get().find(skillId); + if (pcStats.getSkill(skillId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase()) + { + MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage17}"); + return; + } + // increase skill MWWorld::LiveCellRef *playerRef = player.get(); @@ -142,7 +146,7 @@ namespace MWGui pcStats.increaseSkill (skillId, *class_, true); // remove gold - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); @@ -150,6 +154,8 @@ namespace MWGui // advance time MWBase::Environment::get().getWorld ()->advanceTime (2); + MWBase::Environment::get().getMechanicsManager()->rest(false); + MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getWorld ()->getFader()->fadeOut(0.25); mFadeTimeRemaining = 0.5; diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 93ac8299d..c314ce1fd 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include "../mwbase/environment.hpp" @@ -9,11 +11,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" - -#include "inventorywindow.hpp" -#include "tradewindow.hpp" +#include "../mwworld/containerstore.hpp" namespace MWGui { @@ -51,13 +50,15 @@ namespace MWGui const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + if(interior) { price = gmst.find("fMagesGuildTravel")->getFloat(); } else { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); ESM::Position PlayerPos = player.getRefData().getPosition(); float d = sqrt( pow(pos.pos[0] - PlayerPos.pos[0],2) + pow(pos.pos[1] - PlayerPos.pos[1],2) + pow(pos.pos[2] - PlayerPos.pos[2],2) ); price = d/gmst.find("fTravelMult")->getFloat(); @@ -65,7 +66,8 @@ namespace MWGui price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); - MyGUI::Button* toAdd = mDestinationsView->createWidget((price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); + MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); + toAdd->setEnabled(price<=playerGold); mCurrentY += sLineHeight; if(interior) toAdd->setUserString("interior","y"); @@ -121,13 +123,16 @@ namespace MWGui int price; iss >> price; - if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + if (playerGoldgetTradeWindow ()->addOrRemoveGold (-price); + + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); int x,y; @@ -143,7 +148,7 @@ namespace MWGui int hours = static_cast(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->getFloat()); for(int i = 0;i < hours;i++) { - MWBase::Environment::get().getMechanicsManager ()->restoreDynamicStats (); + MWBase::Environment::get().getMechanicsManager ()->rest (true); } MWBase::Environment::get().getWorld()->advanceTime(hours); @@ -164,7 +169,10 @@ namespace MWGui void TravelWindow::updateLabels() { - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 2467f6c40..3c1a4b3fa 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -9,7 +9,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -49,6 +48,7 @@ namespace MWGui , mRemainingTime(0.05) , mCurHour(0) , mManualHours(1) + , mInterruptAt(-1) { getWidget(mDateTimeText, "DateTimeText"); getWidget(mRestText, "RestText"); @@ -87,49 +87,7 @@ namespace MWGui onHourSliderChangedPosition(mHourSlider, 0); mHourSlider->setScrollPosition (0); - // http://www.uesp.net/wiki/Lore:Calendar - std::string month; - int m = MWBase::Environment::get().getWorld ()->getMonth (); - 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; - } + std::string month = MWBase::Environment::get().getWorld ()->getMonthName(); int hour = MWBase::Environment::get().getWorld ()->getTimeStamp ().getHour (); bool pm = hour >= 12; if (hour >= 13) hour -= 12; @@ -145,43 +103,7 @@ namespace MWGui void WaitDialog::onUntilHealedButtonClicked(MyGUI::Widget* sender) { - // 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(ESM::MagicEffect::StuntedMagicka)).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 + int autoHours = MWBase::Environment::get().getMechanicsManager()->getHoursToRest(); startWaiting(autoHours); } @@ -193,7 +115,8 @@ namespace MWGui void WaitDialog::startWaiting(int hoursToWait) { - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.2); + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->getFader ()->fadeOut(0.2); setVisible(false); mProgressBar.setVisible (true); @@ -201,6 +124,30 @@ namespace MWGui mCurHour = 0; mHours = hoursToWait; + // FIXME: move this somewhere else? + mInterruptAt = -1; + MWWorld::Ptr player = world->getPlayerPtr(); + if (mSleeping && player.getCell()->isExterior()) + { + std::string regionstr = player.getCell()->mCell->mRegion; + if (!regionstr.empty()) + { + const ESM::Region *region = world->getStore().get().find (regionstr); + if (!region->mSleepList.empty()) + { + float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); + int x = std::rand()/ (static_cast (RAND_MAX) + 1) * hoursToWait; // [0, hoursRested] + float y = fSleepRandMod * hoursToWait; + if (x > y) + { + float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); + mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); + mInterruptCreatureList = region->mSleepList; + } + } + } + } + mRemainingTime = 0.05; mProgressBar.setProgress (0, mHours); } @@ -218,7 +165,7 @@ namespace MWGui void WaitDialog::setCanRest (bool canRest) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified()) && (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) @@ -243,6 +190,13 @@ namespace MWGui if (!mWaiting) return; + if (mCurHour == mInterruptAt) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sSleepInterrupt}"); + MWBase::Environment::get().getWorld()->spawnRandomCreature(mInterruptCreatureList); + stopWaiting(); + } + mRemainingTime -= dt; while (mRemainingTime < 0) @@ -254,8 +208,7 @@ namespace MWGui if (mCurHour <= mHours) { MWBase::Environment::get().getWorld ()->advanceTime (1); - if (mSleeping) - MWBase::Environment::get().getMechanicsManager ()->restoreDynamicStats (); + MWBase::Environment::get().getMechanicsManager ()->rest (mSleeping); } } @@ -272,11 +225,13 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed); mWaiting = false; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWMechanics::NpcStats &pcstats = MWWorld::Class::get(player).getNpcStats(player); // trigger levelup if possible - if (mSleeping && pcstats.getLevelProgress () >= 10) + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) { MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); } diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index 2723f7a80..d96649af6 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -51,6 +51,9 @@ namespace MWGui int mManualHours; // stores the hours to rest selected via slider float mRemainingTime; + int mInterruptAt; + std::string mInterruptCreatureList; + WaitDialogProgressBar mProgressBar; void onUntilHealedButtonClicked(MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 3fc3187e8..b30cf2bae 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -178,7 +178,7 @@ namespace MWGui } if (mAttributeValueWidget) { - AttributeValue::Type modified = mValue.getModified(), base = mValue.getBase(); + int modified = mValue.getModified(), base = mValue.getBase(); static_cast(mAttributeValueWidget)->setCaption(boost::lexical_cast(modified)); if (modified > base) mAttributeValueWidget->_setWidgetState("increased"); @@ -528,14 +528,9 @@ namespace MWGui if (mBarTextWidget) { - if (mValue >= 0 && mMax > 0) - { - std::stringstream out; - out << mValue << "/" << mMax; - static_cast(mBarTextWidget)->setCaption(out.str().c_str()); - } - else - static_cast(mBarTextWidget)->setCaption(""); + std::stringstream out; + out << mValue << "/" << mMax; + static_cast(mBarTextWidget)->setCaption(out.str().c_str()); } } void MWDynamicStat::setTitle(const std::string& text) @@ -689,22 +684,26 @@ namespace MWGui int total_width = 0; int total_height = 0; std::vector< std::pair > sizes; + sizes.resize(count); for (unsigned int i = 0; i < count; ++i) { MyGUI::Widget* w = getChildAt(i); bool hstretch = w->getUserString ("HStretch") == "true"; + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; h_stretched_count += hstretch; AutoSizedWidget* aw = dynamic_cast(w); if (aw) { - sizes.push_back(std::make_pair(aw->getRequestedSize (), hstretch)); + sizes[i] = std::make_pair(aw->getRequestedSize (), hstretch); total_width += aw->getRequestedSize ().width; total_height = std::max(total_height, aw->getRequestedSize ().height); } else { - sizes.push_back (std::make_pair(w->getSize(), hstretch)); + sizes[i] = std::make_pair(w->getSize(), hstretch); total_width += w->getSize().width; if (!(w->getUserString("VStretch") == "true")) total_height = std::max(total_height, w->getSize().height); @@ -729,8 +728,13 @@ namespace MWGui MyGUI::Widget* w = getChildAt(i); + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + bool vstretch = w->getUserString ("VStretch") == "true"; - int height = vstretch ? total_height : sizes[i].first.height; + int max_height = getSize().height - mPadding*2; + int height = vstretch ? max_height : sizes[i].first.height; MyGUI::IntCoord widgetCoord; widgetCoord.left = curX; @@ -774,6 +778,10 @@ namespace MWGui MyGUI::IntSize size(0,0); for (unsigned int i = 0; i < getChildCount (); ++i) { + bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; + if (hidden) + continue; + AutoSizedWidget* w = dynamic_cast(getChildAt(i)); if (w) { @@ -810,21 +818,27 @@ namespace MWGui int total_height = 0; int total_width = 0; std::vector< std::pair > sizes; + sizes.resize(count); for (unsigned int i = 0; i < count; ++i) { MyGUI::Widget* w = getChildAt(i); + + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + bool vstretch = w->getUserString ("VStretch") == "true"; v_stretched_count += vstretch; AutoSizedWidget* aw = dynamic_cast(w); if (aw) { - sizes.push_back(std::make_pair(aw->getRequestedSize (), vstretch)); + sizes[i] = std::make_pair(aw->getRequestedSize (), vstretch); total_height += aw->getRequestedSize ().height; total_width = std::max(total_width, aw->getRequestedSize ().width); } else { - sizes.push_back (std::make_pair(w->getSize(), vstretch)); + sizes[i] = std::make_pair(w->getSize(), vstretch); total_height += w->getSize().height; if (!(w->getUserString("HStretch") == "true")) @@ -850,8 +864,13 @@ namespace MWGui MyGUI::Widget* w = getChildAt(i); + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + bool hstretch = w->getUserString ("HStretch") == "true"; - int width = hstretch ? total_width : sizes[i].first.width; + int maxWidth = getSize().width - mPadding*2; + int width = hstretch ? maxWidth : sizes[i].first.width; MyGUI::IntCoord widgetCoord; widgetCoord.top = curY; @@ -890,6 +909,10 @@ namespace MWGui MyGUI::IntSize size(0,0); for (unsigned int i = 0; i < getChildCount (); ++i) { + bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; + if (hidden) + continue; + AutoSizedWidget* w = dynamic_cast(getChildAt(i)); if (w) { diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 1630ab3c9..adc56f423 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -133,7 +133,7 @@ namespace MWGui public: MWAttribute(); - typedef MWMechanics::Stat AttributeValue; + typedef MWMechanics::AttributeValue AttributeValue; void setAttributeId(int attributeId); void setAttributeValue(const AttributeValue& value); diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index cc74579ab..87b26b814 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -1,6 +1,7 @@ #include "windowbase.hpp" #include "../mwbase/windowmanager.hpp" +#include "container.hpp" using namespace MWGui; @@ -50,3 +51,36 @@ void WindowModal::close() { MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); } + +NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) + : mDrag(drag), mWidget(widget), mTransparent(false) +{ +} + +void NoDrop::onFrame(float dt) +{ + MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance().getMousePosition(); + + if (mDrag->mIsOnDragAndDrop) + { + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget(); + while (focus && focus != mWidget) + focus = focus->getParent(); + + if (focus == mWidget) + mTransparent = true; + } + if (!mWidget->getAbsoluteCoord().inside(mousePos)) + mTransparent = false; + + if (mTransparent) + { + mWidget->setNeedMouseFocus(false); // Allow click-through + mWidget->setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5)); + } + else + { + mWidget->setNeedMouseFocus(true); + mWidget->setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5)); + } +} diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 2c014baf0..48de9ea87 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -11,6 +11,7 @@ namespace MWBase namespace MWGui { class WindowManager; + class DragAndDrop; class WindowBase: public OEngine::GUI::Layout { @@ -42,6 +43,21 @@ namespace MWGui virtual void open(); virtual void close(); }; + + /// A window that cannot be the target of a drag&drop action. + /// When hovered with a drag item, the window will become transparent and allow click-through. + class NoDrop + { + public: + NoDrop(DragAndDrop* drag, MyGUI::Widget* widget); + + void onFrame(float dt); + + private: + MyGUI::Widget* mWidget; + DragAndDrop* mDrag; + bool mTransparent; + }; } #endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 4b4d2dfd1..5448bc3c4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -14,8 +14,10 @@ #include #include "../mwbase/inputmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "console.hpp" #include "journalwindow.hpp" @@ -43,6 +45,7 @@ #include "waitdialog.hpp" #include "enchantingdialog.hpp" #include "trainingwindow.hpp" +#include "recharge.hpp" #include "exposedwindow.hpp" #include "cursor.hpp" #include "merchantrepair.hpp" @@ -95,10 +98,10 @@ namespace MWGui , mTrainingWindow(NULL) , mMerchantRepair(NULL) , mSoulgemDialog(NULL) + , mRecharge(NULL) , mRepair(NULL) , mCompanionWindow(NULL) , mTranslationDataStorage (translationDataStorage) - , mSoftwareCursor(NULL) , mCharGen(NULL) , mInputBlocker(NULL) , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) @@ -111,9 +114,6 @@ namespace MWGui , mPlayerMinorSkills() , mPlayerMajorSkills() , mPlayerSkillValues() - , mPlayerHealth() - , mPlayerMagicka() - , mPlayerFatigue() , mGui(NULL) , mGuiModes() , mCursorManager(NULL) @@ -126,7 +126,6 @@ namespace MWGui , mFPS(0.0f) , mTriangleCount(0) , mBatchCount(0) - , mUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")) { // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); @@ -171,16 +170,19 @@ namespace MWGui mLoadingScreen->onResChange (w,h); //set up the hardware cursor manager - mSoftwareCursor = new Cursor(); mCursorManager = new SFO::SDLCursorManager(); MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); - setUseHardwareCursors(mUseHardwareCursors); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - mCursorManager->cursorVisibilityChange(false); + SDL_ShowCursor(false); + + mCursorManager->setEnabled(true); + + // hide mygui's pointer + MyGUI::PointerManager::getInstance().setVisible(false); } void WindowManager::initUI() @@ -197,18 +199,26 @@ namespace MWGui mDragAndDrop->mDraggedWidget = 0; mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; + mRecharge = new Recharge(); mMenu = new MainMenu(w,h); - mMap = new MapWindow(""); - mStatsWindow = new StatsWindow(); + mMap = new MapWindow(mDragAndDrop, ""); + trackWindow(mMap, "map"); + mStatsWindow = new StatsWindow(mDragAndDrop); + trackWindow(mStatsWindow, "stats"); mConsole = new Console(w,h, mConsoleOnlyScripts); + trackWindow(mConsole, "console"); mJournal = JournalWindow::create(JournalViewModel::create ()); - mMessageBoxManager = new MessageBoxManager(); + mMessageBoxManager = new MessageBoxManager( + MWBase::Environment::get().getWorld()->getStore().get().find("fMessageTimePerChar")->getFloat()); mInventoryWindow = new InventoryWindow(mDragAndDrop); mTradeWindow = new TradeWindow(); + trackWindow(mTradeWindow, "barter"); mSpellBuyingWindow = new SpellBuyingWindow(); mTravelWindow = new TravelWindow(); mDialogueWindow = new DialogueWindow(); + trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); + trackWindow(mContainerWindow, "container"); mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); @@ -217,7 +227,9 @@ namespace MWGui mSettingsWindow = new SettingsWindow(); mConfirmationDialog = new ConfirmationDialog(); mAlchemyWindow = new AlchemyWindow(); - mSpellWindow = new SpellWindow(); + trackWindow(mAlchemyWindow, "alchemy"); + mSpellWindow = new SpellWindow(mDragAndDrop); + trackWindow(mSpellWindow, "spells"); mQuickKeysMenu = new QuickKeysMenu(); mLevelupDialog = new LevelupDialog(); mWaitDialog = new WaitDialog(); @@ -228,6 +240,7 @@ namespace MWGui mRepair = new Repair(); mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); + trackWindow(mCompanionWindow, "companion"); mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); @@ -238,17 +251,14 @@ namespace MWGui // Setup player stats for (int i = 0; i < ESM::Attribute::Length; ++i) { - mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::Stat())); + mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue())); } for (int i = 0; i < ESM::Skill::Length; ++i) { - mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::Stat())); + mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue())); } - unsetSelectedSpell(); - unsetSelectedWeapon(); - // Set up visibility updateVisible(); @@ -313,8 +323,9 @@ namespace MWGui delete mMerchantRepair; delete mRepair; delete mSoulgemDialog; - delete mSoftwareCursor; delete mCursorManager; + delete mRecharge; + delete mCompanionWindow; cleanupGarbage(); @@ -343,8 +354,6 @@ namespace MWGui mHud->setBatchCount(mBatchCount); mHud->update(); - - mSoftwareCursor->update(); } void WindowManager::updateVisible() @@ -378,6 +387,7 @@ namespace MWGui mRepair->setVisible(false); mCompanionWindow->setVisible(false); mInventoryWindow->setTrading(false); + mRecharge->setVisible(false); mHud->setVisible(mHudEnabled); @@ -495,6 +505,9 @@ namespace MWGui case GM_SpellCreation: mSpellCreationDialog->setVisible(true); break; + case GM_Recharge: + mRecharge->setVisible(true); + break; case GM_Enchanting: mEnchantingDialog->setVisible(true); break; @@ -533,7 +546,7 @@ namespace MWGui } } - void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) + void WindowManager::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { mStatsWindow->setValue (id, value); mCharGen->setValue(id, value); @@ -564,7 +577,7 @@ namespace MWGui } - void WindowManager::setValue (int parSkill, const MWMechanics::Stat& value) + void WindowManager::setValue (int parSkill, const MWMechanics::SkillValue& value) { /// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we /// allow custom skills. @@ -578,35 +591,8 @@ namespace MWGui mStatsWindow->setValue (id, value); mHud->setValue (id, value); mCharGen->setValue(id, value); - if (id == "HBar") - { - mPlayerHealth = value; - mCharGen->setPlayerHealth (value); - } - else if (id == "MBar") - { - mPlayerMagicka = value; - mCharGen->setPlayerMagicka (value); - } - else if (id == "FBar") - { - mPlayerFatigue = value; - mCharGen->setPlayerFatigue (value); - } } - #if 0 - MWMechanics::DynamicStat WindowManager::getValue(const std::string& id) - { - if(id == "HBar") - return layerHealth; - else if (id == "MBar") - return mPlayerMagicka; - else if (id == "FBar") - return mPlayerFatigue; - } - #endif - void WindowManager::setValue (const std::string& id, const std::string& value) { mStatsWindow->setValue (id, value); @@ -662,19 +648,14 @@ namespace MWGui mGarbageDialogs.push_back(dialog); } - void WindowManager::messageBox (const std::string& message, const std::vector& buttons, bool showInDialogueModeOnly) + void WindowManager::messageBox (const std::string& message, const std::vector& buttons, enum MWGui::ShowInDialogueMode showInDialogueMode) { if (buttons.empty()) { /* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */ - if (getMode() == GM_Dialogue) { + if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) { mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); - } else { - if (showInDialogueModeOnly) { - if (getMode() == GM_Dialogue) - mMessageBoxManager->createMessageBox(message); - } else { - mMessageBoxManager->createMessageBox(message); - } + } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) { + mMessageBoxManager->createMessageBox(message); } } else { mMessageBoxManager->createInteractiveMessageBox(message, buttons); @@ -692,17 +673,6 @@ namespace MWGui mMessageBoxManager->removeStaticMessageBox(); } - void WindowManager::enterPressed () - { - mMessageBoxManager->okayPressed(); - } - - void WindowManager::activateKeyPressed () - { - mMessageBoxManager->okayPressed(); - mCountDialog->cancel(); - } - int WindowManager::readPressedButton () { return mMessageBoxManager->readPressedButton(); @@ -725,6 +695,10 @@ namespace MWGui mToolTips->onFrame(frameDuration); + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_NoGame) + return; + if (mDragAndDrop->mIsOnDragAndDrop) { assert(mDragAndDrop->mDraggedWidget); @@ -735,7 +709,9 @@ namespace MWGui mInventoryWindow->onFrame(); - mStatsWindow->onFrame(); + mStatsWindow->onFrame(frameDuration); + mMap->onFrame(frameDuration); + mSpellWindow->onFrame(frameDuration); mWaitDialog->onFrame(frameDuration); @@ -756,31 +732,20 @@ namespace MWGui mCompanionWindow->onFrame(); } - void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) + void WindowManager::changeCell(MWWorld::CellStore* cell) { + std::string name = MWBase::Environment::get().getWorld()->getCellName (cell); + + mMap->setCellName( name ); + mHud->setCellName( name ); + if (cell->mCell->isExterior()) { - std::string name; - if (cell->mCell->mName != "") - { - name = cell->mCell->mName; + if (!cell->mCell->mName.empty()) mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->mCell->getGridX (), cell->mCell->getGridY ()); - } - else - { - const ESM::Region* region = - MWBase::Environment::get().getWorld()->getStore().get().search(cell->mCell->mRegion); - if (region) - name = region->mName; - else - name = getGameSettingString("sDefaultCellname", "Wilderness"); - } mMap->cellExplored(cell->mCell->getGridX(), cell->mCell->getGridY()); - mMap->setCellName( name ); - mHud->setCellName( name ); - mMap->setCellPrefix("Cell"); mHud->setCellPrefix("Cell"); mMap->setActiveCell( cell->mCell->getGridX(), cell->mCell->getGridY() ); @@ -788,12 +753,16 @@ namespace MWGui } else { - mMap->setCellName( cell->mCell->mName ); - mHud->setCellName( cell->mCell->mName ); mMap->setCellPrefix( cell->mCell->mName ); mHud->setCellPrefix( cell->mCell->mName ); - } + Ogre::Vector3 worldPos; + if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) + worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); + else + MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); + mMap->setGlobalMapPlayerPosition(worldPos.x, worldPos.y); + } } void WindowManager::setInteriorMapTexture(const int x, const int y) @@ -877,21 +846,9 @@ namespace MWGui MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop); } - void WindowManager::setUseHardwareCursors(bool use) - { - mCursorManager->setEnabled(use); - mSoftwareCursor->setVisible(!use && mCursorVisible); - } - void WindowManager::setCursorVisible(bool visible) { - if(mCursorVisible == visible) - return; - mCursorVisible = visible; - mCursorManager->cursorVisibilityChange(visible); - - mSoftwareCursor->setVisible(!mUseHardwareCursors && visible); } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) @@ -922,8 +879,6 @@ namespace MWGui mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); - setUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")); - for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { @@ -940,6 +895,17 @@ namespace MWGui mLoadingScreen->onResChange (x,y); if (!mHud) return; // UI not initialized yet + + for (std::map::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it) + { + MyGUI::IntPoint pos (Settings::Manager::getFloat(it->second + " x", "Windows") * x, + Settings::Manager::getFloat(it->second+ " y", "Windows") * y); + MyGUI::IntSize size (Settings::Manager::getFloat(it->second + " w", "Windows") * x, + Settings::Manager::getFloat(it->second + " h", "Windows") * y); + it->first->setPosition(pos); + it->first->setSize(size); + } + mHud->onResChange(x, y); mConsole->onResChange(x, y); mMenu->onResChange(x, y); @@ -975,8 +941,6 @@ namespace MWGui void WindowManager::onCursorChange(const std::string &name) { - mSoftwareCursor->onCursorChange(name); - if(!mCursorManager->cursorChanged(name)) return; //the cursor manager doesn't want any more info about this cursor //See if we can get the information we need out of the cursor resource @@ -1033,6 +997,7 @@ namespace MWGui void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) { + mSelectedSpell = spellId; mHud->setSelectedSpell(spellId, successChancePercent); const ESM::Spell* spell = @@ -1043,6 +1008,7 @@ namespace MWGui void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { + mSelectedSpell = ""; const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get() .find(MWWorld::Class::get(item).getEnchantment(item)); @@ -1062,7 +1028,13 @@ namespace MWGui void WindowManager::unsetSelectedSpell() { + mSelectedSpell = ""; mHud->unsetSelectedSpell(); + + MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); + if (player->getDrawState() == MWMechanics::DrawState_Spell) + player->setDrawState(MWMechanics::DrawState_Nothing); + mSpellWindow->setTitle("#{sNone}"); } @@ -1189,12 +1161,12 @@ namespace MWGui return mGuiModes.back(); } - std::map > WindowManager::getPlayerSkillValues() + std::map WindowManager::getPlayerSkillValues() { return mPlayerSkillValues; } - std::map > WindowManager::getPlayerAttributeValues() + std::map WindowManager::getPlayerAttributeValues() { return mPlayerAttributes; } @@ -1258,7 +1230,7 @@ namespace MWGui bool WindowManager::getRestEnabled() { //Enable rest dialogue if character creation finished - if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalVariable ("chargenstate").mFloat==-1) + if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) mRestAllowed=true; return mRestAllowed; } @@ -1320,6 +1292,7 @@ namespace MWGui void WindowManager::changePointer(const std::string &name) { + MyGUI::PointerManager::getInstance().setPointer(name); onCursorChange(name); } @@ -1336,6 +1309,9 @@ namespace MWGui void WindowManager::updatePlayer() { + unsetSelectedSpell(); + unsetSelectedWeapon(); + mInventoryWindow->updatePlayer(); } @@ -1366,4 +1342,60 @@ namespace MWGui return mLoadingScreen; } + void WindowManager::startRecharge(MWWorld::Ptr soulgem) + { + mRecharge->start(soulgem); + } + + bool WindowManager::getCursorVisible() + { + return mCursorVisible; + } + + void WindowManager::trackWindow(OEngine::GUI::Layout *layout, const std::string &name) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint pos (Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width, + Settings::Manager::getFloat(name + " y", "Windows") * viewSize.height); + MyGUI::IntSize size (Settings::Manager::getFloat(name + " w", "Windows") * viewSize.width, + Settings::Manager::getFloat(name + " h", "Windows") * viewSize.height); + layout->mMainWidget->setPosition(pos); + layout->mMainWidget->setSize(size); + + MyGUI::Window* window = dynamic_cast(layout->mMainWidget); + if (!window) + throw std::runtime_error("Attempting to track size of a non-resizable window"); + window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); + mTrackedWindows[window] = name; + } + + void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender) + { + std::string setting = mTrackedWindows[_sender]; + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = _sender->getPosition().left / float(viewSize.width); + float y = _sender->getPosition().top / float(viewSize.height); + float w = _sender->getSize().width / float(viewSize.width); + float h = _sender->getSize().height / float(viewSize.height); + Settings::Manager::setFloat(setting + " x", "Windows", x); + Settings::Manager::setFloat(setting + " y", "Windows", y); + Settings::Manager::setFloat(setting + " w", "Windows", w); + Settings::Manager::setFloat(setting + " h", "Windows", h); + } + + void WindowManager::clear() + { + mMap->clear(); + } + + void WindowManager::write(ESM::ESMWriter &writer) + { + mMap->write(writer); + } + + void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type) + { + mMap->readRecord(reader, type); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index badb333a7..dafb65e47 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -16,6 +16,7 @@ namespace MyGUI { class Gui; class Widget; + class Window; class UString; } @@ -77,6 +78,7 @@ namespace MWGui class MerchantRepair; class Repair; class SoulgemDialog; + class Recharge; class CompanionWindow; class WindowManager : public MWBase::WindowManager @@ -103,6 +105,7 @@ namespace MWGui */ virtual void update(); + /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget); virtual void setNewGame(bool newgame); @@ -150,8 +153,8 @@ namespace MWGui virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount); ///< Set value for the given ID. - virtual void setValue (const std::string& id, const MWMechanics::Stat& value); - virtual void setValue (int parSkill, const MWMechanics::Stat& value); + virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value); + virtual void setValue (int parSkill, const MWMechanics::SkillValue& value); virtual void setValue (const std::string& id, const MWMechanics::DynamicStat& value); virtual void setValue (const std::string& id, const std::string& value); virtual void setValue (const std::string& id, int value); @@ -198,6 +201,7 @@ namespace MWGui virtual void activateQuickKey (int index); + virtual std::string getSelectedSpell() { return mSelectedSpell; } virtual void setSelectedSpell(const std::string& spellId, int successChancePercent); virtual void setSelectedEnchantItem(const MWWorld::Ptr& item); virtual void setSelectedWeapon(const MWWorld::Ptr& item); @@ -216,18 +220,16 @@ 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 = std::vector(), bool showInDialogueModeOnly = false); + virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible); virtual void staticMessageBox(const std::string& message); virtual void removeStaticMessageBox(); - virtual void enterPressed (); - virtual void activateKeyPressed (); virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual void onFrame (float frameDuration); /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - virtual std::map > getPlayerSkillValues(); - virtual std::map > getPlayerAttributeValues(); + virtual std::map getPlayerSkillValues(); + virtual std::map getPlayerAttributeValues(); virtual SkillList getPlayerMinorSkills(); virtual SkillList getPlayerMajorSkills(); @@ -263,6 +265,7 @@ namespace MWGui virtual void startTraining(MWWorld::Ptr actor); virtual void startRepair(MWWorld::Ptr actor); virtual void startRepairItem(MWWorld::Ptr item); + virtual void startRecharge(MWWorld::Ptr soulgem); virtual void frameStarted(float dt); @@ -276,9 +279,23 @@ namespace MWGui void onSoulgemDialogButtonPressed (int button); + virtual bool getCursorVisible(); + + /// Clear all savegame-specific data + virtual void clear(); + + virtual void write (ESM::ESMWriter& writer); + virtual void readRecord (ESM::ESMReader& reader, int32_t type); + private: bool mConsoleOnlyScripts; + std::map mTrackedWindows; + void trackWindow(OEngine::GUI::Layout* layout, const std::string& name); + void onWindowChangeCoord(MyGUI::Window* _sender); + + std::string mSelectedSpell; + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; @@ -313,6 +330,7 @@ namespace MWGui MerchantRepair* mMerchantRepair; SoulgemDialog* mSoulgemDialog; Repair* mRepair; + Recharge* mRecharge; CompanionWindow* mCompanionWindow; Translation::Storage& mTranslationDataStorage; @@ -333,11 +351,9 @@ namespace MWGui // Various stats about player as needed by window manager std::string mPlayerName; std::string mPlayerRaceId; - std::map > mPlayerAttributes; + std::map mPlayerAttributes; SkillList mPlayerMajorSkills, mPlayerMinorSkills; - std::map > mPlayerSkillValues; - MWMechanics::DynamicStat mPlayerHealth, mPlayerMagicka, mPlayerFatigue; - + std::map mPlayerSkillValues; MyGUI::Gui *mGui; // Gui std::vector mGuiModes; @@ -366,9 +382,6 @@ namespace MWGui unsigned int mTriangleCount; unsigned int mBatchCount; - bool mUseHardwareCursors; - void setUseHardwareCursors(bool use); - /** * Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string, * so this method will retrieve the GMST with the name \a _tag and place the result in \a _result diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 43f2bcc15..7ed3007ff 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -19,7 +20,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwgui/bookwindow.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwmechanics/creaturestats.hpp" using namespace ICS; @@ -87,7 +88,7 @@ namespace MWInput { InputManager::InputManager(OEngine::Render::OgreRenderer &ogre, OMW::Engine& engine, - const std::string& userFile, bool userFileExists) + const std::string& userFile, bool userFileExists, bool grab) : mOgre(ogre) , mPlayer(NULL) , mEngine(engine) @@ -103,6 +104,7 @@ namespace MWInput , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) , mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input")) , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) + , mGrabCursor (Settings::Manager::getBool("grab cursor", "Input")) , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) , mOverencumberedMessageDelay(0.f) @@ -111,7 +113,7 @@ namespace MWInput Ogre::RenderWindow* window = ogre.getWindow (); - mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow()); + mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow(), grab); mInputManager->setMouseEventCallback (this); mInputManager->setKeyboardEventCallback (this); mInputManager->setWindowEventCallback(this); @@ -158,20 +160,6 @@ namespace MWInput if (action == A_Use) { MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); - if (currentValue == 1) - { - int type = MWMechanics::CreatureStats::AT_Chop; - bool forward = (mInputBinder->getChannel(A_MoveForward)->getValue() > 0 - || mInputBinder->getChannel(A_MoveBackward)->getValue() > 0); - bool side = (mInputBinder->getChannel(A_MoveLeft)->getValue() > 0 - || mInputBinder->getChannel(A_MoveRight)->getValue() > 0); - if (side && !forward) - type = MWMechanics::CreatureStats::AT_Slash; - if (forward && !side) - type = MWMechanics::CreatureStats::AT_Thrust; - - MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackType(type); - } } if (currentValue == 1) @@ -180,10 +168,9 @@ namespace MWInput switch (action) { case A_GameMenu: - toggleMainMenu (); - break; - case A_Quit: - exitNow(); + if(!(MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running + && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu)) + toggleMainMenu (); break; case A_Screenshot: screenshot(); @@ -197,14 +184,7 @@ namespace MWInput case A_Activate: resetIdleTime(); - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container) - toggleContainer (); - else - MWBase::Environment::get().getWindowManager()->activateKeyPressed(); - } - else + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) activate(); break; case A_Journal: @@ -267,17 +247,18 @@ namespace MWInput void InputManager::update(float dt, bool loading) { + mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); + mInputManager->capture(loading); // 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) mInputBinder->update(dt); - bool main_menu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); + bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) + && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console; bool was_relative = mInputManager->getMouseRelative(); bool is_relative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -287,7 +268,7 @@ namespace MWInput mInputManager->setMouseRelative(is_relative); //we let the mouse escape in the main menu - mInputManager->setGrabPointer(!main_menu); + mInputManager->setGrabPointer(grab && (mGrabCursor || is_relative)); //we switched to non-relative mode, move our cursor to where the in-game //cursor is @@ -300,7 +281,9 @@ namespace MWInput return; // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode() + || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) + return; // Configure player movement according to keyboard input. Actual movement will @@ -354,7 +337,7 @@ namespace MWInput // 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 (); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); mOverencumberedMessageDelay -= dt; if (MWWorld::Class::get(player).getEncumbrance(player) >= MWWorld::Class::get(player).getCapacity(player)) { @@ -378,10 +361,9 @@ namespace MWInput MWBase::Environment::get().getWorld()->togglePreviewMode(true); } } else { - if (mPreviewPOVDelay > 0.5) { - //disable preview mode - MWBase::Environment::get().getWorld()->togglePreviewMode(false); - } else if (mPreviewPOVDelay > 0.f) { + //disable preview mode + MWBase::Environment::get().getWorld()->togglePreviewMode(false); + if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) { MWBase::Environment::get().getWorld()->togglePOV(); } mPreviewPOVDelay = 0.f; @@ -431,6 +413,9 @@ namespace MWInput if (it->first == "Input" && it->second == "ui sensitivity") mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input"); + if (it->first == "Input" && it->second == "grab cursor") + mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); + } } @@ -468,15 +453,46 @@ namespace MWInput bool InputManager::keyPressed( const SDL_KeyboardEvent &arg ) { - mInputBinder->keyPressed (arg); - - if(arg.keysym.sym == SDLK_RETURN - && MWBase::Environment::get().getWindowManager()->isGuiMode()) + // Cut, copy & paste + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (focus) { - // Pressing enter when a messagebox is prompting for "ok" will activate the ok button - MWBase::Environment::get().getWindowManager()->enterPressed(); + MyGUI::EditBox* edit = focus->castType(false); + if (edit && !edit->getEditReadOnly()) + { + if (arg.keysym.sym == SDLK_v && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) + { + char* text = SDL_GetClipboardText(); + + if (text) + { + edit->addText(MyGUI::UString(text)); + SDL_free(text); + } + } + if (arg.keysym.sym == SDLK_x && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) + { + std::string text = edit->getTextSelection(); + if (text.length()) + { + SDL_SetClipboardText(text.c_str()); + edit->deleteTextSelection(); + } + } + } + if (edit && !edit->getEditStatic()) + { + if (arg.keysym.sym == SDLK_c && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) + { + std::string text = edit->getTextSelection(); + if (text.length()) + SDL_SetClipboardText(text.c_str()); + } + } } + mInputBinder->keyPressed (arg); + OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); if (kc != OIS::KC_UNASSIGNED) @@ -514,7 +530,7 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) { MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType(false); - if (b) + if (b && b->getEnabled()) { MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f); } @@ -553,15 +569,6 @@ namespace MWInput mMouseWheel = int(arg.z); MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); - - //if the player is reading a book and flicking the mouse wheel - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Book && arg.zrel) - { - if (arg.zrel < 0) - MWBase::Environment::get().getWindowManager()->getBookWindow()->nextPage(); - else - MWBase::Environment::get().getWindowManager()->getBookWindow()->prevPage(); - } } if (mMouseLookEnabled) @@ -570,8 +577,6 @@ namespace MWInput double x = arg.xrel * mCameraSensitivity * (1.0f/256.f); double y = arg.yrel * mCameraSensitivity * (1.0f/256.f) * (mInvertY ? -1 : 1) * mCameraYMultiplier; - float scale = MWBase::Environment::get().getFrameDuration(); - if(scale <= 0.0f) scale = 1.0f; float rot[3]; rot[0] = -y; @@ -581,8 +586,8 @@ namespace MWInput // Only actually turn player when we're not in vanity mode if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) { - mPlayer->yaw(x/scale); - mPlayer->pitch(-y/scale); + mPlayer->yaw(x); + mPlayer->pitch(-y); } if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change @@ -609,6 +614,11 @@ namespace MWInput mOgre.windowResized(x,y); } + void InputManager::windowClosed() + { + MWBase::Environment::get().getStateManager()->requestQuit(); + } + void InputManager::toggleMainMenu() { if (MyGUI::InputManager::getInstance ().isModalAny()) @@ -633,7 +643,11 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the magic window is accessible - if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Magic)) + if (!mControlSwitch["playermagic"]) + return; + + // Not allowed if no spell selected + if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty()) return; MWMechanics::DrawState_ state = mPlayer->getDrawState(); @@ -648,7 +662,7 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the inventory window is accessible - if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + if (!mControlSwitch["playerfighting"]) return; MWMechanics::DrawState_ state = mPlayer->getDrawState(); @@ -693,21 +707,6 @@ namespace MWInput // .. but don't touch any other mode, except container. } - void InputManager::toggleContainer() - { - if (MyGUI::InputManager::getInstance ().isModalAny()) - return; - - if(MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container) - MWBase::Environment::get().getWindowManager()->popGuiMode(); - else - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container); - } - - } - void InputManager::toggleConsole() { if (MyGUI::InputManager::getInstance ().isModalAny()) @@ -753,7 +752,8 @@ namespace MWInput void InputManager::showQuickKeysMenu() { - if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode () + && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); @@ -779,13 +779,6 @@ namespace MWInput mAlwaysRunActive = !mAlwaysRunActive; } - // Exit program now button (which is disabled in GUI mode) - void InputManager::exitNow() - { - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) - Ogre::Root::getSingleton().queueEndRendering (); - } - void InputManager::resetIdleTime() { if (mTimeIdle < 0) @@ -795,9 +788,11 @@ namespace MWInput void InputManager::updateIdleTime(float dt) { + static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get() + .find("fVanityDelay")->getFloat(); if (mTimeIdle >= 0.f) mTimeIdle += dt; - if (mTimeIdle > 30.f) { + if (mTimeIdle > vanityDelay) { MWBase::Environment::get().getWorld()->toggleVanityMode(true); mTimeIdle = -1.f; } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 5f9a752d7..d41b4c3f3 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -1,5 +1,5 @@ -#ifndef _MWINPUT_MWINPUTMANAGERIMP_H -#define _MWINPUT_MWINPUTMANAGERIMP_H +#ifndef MWINPUT_MWINPUTMANAGERIMP_H +#define MWINPUT_MWINPUTMANAGERIMP_H #include "../mwgui/mode.hpp" @@ -61,7 +61,7 @@ namespace MWInput public: InputManager(OEngine::Render::OgreRenderer &_ogre, OMW::Engine& engine, - const std::string& userFile, bool userFileExists); + const std::string& userFile, bool userFileExists, bool grab); virtual ~InputManager(); @@ -97,6 +97,7 @@ namespace MWInput virtual void windowVisibilityChange( bool visible ); virtual void windowFocusChange( bool have_focus ); virtual void windowResized (int x, int y); + virtual void windowClosed (); virtual void channelChanged(ICS::Channel* channel, float currentValue, float previousValue); @@ -137,6 +138,8 @@ namespace MWInput bool mDragDrop; + bool mGrabCursor; + bool mInvertY; float mCameraSensitivity; @@ -170,14 +173,12 @@ namespace MWInput void toggleSpell(); void toggleWeapon(); void toggleInventory(); - void toggleContainer(); void toggleConsole(); void screenshot(); void toggleJournal(); void activate(); void toggleWalking(); void toggleAutoMove(); - void exitNow(); void rest(); void quickKey (int index); @@ -194,7 +195,7 @@ namespace MWInput A_GameMenu, - A_Quit, // Exit the program + A_Unused, A_Screenshot, // Take a screenshot diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 9aca6b7b7..994798b0b 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,26 +1,8 @@ - #include "activespells.hpp" -#include - -#include - -#include -#include -#include -#include -#include - -#include "../mwworld/esmstore.hpp" - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" - -#include "creaturestats.hpp" -#include "npcstats.hpp" - namespace MWMechanics { void ActiveSpells::update() const @@ -29,6 +11,7 @@ namespace MWMechanics MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); + // Erase no longer active spells if (mLastUpdate!=now) { TContainer::iterator iter (mSpells.begin()); @@ -62,162 +45,28 @@ namespace MWMechanics for (TIterator iter (begin()); iter!=end(); ++iter) { - std::pair > effects = getEffectList (iter->first); + const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - const MWWorld::TimeStamp& start = iter->second.first; - float magnitude = iter->second.second; + const std::vector& effects = iter->second.mEffects; - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { - if (iter->mDuration) - { - int duration = iter->mDuration; - - if (effects.second.first) - duration *= magnitude; - - MWWorld::TimeStamp end = start; - end += static_cast (duration)* - MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - - if (end>now) - { - EffectParam param; - - if (effects.second.first) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); - - if (iter->mDuration==0) - { - param.mMagnitude = - static_cast (magnitude / (0.1 * magicEffect->mData.mBaseCost)); - } - else - { - param.mMagnitude = - static_cast (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost)); - } - } - else - param.mMagnitude = static_cast ( - (iter->mMagnMax-iter->mMagnMin)*magnitude + iter->mMagnMin); - - mEffects.add (*iter, param); - } - } + int duration = effectIt->mDuration; + MWWorld::TimeStamp end = start; + end += static_cast (duration)* + MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); + + if (end>now) + mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude)); } } } - 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, std::make_pair(false, false)); - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (potion->mEffects, std::make_pair(false, true)); - - 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; - - std::pair > result; - result.second.second = true; - result.second.first = true; - - result.first.mList.push_back (effect); - - return result; - } - - throw std::runtime_error ("ID " + id + " can not produce lasting effects"); - } - ActiveSpells::ActiveSpells() - : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) + : mSpellsChanged (false) + , mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor) - { - std::pair > effects = getEffectList (id); - bool stacks = effects.second.second; - - bool found = false; - - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) - { - if (iter->mDuration) - { - found = true; - break; - } - } - - if (!found) - return false; - - TContainer::iterator iter = mSpells.find (id); - - float random = static_cast (std::rand()) / RAND_MAX; - - if (effects.second.first) - { - // ingredient -> special treatment required. - const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); - const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - random *= 100; - random = random / std::min (x, 100.0f); - random *= 0.25 * x; - } - - if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, - std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random))); - else - iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random); - - mSpellsChanged = true; - - return true; - } - - void ActiveSpells::removeSpell (const std::string& id) - { - TContainer::iterator iter = mSpells.find (id); - - if (iter!=mSpells.end()) - { - mSpells.erase (iter); - mSpellsChanged = true; - } - } - const MagicEffects& ActiveSpells::getMagicEffects() const { update(); @@ -226,36 +75,31 @@ namespace MWMechanics ActiveSpells::TIterator ActiveSpells::begin() const { - update(); return mSpells.begin(); } ActiveSpells::TIterator ActiveSpells::end() const { - update(); return mSpells.end(); } double ActiveSpells::timeToExpire (const TIterator& iterator) const { - std::pair > effects = getEffectList (iterator->first); + const std::vector& effects = iterator->second.mEffects; int duration = 0; - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + for (std::vector::const_iterator iter (effects.begin()); + iter!=effects.end(); ++iter) { if (iter->mDuration > duration) duration = iter->mDuration; } - if (effects.second.first) - duration *= iterator->second.second; - double scaledDuration = duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.first; + double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp; if (usedUp>=scaledDuration) return 0; @@ -281,4 +125,102 @@ namespace MWMechanics { return mSpells; } + + void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, + const std::string &displayName, const std::string& casterHandle) + { + bool exists = false; + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + if (id == it->first) + exists = true; + } + + ActiveSpellParams params; + params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + params.mEffects = effects; + params.mDisplayName = displayName; + params.mCasterHandle = casterHandle; + + if (!exists || stack) + mSpells.insert (std::make_pair(id, params)); + else + mSpells.find(id)->second = params; + + mSpellsChanged = true; + } + + void ActiveSpells::removeEffects(const std::string &id) + { + mSpells.erase(Misc::StringUtils::lowerCase(id)); + mSpellsChanged = true; + } + + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + + for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end(); ++effectIt) + { + std::string name = it->second.mDisplayName; + + float remainingTime = effectIt->mDuration + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + float magnitude = effectIt->mMagnitude; + + if (magnitude) + visitor.visit(effectIt->mKey, name, it->second.mCasterHandle, magnitude, remainingTime); + } + } + } + + void ActiveSpells::purgeAll(float chance) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) + { + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + mSpells.erase(it++); + else + ++it; + } + mSpellsChanged = true; + } + + void ActiveSpells::purgeEffect(short effectId) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + for (std::vector::iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end();) + { + if (effectIt->mKey.mId == effectId) + effectIt = it->second.mEffects.erase(effectIt); + else + effectIt++; + } + } + mSpellsChanged = true; + } + + void ActiveSpells::purge(const std::string &actorHandle) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + for (std::vector::iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end();) + { + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mKey.mId); + if (effect->mData.mFlags & ESM::MagicEffect::CasterLinked + && it->second.mCasterHandle == actorHandle) + effectIt = it->second.mEffects.erase(effectIt); + else + effectIt++; + } + } + mSpellsChanged = true; + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 8c859b2cb..7a40afb4c 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -9,16 +9,7 @@ #include "magiceffects.hpp" -namespace ESM -{ - struct Spell; - struct EffectList; -} - -namespace MWWorld -{ - class Ptr; -} +#include namespace MWMechanics { @@ -30,12 +21,32 @@ namespace MWMechanics { public: - typedef std::multimap > TContainer; + // Parameters of an effect concerning lasting effects. + // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. + // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. + struct Effect + { + float mMagnitude; + EffectKey mKey; + float mDuration; + }; + + struct ActiveSpellParams + { + std::vector mEffects; + MWWorld::TimeStamp mTimeStamp; + std::string mDisplayName; + + // Handle to the caster that that inflicted this spell on us + std::string mCasterHandle; + }; + + typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; private: - mutable TContainer mSpells; // spellId, (time of casting, relative magnitude) + mutable TContainer mSpells; mutable MagicEffects mEffects; mutable bool mSpellsChanged; mutable MWWorld::TimeStamp mLastUpdate; @@ -44,35 +55,51 @@ namespace MWMechanics void rebuildEffects() const; - std::pair > getEffectList (const std::string& id) const; - ///< @return (EffectList, (isIngredient, stacks)) + double timeToExpire (const TIterator& iterator) const; + ///< Returns time (in in-game hours) until the spell pointed to by \a iterator + /// expires. + + const TContainer& getActiveSpells() const; + + TIterator begin() const; + + TIterator end() const; public: ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor); - ///< Overwrites an existing spell with the same ID. If the spell does not have any - /// non-instant effects, it is ignored. + /// Add lasting effects + /// + /// \brief addSpell + /// \param id ID for stacking purposes. + /// \param stack If false, the spell is not added if one with the same ID exists already. + /// \param effects + /// \param displayName Name for display in magic menu. + /// \param casterHandle /// - /// \return Has the spell been added? + void addSpell (const std::string& id, bool stack, std::vector effects, + const std::string& displayName, const std::string& casterHandle); + + /// Removes the active effects from this spell/potion/.. with \a id + void removeEffects (const std::string& id); + + /// Remove all active effects with this effect id + void purgeEffect (short effectId); - void removeSpell (const std::string& id); + /// Remove all active effects, if roll succeeds (for each effect) + void purgeAll (float chance); + + /// Remove all effects with CASTER_LINKED flag that were cast by \a actorHandle + void purge (const std::string& actorHandle); bool isSpellActive (std::string id) const; ///< case insensitive const MagicEffects& getMagicEffects() const; - const TContainer& getActiveSpells() const; + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - TIterator begin() const; - - TIterator end() const; - - double timeToExpire (const TIterator& iterator) const; - ///< Returns time (in in-game hours) until the spell pointed to by \a iterator - /// expires. }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 70b820f8c..1fb22ce63 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -11,6 +11,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/player.hpp" #include "../mwbase/world.hpp" @@ -22,30 +24,212 @@ #include "creaturestats.hpp" #include "movement.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + +#include "aicombat.hpp" +#include "aifollow.hpp" + +namespace +{ + +void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) +{ + if (bound) + { + MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); + MWWorld::ActionEquip action(newPtr); + action.execute(actor); + } + else + { + actor.getClass().getContainerStore(actor).remove(item, 1, actor); + } +} + +bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) +{ + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator item = + inv.getSlot(slot); + if (item != inv.end()) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + if (item->getCellRef().mCharge == -1) + item->getCellRef().mCharge = item->getClass().getItemMaxHealth(*item); + + if (item->getCellRef().mCharge == 0) + return false; + + item->getCellRef().mCharge -= + std::min(disintegrate, + static_cast(item->getCellRef().mCharge)); + + if (item->getCellRef().mCharge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr.getRefData().getHandle() != "player") + inv.autoEquip(ptr); + else + inv.unequipItem(*item, ptr); + } + + return true; + } + } + return false; +} + +void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) +{ + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); + + bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).mMagnitude > 0; + int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + + health = 0.1 * endurance; + + magicka = 0; + if (!stunted) + { + float fRestMagicMult = settings.find("fRestMagicMult")->getFloat (); + magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + } +} + +} + namespace MWMechanics { + + class SoulTrap : public MWMechanics::EffectSourceVisitor + { + MWWorld::Ptr mCreature; + MWWorld::Ptr mActor; + public: + SoulTrap(MWWorld::Ptr trappedCreature) + : mCreature(trappedCreature) {} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime = -1) + { + if (key.mId != ESM::MagicEffect::Soultrap) + return; + if (magnitude <= 0) + return; + + MWBase::World* world = MWBase::Environment::get().getWorld(); + + MWWorld::Ptr caster = world->searchPtrViaHandle(casterHandle); + if (caster.isEmpty() || !caster.getClass().isActor()) + return; + + static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->getFloat(); + + float creatureSoulValue = mCreature.get()->mBase->mData.mSoul; + if (creatureSoulValue == 0) + return; + + // Use the smallest soulgem that is large enough to hold the soul + MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); + MWWorld::ContainerStoreIterator gem = container.end(); + float gemCapacity = std::numeric_limits().max(); + std::string soulgemFilter = "misc_soulgem"; // no other way to check for soulgems? :/ + for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); + it != container.end(); ++it) + { + const std::string& id = it->getCellRef().mRefID; + if (id.size() >= soulgemFilter.size() + && id.substr(0,soulgemFilter.size()) == soulgemFilter) + { + float thisGemCapacity = it->get()->mBase->mData.mValue * fSoulgemMult; + if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity + && it->getCellRef().mSoul.empty()) + { + gem = it; + gemCapacity = thisGemCapacity; + } + } + } + + if (gem == container.end()) + return; + + // Set the soul on just one of the gems, not the whole stack + gem->getContainerStore()->unstack(*gem, caster); + gem->getCellRef().mSoul = mCreature.getCellRef().mRefID; + + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); + } + }; + void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects adjustMagicEffects (ptr); - calculateDynamicStats (ptr); - calculateCreatureStatModifiers (ptr); + if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) + calculateDynamicStats (ptr); + calculateCreatureStatModifiers (ptr, duration); - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) + // AI + if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - // AI CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - creatureStats.getAiSequence().execute (ptr); + //engage combat or not? + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if(ptr != player && !creatureStats.isHostile()) + { + ESM::Position playerpos = player.getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified(); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) + { + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayer().getPlayer())); + creatureStats.setHostile(true); + } + } - // fatigue restoration - calculateRestoration(ptr, duration); + creatureStats.getAiSequence().execute (ptr,duration); } + + // fatigue restoration + calculateRestoration(ptr, duration, false); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) { if(!paused) + { updateDrowning(ptr, duration); + calculateNpcStatModifiers(ptr); + updateEquippedLight(ptr, duration); + } } void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) @@ -62,7 +246,7 @@ namespace MWMechanics now += creatureStats.getActiveSpells().getMagicEffects(); - MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now); + //MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now); creatureStats.setMagicEffects(now); @@ -93,42 +277,37 @@ namespace MWMechanics creatureStats.setFatigue(fatigue); } - void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) + void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep) { - CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return; - int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); - - float capacity = MWWorld::Class::get(ptr).getCapacity(ptr); - float encumbrance = MWWorld::Class::get(ptr).getEncumbrance(ptr); - float normalizedEncumbrance = (capacity == 0 ? 1 : encumbrance/capacity); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - if (duration == 3600) + if (sleep) { - // the actor is sleeping, restore health and magicka + float health, magicka; + getRestorationPerHourOfSleep(ptr, health, magicka); - bool stunted = stats.getMagicEffects ().get(MWMechanics::EffectKey(ESM::MagicEffect::StuntedMagicka)).mMagnitude > 0; + DynamicStat stat = stats.getHealth(); + stat.setCurrent(stat.getCurrent() + health); + stats.setHealth(stat); - DynamicStat health = stats.getHealth(); - health.setCurrent (health.getCurrent() + 0.1 * endurance); - stats.setHealth (health); + stat = stats.getMagicka(); + stat.setCurrent(stat.getCurrent() + magicka); + stats.setMagicka(stat); + } - if (!stunted) - { - float fRestMagicMult = settings.find("fRestMagicMult")->getFloat (); + int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); - DynamicStat magicka = stats.getMagicka(); - magicka.setCurrent (magicka.getCurrent() - + fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified()); - stats.setMagicka (magicka); - } - } + float capacity = ptr.getClass().getCapacity(ptr); + float encumbrance = ptr.getClass().getEncumbrance(ptr); + float normalizedEncumbrance = (capacity == 0 ? 1 : encumbrance/capacity); + if (normalizedEncumbrance > 1) + normalizedEncumbrance = 1; // restore fatigue - float fFatigueReturnBase = settings.find("fFatigueReturnBase")->getFloat (); float fFatigueReturnMult = settings.find("fFatigueReturnMult")->getFloat (); float fEndFatigueMult = settings.find("fEndFatigueMult")->getFloat (); @@ -139,9 +318,10 @@ namespace MWMechanics DynamicStat fatigue = stats.getFatigue(); fatigue.setCurrent (fatigue.getCurrent() + duration * x); stats.setFatigue (fatigue); + } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) + void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); @@ -149,9 +329,10 @@ namespace MWMechanics // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { - Stat stat = creatureStats.getAttribute(i); + AttributeValue stat = creatureStats.getAttribute(i); stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude); + effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude); creatureStats.setAttribute(i, stat); } @@ -160,11 +341,238 @@ namespace MWMechanics for(int i = 0;i < 3;++i) { DynamicStat stat = creatureStats.getDynamic(i); - stat.setModifier(effects.get(EffectKey(80+i)).mMagnitude - - effects.get(EffectKey(18+i)).mMagnitude); + stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).mMagnitude - + effects.get(ESM::MagicEffect::DrainHealth+i).mMagnitude); + + + float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).mMagnitude; + stat.setCurrent(stat.getCurrent() + currentDiff * duration, i == 2); creatureStats.setDynamic(i, stat); } + + // AI setting modifiers + int creature = !ptr.getClass().isNpc(); + if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Humanoid) + creature = false; + // Note: the Creature variants only work on normal creatures, not on daedra or undead creatures. + if (!creature || ptr.get()->mBase->mData.mType == ESM::Creature::Creatures) + { + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid+creature).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).mMagnitude); + creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); + + stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid+creature).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::RallyHumanoid+creature).mMagnitude); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) + { + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).mMagnitude); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + + // Apply disintegration (reduces item health) + float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).mMagnitude; + if (disintegrateWeapon > 0) + disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration); + float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).mMagnitude; + if (disintegrateArmor > 0) + { + // According to UESP + int priorities[] = { + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots + }; + + for (unsigned int i=0; i health = creatureStats.getHealth(); + for (unsigned int i=0; iisExterior()) + continue; + float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); + float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float damageScale = 1.f - timeDiff / 7.f; + // When cloudy, the sun damage effect is halved + static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicSunBlockedMult")->getFloat(); + + int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); + if (weather > 1) + damageScale *= fMagicSunBlockedMult; + health.setCurrent(health.getCurrent() - magnitude * duration * damageScale); + } + else + health.setCurrent(health.getCurrent() - magnitude * duration); + + } + creatureStats.setHealth(health); + + // TODO: dirty flag for magic effects to avoid some unnecessary work below? + + // Update bound effects + static std::map boundItemsMap; + if (boundItemsMap.empty()) + { + boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID"; + boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID"; + boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID"; + boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID"; + boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below) + boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID"; + boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID"; + boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID"; + boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID"; + boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID"; + boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID"; + } + + for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) + { + bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); + int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; + if (found != (magnitude > 0)) + { + std::string itemGmst = it->second; + std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( + itemGmst)->getString(); + if (it->first == ESM::MagicEffect::BoundGloves) + { + adjustBoundItem("sMagicBoundLeftGauntletID", magnitude > 0, ptr); + adjustBoundItem("sMagicBoundRightGauntletID", magnitude > 0, ptr); + } + else + adjustBoundItem(item, magnitude > 0, ptr); + + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + } + } + + // Update summon effects + static std::map summonMap; + if (summonMap.empty()) + { + summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; + summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID"; + summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID"; + summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID"; + summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID"; + summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID"; + summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID"; + summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID"; + summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID"; + summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID"; + summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID"; + summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID"; + summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID"; + summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID"; + summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID"; + summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID"; + summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID"; + summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID"; + summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID"; + summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID"; + summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID"; + summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; + } + + for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) + { + bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); + int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; + if (found != (magnitude > 0)) + { + if (magnitude > 0) + { + ESM::Position ipos = ptr.getRefData().getPosition(); + Ogre::Vector3 pos(ipos.pos); + Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + const float distance = 50; + pos = pos + distance*rot.yAxis(); + ipos.pos[0] = pos.x; + ipos.pos[1] = pos.y; + ipos.pos[2] = pos.z; + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + + std::string creatureID = + MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); + + if (!creatureID.empty()) + { + MWWorld::CellStore* store = ptr.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); + ref.getPtr().getCellRef().mPos = ipos; + + // TODO: Add AI to follow player and fight for him + // TODO: VFX_SummonStart, VFX_SummonEnd + creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + } + } + else + { + std::string handle = creatureStats.mSummonedCreatures[it->first]; + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + if (!ptr.isEmpty()) + { + MWBase::Environment::get().getWorld()->deleteObject(ptr); + creatureStats.mSummonedCreatures.erase(it->first); + } + } + } + } + } + + void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) + { + NpcStats &npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + const MagicEffects &effects = npcStats.getMagicEffects(); + + // skills + for(int i = 0;i < ESM::Skill::Length;++i) + { + SkillValue& skill = npcStats.getSkill(i); + skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude); + } } void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) @@ -187,10 +595,11 @@ namespace MWMechanics if(timeLeft == 0.0f) { // If drowning, apply 3 points of damage per second - ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - 3.0f*duration); + static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->getFloat(); + ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - fSuffocationDamage*duration); // Play a drowning sound as necessary for the player - if(ptr == world->getPlayer().getPlayer()) + if(ptr == world->getPlayerPtr()) { MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); if(!sndmgr->getSoundPlaying(MWWorld::Ptr(), "drown")) @@ -199,10 +608,120 @@ namespace MWMechanics } } else - stats.setTimeToStartDrowning(20); + { + static const float fHoldBreathTime = world->getStore().get().find("fHoldBreathTime")->getFloat(); + stats.setTimeToStartDrowning(fHoldBreathTime); + } } - Actors::Actors() : mDuration (0) {} + void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration) + { + bool isPlayer = ptr.getRefData().getHandle()=="player"; + + MWWorld::InventoryStore &inventoryStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); + MWWorld::ContainerStoreIterator heldIter = + inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + /** + * Automatically equip NPCs torches at night and unequip them at day + */ + if (!isPlayer) + { + MWWorld::ContainerStoreIterator torch = inventoryStore.end(); + for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) + { + if (it->getTypeName() == typeid(ESM::Light).name()) + { + torch = it; + break; + } + } + + if (MWBase::Environment::get().getWorld()->isDark()) + { + if (torch != inventoryStore.end()) + { + if (!MWWorld::Class::get (ptr).getCreatureStats (ptr).isHostile()) + { + // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. + if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) + inventoryStore.unequipItem(*heldIter, ptr); + + // Also unequip twohanded weapons which conflict with anything in CarriedLeft + if (torch->getClass().canBeEquipped(*torch, ptr).first == 3) + inventoryStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, ptr); + } + + heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + // If we have a torch and can equip it (left slot free, no + // twohanded weapon in right slot), then equip it now. + if (heldIter == inventoryStore.end() + && torch->getClass().canBeEquipped(*torch, ptr).first == 1) + { + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); + } + } + } + else + { + if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name()) + { + // At day, unequip lights and auto equip shields or other suitable items + // (Note: autoEquip will ignore lights) + inventoryStore.autoEquip(ptr); + } + } + } + + heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + //If holding a light... + if(heldIter.getType() == MWWorld::ContainerStore::Type_Light) + { + // Use time from the player's light + if(isPlayer) + { + float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter); + + // -1 is infinite light source. Other negative values are treated as 0. + if(timeRemaining != -1.0f) + { + timeRemaining -= duration; + + if(timeRemaining > 0.0f) + heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining); + else + { + inventoryStore.remove(*heldIter, 1, ptr); // remove it + return; + } + } + } + + // Both NPC and player lights extinguish in water. + if(MWBase::Environment::get().getWorld()->isSwimming(ptr)) + { + inventoryStore.remove(*heldIter, 1, ptr); // remove it + + // ...But, only the player makes a sound. + if(isPlayer) + MWBase::Environment::get().getSoundManager()->playSound("torch out", + 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoEnv); + } + } + } + + Actors::Actors() {} + + Actors::~Actors() + { + PtrControllerMap::iterator it(mActors.begin()); + for (; it != mActors.end(); ++it) + { + delete it->second; + it->second = NULL; + } + } void Actors::addActor (const MWWorld::Ptr& ptr) { @@ -238,12 +757,12 @@ namespace MWMechanics } } - void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore) + void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore) { PtrControllerMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { - if(iter->first.getCell()==cellStore) + if(iter->first.getCell()==cellStore && iter->first != ignore) { delete iter->second; mActors.erase(iter++); @@ -255,71 +774,130 @@ namespace MWMechanics void Actors::update (float duration, bool paused) { - mDuration += duration; - - //if (mDuration>=0.25) + if(!paused) { - float totalDuration = mDuration; - mDuration = 0; + // Reset data from previous frame + for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + // Reset last hit object, which is only valid for one frame + // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation + // (below) + iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); + } + + // AI and magic effects update + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) + { + updateActor(iter->first, duration); + if(iter->first.getTypeName() == typeid(ESM::NPC).name()) + updateNpc(iter->first, duration, paused); + } + } + + // Looping magic VFX update + // Note: we need to do this before any of the animations are updated. + // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), + // so updating VFX immediately after that would just remove the particle effects instantly. + // There needs to be a magic effect update in between. + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + iter->second->updateContinuousVfx(); + // Animation/movement update + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( + ESM::MagicEffect::Paralyze).mMagnitude > 0) + iter->second->skipAnim(); + iter->second->update(duration); + } + + // Kill dead actors for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { const MWWorld::Class &cls = MWWorld::Class::get(iter->first); CreatureStats &stats = cls.getCreatureStats(iter->first); - stats.setLastHitObject(std::string()); if(!stats.isDead()) { if(iter->second->isDead()) iter->second->resurrect(); - updateActor(iter->first, totalDuration); - if(iter->first.getTypeName() == typeid(ESM::NPC).name()) - updateNpc(iter->first, totalDuration, paused); - if(!stats.isDead()) continue; } // If it's the player and God Mode is turned on, keep it alive - if(iter->first.getRefData().getHandle()=="player" && + if(iter->first.getRefData().getHandle()=="player" && MWBase::Environment::get().getWorld()->getGodModeState()) { - MWMechanics::DynamicStat stat(stats.getHealth()); + MWMechanics::DynamicStat stat (stats.getHealth()); - if(stat.getModified()<1) + if (stat.getModified()<1) { stat.setModified(1, 0); stats.setHealth(stat); } - stats.resurrect(); continue; } - if(iter->second->isDead()) - continue; + // Make sure spell effects with CasterLinked flag are removed + // TODO: would be nice not to do this all the time... + for(PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + { + MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); + spells.purge(iter->first.getRefData().getHandle()); + } - iter->second->kill(); + // FIXME: see http://bugs.openmw.org/issues/869 + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); - ++mDeathCount[cls.getId(iter->first)]; + if (iter->second->kill()) + { + ++mDeathCount[cls.getId(iter->first)]; - if(cls.isEssential(iter->first)) - MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); - } - } + // Apply soultrap + if (iter->first.getTypeName() == typeid(ESM::Creature).name()) + { + SoulTrap soulTrap (iter->first); + stats.getActiveSpells().visitEffectSources(soulTrap); + } - if(!paused) - { - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - iter->second->update(duration); + // Reset magic effects and recalculate derived effects + // One case where we need this is to make sure bound items are removed upon death + stats.setMagicEffects(MWMechanics::MagicEffects()); + calculateCreatureStatModifiers(iter->first, 0); + + if(cls.isEssential(iter->first)) + MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); + } + } } } - - void Actors::restoreDynamicStats() + void Actors::restoreDynamicStats(bool sleep) { for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - calculateRestoration(iter->first, 3600); + calculateRestoration(iter->first, 3600, sleep); + } + + int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const + { + float healthPerHour, magickaPerHour; + getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour); + + CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + float healthHours = healthPerHour >= 0 + ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour + : 1.0f; + float magickaHours = magickaPerHour >= 0 + ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour + : 1.0f; + + int autoHours = std::ceil(std::max(1.f, std::max(healthHours, magickaHours))); + return autoHours; } int Actors::countDeaths (const std::string& id) const @@ -357,4 +935,31 @@ namespace MWMechanics return iter->second->isAnimPlaying(groupName); return false; } + + void Actors::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out) + { + for (PtrControllerMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + { + if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius) + out.push_back(iter->first); + } + } + + std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) + { + std::list list; + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) + { + const MWWorld::Class &cls = MWWorld::Class::get(iter->first); + CreatureStats &stats = cls.getCreatureStats(iter->first); + + if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow) + { + MWMechanics::AiFollow* package = static_cast(stats.getAiSequence().getActivePackage()); + if(package->getFollowedActor() == actor.getCellRef().mRefID) + list.push_front(iter->first); + } + } + return list; + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 69878a000..4b18ac862 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,28 +25,36 @@ namespace MWMechanics { class Actors { - typedef std::map PtrControllerMap; - PtrControllerMap mActors; - - std::map mDeathCount; + std::map mDeathCount; - float mDuration; - - void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); + void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); void adjustMagicEffects (const MWWorld::Ptr& creature); void calculateDynamicStats (const MWWorld::Ptr& ptr); - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr); + void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); + void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); - void calculateRestoration (const MWWorld::Ptr& ptr, float duration); + void calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep); void updateDrowning (const MWWorld::Ptr& ptr, float duration); + void updateEquippedLight (const MWWorld::Ptr& ptr, float duration); + public: Actors(); + ~Actors(); + + typedef std::map PtrControllerMap; + + PtrControllerMap::const_iterator begin() { return mActors.begin(); } + PtrControllerMap::const_iterator end() { return mActors.end(); } + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } void addActor (const MWWorld::Ptr& ptr); ///< Register an actor for stats management @@ -61,8 +69,8 @@ namespace MWMechanics 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 dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); + ///< Deregister all actors (except for \a ignore) in the given cell. void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement @@ -71,9 +79,12 @@ namespace MWMechanics ///< This function is normally called automatically during the update process, but it can /// also be called explicitly at any time to force an update. - void restoreDynamicStats(); + void restoreDynamicStats(bool sleep); ///< If the player is sleeping, this should be called every hour. - + + int getHoursToRest(const MWWorld::Ptr& ptr) const; + ///< Calculate how many hours the given actor needs to rest in order to be fully healed + int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. @@ -82,6 +93,15 @@ namespace MWMechanics void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); void skipAnimation(const MWWorld::Ptr& ptr); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); + + void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out); + + std::list getActorsFollowing(const MWWorld::Ptr& actor); + /// +#include -MWMechanics::AiActivate::AiActivate(const std::string &objectId) -: mObjectId(objectId) -{ -} -MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const -{ - return new AiActivate(*this); -} -bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor) -{ - std::cout << "AiActivate completed.\n"; - return true; -} - -int MWMechanics::AiActivate::getTypeId() const -{ - return 4; -} +MWMechanics::AiActivate::AiActivate(const std::string &objectId) +: mObjectId(objectId) +{ +} +MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const +{ + return new AiActivate(*this); +} +bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) +{ + std::cout << "AiActivate completed.\n"; + return true; +} + +int MWMechanics::AiActivate::getTypeId() const +{ + return TypeIdActivate; +} diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 7f3d4016d..f922e238c 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -12,7 +12,7 @@ namespace MWMechanics public: AiActivate(const std::string &objectId); virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp new file mode 100644 index 000000000..3653587f8 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -0,0 +1,407 @@ +#include "aicombat.hpp" + +#include +#include + + +#include "../mwworld/class.hpp" +#include "../mwworld/timestamp.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" + + +#include "npcstats.hpp" +#include "steering.hpp" +#include "movement.hpp" +#include "character.hpp" // fixme: for getActiveWeapon + +namespace +{ + static float sgn(Ogre::Radian a) + { + if(a.valueDegrees() > 0) + return 1.0; + return -1.0; + } + + //chooses an attack depending on probability to avoid uniformity + void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); +} + +namespace MWMechanics +{ + AiCombat::AiCombat(const MWWorld::Ptr& actor) : + mTarget(actor), + mTimerAttack(0), + mTimerReact(0), + mTimerCombatMove(0), + mFollowTarget(false), + mReadyToAttack(false), + mStrike(false), + mCombatMove(false), + mRotate(false), + mMovement(), + mTargetAngle(0) + { + } + + bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) + { + //General description + if(!actor.getClass().getCreatureStats(actor).isHostile() + || actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0) + return true; + + if(mTarget.getClass().getCreatureStats(mTarget).isDead()) + return true; + + //Update every frame + if(mCombatMove) + { + mTimerCombatMove -= duration; + if( mTimerCombatMove <= 0) + { + mTimerCombatMove = 0; + mMovement.mPosition[1] = mMovement.mPosition[0] = 0; + mCombatMove = false; + } + } + + actor.getClass().getMovementSettings(actor) = mMovement; + + if (mRotate) + { + if (zTurn(actor, Ogre::Degree(mTargetAngle))) + mRotate = false; + } + + + mTimerAttack -= duration; + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); + + float tReaction = 0.25f; + if(mTimerReact < tReaction) + { + mTimerReact += duration; + return false; + } + + //Update with period = tReaction + + mTimerReact = 0; + + //actual attacking logic + //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f + float attackPeriod = 1.0f; + if(mReadyToAttack) + { + if(mTimerAttack <= -attackPeriod) + { + //TODO: should depend on time between 'start' to 'min attack' + //for better controlling of NPCs' attack strength. + //Also it seems that this time is different for slash/thrust/chop + mTimerAttack = 0.35f * static_cast(rand())/RAND_MAX; + mStrike = true; + + //say a provoking combat phrase + if (actor.getClass().isNpc()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } + } + } + else if (mTimerAttack <= 0) + mStrike = false; + } + else + { + mTimerAttack = -attackPeriod; + mStrike = false; + } + + const MWWorld::Class &cls = actor.getClass(); + const ESM::Weapon *weapon = NULL; + MWMechanics::WeaponType weaptype; + float weapRange, weapSpeed = 1.0f; + + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + + if (actor.getClass().hasInventoryStore(actor)) + { + MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + + //Get weapon speed and range + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(cls.getNpcStats(actor), cls.getInventoryStore(actor), &weaptype); + if (weaptype == WeapType_HandToHand) + { + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + weapRange = gmst.find("fHandToHandReach")->getFloat(); + } + else + { + weapon = weaponSlot->get()->mBase; + weapRange = weapon->mData.mReach; + weapSpeed = weapon->mData.mSpeed; + } + weapRange *= 100.0f; + } + else //is creature + { + weaptype = WeapType_HandToHand; //doesn't matter, should only reflect if it is melee or distant weapon + weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) + } + + ESM::Position pos = actor.getRefData().getPosition(); + + float rangeMelee; + float rangeCloseUp; + bool distantCombat = false; + if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown) + { + rangeMelee = 1000; // TODO: should depend on archer skill + rangeCloseUp = 0; //doesn't needed when attacking from distance + distantCombat = true; + } + else + { + rangeMelee = weapRange; + rangeCloseUp = 300; + } + + Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]); + ESM::Position targetPos = mTarget.getRefData().getPosition(); + Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]); + Ogre::Vector3 vDir = vDest - vStart; + float distBetween = vDir.length(); + + if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) ) + { + //Melee and Close-up combat + vDir.z = 0; + float dirLen = vDir.length(); + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); + mRotate = true; + + //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); + if (mFollowTarget && distBetween > rangeMelee) + { + //Close-up combat: just run up on target + mMovement.mPosition[1] = 1; + } + else + { + //Melee: stop running and attack + mMovement.mPosition[1] = 0; + + // When attacking with a weapon, choose between slash, thrust or chop + if (actor.getClass().hasInventoryStore(actor)) + chooseBestAttack(weapon, mMovement); + + if(mMovement.mPosition[0] || mMovement.mPosition[1]) + { + mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; + mCombatMove = true; + } + else if(actor.getClass().isNpc() && (!distantCombat || (distantCombat && rangeMelee/5))) + { + //apply sideway movement (kind of dodging) with some probability + if(static_cast(rand())/RAND_MAX < 0.25) + { + mMovement.mPosition[0] = static_cast(rand())/RAND_MAX < 0.5? 1: -1; + mTimerCombatMove = 0.05f + 0.15f * static_cast(rand())/RAND_MAX; + mCombatMove = true; + } + } + + if(distantCombat && distBetween < rangeMelee/4) + { + mMovement.mPosition[1] = -1; + } + + mReadyToAttack = true; + //only once got in melee combat, actor is allowed to use close-up shortcutting + mFollowTarget = true; + } + } + else + { + //target is at far distance: build path to target OR follow target (if previously actor had reached it once) + mFollowTarget = false; + + buildNewPath(actor); + + //delete visited path node + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); + + //try shortcut + if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); + else + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mRotate = true; + + mMovement.mPosition[1] = 1; + mReadyToAttack = false; + } + + if(distBetween > rangeMelee) + { + //special run attack; it shouldn't affect melee combat tactics + if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1) + { + //check if actor can overcome the distance = distToTarget - attackerWeapRange + //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) + //then start attacking + float speed1 = cls.getSpeed(actor); + float speed2 = mTarget.getClass().getSpeed(mTarget); + if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 + && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) + speed2 = 0; + + float s1 = distBetween - weapRange; + float t = s1/speed1; + float s2 = speed2 * t; + float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + if (t + s2/speed1 <= t_swing) + { + mReadyToAttack = true; + if(mTimerAttack <= -attackPeriod) + { + mTimerAttack = 0.3f*static_cast(rand())/RAND_MAX; + mStrike = true; + } + } + } + } + + actor.getClass().getMovementSettings(actor) = mMovement; + + return false; + } + + void AiCombat::buildNewPath(const MWWorld::Ptr& actor) + { + //Construct path to target + ESM::Pathgrid::Point dest; + dest.mX = mTarget.getRefData().getPosition().pos[0]; + dest.mY = mTarget.getRefData().getPosition().pos[1]; + dest.mZ = mTarget.getRefData().getPosition().pos[2]; + Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ); + + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); + float dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + + float targetPosThreshold; + bool isOutside = actor.getCell()->mCell->isExterior(); + if (isOutside) + targetPosThreshold = 300; + else + targetPosThreshold = 100; + + if(dist > targetPosThreshold) + { + //construct new path only if target has moved away more than on + ESM::Position pos = actor.getRefData().getPosition(); + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + if(!mPathFinder.isPathConstructed()) + mPathFinder.buildPath(start, dest, actor.getCell(), isOutside); + else + { + PathFinder newPathFinder; + newPathFinder.buildPath(start, dest, actor.getCell(), isOutside); + + //TO EXPLORE: + //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, + //not the actual path length. Here we should know if the new path is actually more effective. + //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) + newPathFinder.syncStart(mPathFinder.getPath()); + mPathFinder = newPathFinder; + } + } + } + + int AiCombat::getTypeId() const + { + return TypeIdCombat; + } + + unsigned int AiCombat::getPriority() const + { + return 1; + } + + const std::string &AiCombat::getTargetId() const + { + return mTarget.getRefData().getHandle(); + } + + + AiCombat *MWMechanics::AiCombat::clone() const + { + return new AiCombat(*this); + } +} + + +namespace +{ + +void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) +{ + if (weapon == NULL) + { + //hand-to-hand deal equal damage for each type + float roll = static_cast(rand())/RAND_MAX; + if(roll <= 0.333f) //side punch + { + movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; + movement.mPosition[1] = 0; + } + else if(roll <= 0.666f) //forward punch + movement.mPosition[1] = 1; + else + { + movement.mPosition[1] = movement.mPosition[0] = 0; + } + + return; + } + + //the more damage attackType deals the more probability it has + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + + float total = slash + chop + thrust; + + float roll = static_cast(rand())/RAND_MAX; + if(roll <= static_cast(slash)/total) + { + movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; + movement.mPosition[1] = 0; + } + else if(roll <= (static_cast(slash) + static_cast(thrust))/total) + movement.mPosition[1] = 1; + else + movement.mPosition[1] = movement.mPosition[0] = 0; +} + +} diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp new file mode 100644 index 000000000..767a36292 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -0,0 +1,55 @@ +#ifndef GAME_MWMECHANICS_AICOMBAT_H +#define GAME_MWMECHANICS_AICOMBAT_H + +#include "aipackage.hpp" + +#include "pathfinding.hpp" + +#include "movement.hpp" + +#include "../mwbase/world.hpp" + +namespace MWMechanics +{ + class AiCombat : public AiPackage + { + public: + AiCombat(const MWWorld::Ptr& actor); + + virtual AiCombat *clone() const; + + virtual bool execute (const MWWorld::Ptr& actor,float duration); + ///< \return Package completed? + + virtual int getTypeId() const; + + virtual unsigned int getPriority() const; + + const std::string &getTargetId() const; + + private: + PathFinder mPathFinder; + // controls duration of the actual strike + float mTimerAttack; + float mTimerReact; + // controls duration of the sideway & forward moves + // when mCombatMove is true + float mTimerCombatMove; + + // the z rotation angle (degrees) we want to reach + // used every frame when mRotate is true + float mTargetAngle; + + bool mReadyToAttack, mStrike; + bool mFollowTarget; + bool mCombatMove; + bool mRotate; + + MWMechanics::Movement mMovement; + MWWorld::Ptr mTarget; + + void buildNewPath(const MWWorld::Ptr& actor); + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 556e0b126..bac258425 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -3,12 +3,13 @@ #include "movement.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/timestamp.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "steering.hpp" + namespace { float sgn(float a) @@ -29,12 +30,12 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) - , cellX(std::numeric_limits::max()) - , cellY(std::numeric_limits::max()) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) { mMaxDist = 470; - // The CS Help File states that if a duration is givin, the AI package will run for that long + // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) mDuration = 0; @@ -48,12 +49,12 @@ namespace 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) - , cellX(std::numeric_limits::max()) - , cellY(std::numeric_limits::max()) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) { mMaxDist = 470; - // The CS Help File states that if a duration is givin, the AI package will run for that long + // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) mDuration = 0; @@ -71,7 +72,7 @@ namespace MWMechanics return new AiEscort(*this); } - bool AiEscort::execute (const MWWorld::Ptr& actor) + bool AiEscort::execute (const MWWorld::Ptr& actor,float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. @@ -83,49 +84,38 @@ namespace MWMechanics return true; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ESM::Position pos = actor.getRefData().getPosition(); - bool cellChange = actor.getCell()->mCell->mData.mX != cellX || actor.getCell()->mCell->mData.mY != cellY; - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY; if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) { int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); - // Check if actor is near the border of an inactive cell. If so, disable AiEscort. - // FIXME: This *should* pause the AiEscort package instead of terminating it. + // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / 2.0 - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; + return false; } } if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) { int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); - // Check if actor is near the border of an inactive cell. If so, disable AiEscort. - // FIXME: This *should* pause the AiEscort package instead of terminating it. + // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / 2.0 - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; + return false; } } if(!mPathFinder.isPathConstructed() || cellChange) { - cellX = actor.getCell()->mCell->mData.mX; - cellY = actor.getCell()->mCell->mData.mY; - float xCell = 0; - float yCell = 0; - if (actor.getCell()->mCell->isExterior()) - { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; - } + mCellX = actor.getCell()->mCell->mData.mX; + mCellY = actor.getCell()->mCell->mData.mY; ESM::Pathgrid::Point dest; dest.mX = mX; @@ -137,7 +127,7 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + mPathFinder.buildPath(start, dest, actor.getCell(), true); } if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) @@ -161,7 +151,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + zTurn(actor, Ogre::Degree(zAngle)); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; mMaxDist = 470; } @@ -178,7 +168,7 @@ namespace MWMechanics int AiEscort::getTypeId() const { - return 2; + return TypeIdEscort; } } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3ae604035..53b57c058 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -18,7 +18,7 @@ namespace MWMechanics virtual AiEscort *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; @@ -34,8 +34,8 @@ namespace MWMechanics unsigned int mDuration; PathFinder mPathFinder; - int cellX; - int cellY; + int mCellX; + int mCellY; }; } #endif diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index dab9e0283..cf5291fd3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,27 +1,118 @@ -#include "aifollow.hpp" +#include "aifollow.hpp" #include - +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "movement.hpp" + +#include + +#include "steering.hpp" + MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: 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 +: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +{ +} +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), mTimer(0), mStuckTimer(0) { - return new AiFollow(*this); } - bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor) +bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - std::cout << "AiFollow completed.\n"; - return true; + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + + mTimer = mTimer + duration; + mStuckTimer = mStuckTimer + duration; + mTotalTime = mTotalTime + duration; + + ESM::Position pos = actor.getRefData().getPosition(); + + if(mTotalTime > mDuration && mDuration != 0) + return true; + + if((pos.pos[0]-mX)*(pos.pos[0]-mX) + + (pos.pos[1]-mY)*(pos.pos[1]-mY) + + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) + { + if(actor.getCell()->isExterior()) + { + if(mCellId == "") + return true; + } + else + { + if(mCellId == actor.getCell()->mCell->mName) + return true; + } + } + + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + if(mPathFinder.getPath().empty()) + mPathFinder.buildPath(start, dest, actor.getCell(), true); + + + if(mTimer > 0.25) + { + if(!mPathFinder.getPath().empty()) + { + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); + + if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) + +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) + +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) + > 100*100) + mPathFinder.addPointToPath(dest); + } + + mTimer = 0; + } + + if(mStuckTimer>0.5) + { + if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) + +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) + +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck + mPathFinder.buildPath(start, dest, actor.getCell(), true); + + mStuckTimer = 0; + mStuckPos = pos; + } + + if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) + { + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + } + + if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) + < 100*100) + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + else + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + + return false; +} + +std::string MWMechanics::AiFollow::getFollowedActor() +{ + return mActorId; +} + +MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const +{ + return new AiFollow(*this); } int MWMechanics::AiFollow::getTypeId() const { - return 3; + return TypeIdFollow; } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 0b37b0a2d..9d77b903d 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -3,6 +3,8 @@ #include "aipackage.hpp" #include +#include "pathfinding.hpp" +#include "../../../components/esm/defs.hpp" namespace MWMechanics { @@ -13,10 +15,12 @@ namespace MWMechanics 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); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; + std::string getFollowedActor(); + private: float mDuration; float mX; @@ -24,6 +28,14 @@ namespace MWMechanics float mZ; std::string mActorId; std::string mCellId; + + float mTimer; + float mStuckTimer; + float mTotalTime; + + ESM::Position mStuckPos; + + PathFinder mPathFinder; }; } #endif diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index d286fbba8..74c77bf97 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -12,16 +12,28 @@ namespace MWMechanics class AiPackage { public: - + enum TypeId { + TypeIdNone = -1, + TypeIdWander = 0, + TypeIdTravel = 1, + TypeIdEscort = 2, + TypeIdFollow = 3, + TypeIdActivate = 4, + TypeIdCombat = 5 + }; + virtual ~AiPackage(); virtual AiPackage *clone() const = 0; - virtual bool execute (const MWWorld::Ptr& actor) = 0; + virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0; ///< \return Package completed? virtual int getTypeId() const = 0; - ///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate + ///< @see enum TypeId + + virtual unsigned int getPriority() const {return 0;} + ///< higher number is higher priority (0 beeing the lowest) }; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2f06b849a..2110393fd 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -8,6 +8,13 @@ #include "aitravel.hpp" #include "aifollow.hpp" #include "aiactivate.hpp" +#include "aicombat.hpp" + +#include "../mwworld/class.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" void MWMechanics::AiSequence::copy (const AiSequence& sequence) { @@ -16,7 +23,7 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) mPackages.push_back ((*iter)->clone()); } -MWMechanics::AiSequence::AiSequence() : mDone (false) {} +MWMechanics::AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {} MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { @@ -29,6 +36,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s { clear(); copy (sequence); + mDone = sequence.mDone; } return *this; @@ -47,22 +55,47 @@ int MWMechanics::AiSequence::getTypeId() const return mPackages.front()->getTypeId(); } +bool MWMechanics::AiSequence::getCombatTarget(std::string &targetActorId) const +{ + if (getTypeId() != AiPackage::TypeIdCombat) + return false; + const AiCombat *combat = static_cast(mPackages.front()); + targetActorId = combat->getTargetId(); + return true; +} + +void MWMechanics::AiSequence::stopCombat() +{ + while (getTypeId() == AiPackage::TypeIdCombat) + { + delete *mPackages.begin(); + mPackages.erase (mPackages.begin()); + } +} + bool MWMechanics::AiSequence::isPackageDone() const { return mDone; } -void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) +void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) { - if (!mPackages.empty()) + if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) { - if (mPackages.front()->execute (actor)) + if (!mPackages.empty()) { - mPackages.erase (mPackages.begin()); - mDone = true; + mLastAiPackage = mPackages.front()->getTypeId(); + if (mPackages.front()->execute (actor,duration)) + { + delete *mPackages.begin(); + mPackages.erase (mPackages.begin()); + mDone = true; + } + else + { + mDone = false; + } } - else - mDone = false; } } @@ -76,7 +109,17 @@ void MWMechanics::AiSequence::clear() void MWMechanics::AiSequence::stack (const AiPackage& package) { - mPackages.push_front (package.clone()); + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); it++) + { + if(mPackages.front()->getPriority() <= package.getPriority()) + { + mPackages.insert(it,package.clone()); + return; + } + } + + if(mPackages.empty()) + mPackages.push_front (package.clone()); } void MWMechanics::AiSequence::queue (const AiPackage& package) @@ -84,6 +127,14 @@ void MWMechanics::AiSequence::queue (const AiPackage& package) mPackages.push_back (package.clone()); } +MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage() +{ + if(mPackages.empty()) + throw std::runtime_error(std::string("No AI Package!")); + else + return mPackages.front(); +} + void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) { for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) @@ -95,7 +146,7 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) std::vector idles; for (int i=0; i<8; ++i) idles.push_back(data.mIdle[i]); - package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mUnk); + package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat); } else if (it->mType == ESM::AI_Escort) { diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 9f70daeb8..62f48f981 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -18,10 +18,14 @@ namespace MWMechanics class AiSequence { std::list mPackages; + bool mDone; void copy (const AiSequence& sequence); + // The type of AI package that ran last + int mLastAiPackage; + public: AiSequence(); @@ -33,17 +37,28 @@ namespace MWMechanics virtual ~AiSequence(); int getTypeId() const; - ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate + ///< @see enum AiPackage::TypeId + + int getLastRunTypeId() const { return mLastAiPackage; } + ///< Get the typeid of the Ai package that ran last, NOT the currently "active" Ai package that will be run in the next frame. + /// This difference is important when an Ai package has just finished and been removed. + + bool getCombatTarget (std::string &targetActorId) const; + ///< Return true and assign target if combat package is currently + /// active, return false otherwise + + void stopCombat(); + ///< Removes all combat packages until first non-combat or stack empty. bool isPackageDone() const; ///< Has a package been completed during the last update? - void execute (const MWWorld::Ptr& actor); + void execute (const MWWorld::Ptr& actor,float duration); ///< Execute package. void clear(); ///< Remove all packages. - + void stack (const AiPackage& package); ///< Add \a package to the front of the sequence (suspends current package) @@ -51,6 +66,9 @@ namespace MWMechanics ///< Add \a package to the end of the sequence (executed after all other packages have been /// completed) + AiPackage* getActivePackage(); + ///< return the current active package. If there is no active package, throw an exeption + void fill (const ESM::AIPackageList& list); }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index d47a49c70..8a0b2ebd0 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,11 +1,11 @@ #include "aitravel.hpp" -#include "movement.hpp" - #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" + +#include "steering.hpp" +#include "movement.hpp" namespace { @@ -31,14 +31,14 @@ namespace MWMechanics return new AiTravel(*this); } - bool AiTravel::execute (const MWWorld::Ptr& actor) + bool AiTravel::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->mCell; - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->mCell->mData.mX) { int sideX = sgn(cell->mData.mX - player.getCell()->mCell->mData.mX); @@ -62,20 +62,11 @@ namespace MWMechanics } } - const ESM::Pathgrid *pathgrid = world->getStore().get().search(*cell); bool cellChange = cell->mData.mX != cellX || cell->mData.mY != cellY; if(!mPathFinder.isPathConstructed() || cellChange) { cellX = cell->mData.mX; cellY = cell->mData.mY; - float xCell = 0; - float yCell = 0; - - if(cell->isExterior()) - { - xCell = cell->mData.mX * ESM::Land::REAL_SIZE; - yCell = cell->mData.mY * ESM::Land::REAL_SIZE; - } ESM::Pathgrid::Point dest; dest.mX = mX; @@ -87,7 +78,7 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + mPathFinder.buildPath(start, dest, actor.getCell(), true); } if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) @@ -96,8 +87,7 @@ namespace MWMechanics return true; } - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - world->rotateObject(actor, 0, 0, zAngle, false); + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); movement.mPosition[1] = 1; return false; @@ -105,7 +95,7 @@ namespace MWMechanics int AiTravel::getTypeId() const { - return 1; + return TypeIdTravel; } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 6eb9af8ce..b479dfd43 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -13,7 +13,7 @@ namespace MWMechanics AiTravel(float x, float y, float z); virtual AiTravel *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 96a41883b..77316fedf 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -3,13 +3,16 @@ #include "movement.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "creaturestats.hpp" #include +#include "steering.hpp" + namespace { float sgn(float a) @@ -31,6 +34,7 @@ namespace MWMechanics , mX(0) , mY(0) , mZ(0) + , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) { @@ -63,8 +67,10 @@ namespace MWMechanics return new AiWander(*this); } - bool AiWander::execute (const MWWorld::Ptr& actor) + bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { @@ -144,12 +150,12 @@ namespace MWMechanics mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); } - - if(mAllowedNodes.empty()) - mDistance = 0; } } + if(mAllowedNodes.empty()) + mDistance = 0; + // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) mDistance = 0; @@ -183,11 +189,57 @@ namespace MWMechanics playIdle(actor, mPlayedIdle); mChooseAction = false; mIdleNow = true; + + // Play idle voiced dialogue entries randomly + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + float chance = store.get().find("fVoiceIdleOdds")->getFloat(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // Don't bother if the player is out of hearing range + if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } } } if(mIdleNow) { + // Play a random voice greeting if the player gets too close + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + float helloDistance = hello; + int iGreetDistanceMultiplier = store.get().find("iGreetDistanceMultiplier")->getInt(); + helloDistance *= iGreetDistanceMultiplier; + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance( + Ogre::Vector3(actor.getRefData().getPosition().pos)); + + if (!mSaidGreeting) + { + // TODO: check if actor is aware / has line of sight + if (playerDist <= helloDistance + // Only play a greeting if the player is not moving + && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0) + { + mSaidGreeting = true; + MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + // TODO: turn to face player and interrupt the idle animation? + } + } + else + { + float fGreetDistanceReset = store.get().find("fGreetDistanceReset")->getFloat(); + if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier) + mSaidGreeting = false; + } + + // Check if idle animation finished if(!checkIdle(actor, mPlayedIdle)) { mPlayedIdle = 0; @@ -200,6 +252,7 @@ namespace MWMechanics { if(!mPathFinder.isPathConstructed()) { + assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); @@ -213,7 +266,7 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - mPathFinder.buildPath(start, dest, mPathgrid, mXCell, mYCell, false); + mPathFinder.buildPath(start, dest, actor.getCell(), false); if(mPathFinder.isPathConstructed()) { @@ -234,10 +287,6 @@ namespace MWMechanics if(mWalking) { - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - world->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { stopWalking(actor); @@ -245,6 +294,12 @@ namespace MWMechanics mWalking = false; mChooseAction = true; } + else + { + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + } } return false; @@ -252,7 +307,7 @@ namespace MWMechanics int AiWander::getTypeId() const { - return 0; + return TypeIdWander; } void AiWander::stopWalking(const MWWorld::Ptr& actor) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index c82ccc215..9a44aa065 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -16,7 +16,7 @@ namespace MWMechanics AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); virtual AiPackage *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; ///< 0: Wander @@ -32,6 +32,8 @@ namespace MWMechanics std::vector mIdle; bool mRepeat; + bool mSaidGreeting; + float mX; float mY; float mZ; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 1d992be41..af58e9ee0 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -30,13 +30,13 @@ std::set MWMechanics::Alchemy::listEffects() const { std::map effects; - + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) { if (!iter->isEmpty()) { const MWWorld::LiveCellRef *ingredient = iter->get(); - + for (int i=0; i<4; ++i) if (ingredient->mBase->mData.mEffectID[i]!=-1) { @@ -48,13 +48,13 @@ std::set MWMechanics::Alchemy::listEffects() const } } } - + std::set effects2; - + for (std::map::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) if (iter->second>1) effects2.insert (iter->first); - + return effects2; } @@ -62,12 +62,12 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const { bool magnitude = !(flags & ESM::MagicEffect::NoMagnitude); bool duration = !(flags & ESM::MagicEffect::NoDuration); - bool negative = flags & (ESM::MagicEffect::Negative | ESM::MagicEffect::Harmful); + bool negative = flags & (ESM::MagicEffect::Harmful); int tool = negative ? ESM::Apparatus::Retort : ESM::Apparatus::Albemic; int setup = 0; - + if (!mTools[tool].isEmpty() && !mTools[ESM::Apparatus::Calcinator].isEmpty()) setup = 1; else if (!mTools[tool].isEmpty()) @@ -82,23 +82,23 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const mTools[ESM::Apparatus::Calcinator].get()->mBase->mData.mQuality : 0; float quality = 1; - + switch (setup) { case 1: - + quality = negative ? 2 * toolQuality + 3 * calcinatorQuality : (magnitude && duration ? 2 * toolQuality + calcinatorQuality : 2/3.0 * (toolQuality + calcinatorQuality) + 0.5); break; - + case 2: - + quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5); break; - + case 3: - + quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5; break; } @@ -110,8 +110,8 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const else { if (quality==0) - throw std::runtime_error ("invalid derived alchemy apparatus quality"); - + throw std::runtime_error ("invalid derived alchemy apparatus quality"); + value /= quality; } } @@ -141,21 +141,21 @@ void MWMechanics::Alchemy::updateEffects() for (std::set::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->mId); - + MWBase::Environment::get().getWorld()->getStore().get().find (iter->mId); + if (magicEffect->mData.mBaseCost<=0) { std::ostringstream os; os << "invalid base cost for magic effect " << iter->mId; throw std::runtime_error (os.str()); } - + float fPotionT1MagMul = MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1MagMult")->getFloat(); if (fPotionT1MagMul<=0) throw std::runtime_error ("invalid gmst: fPotionT1MagMul"); - + float fPotionT1DurMult = MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1DurMult")->getFloat(); @@ -172,25 +172,25 @@ void MWMechanics::Alchemy::updateEffects() if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) applyTools (magicEffect->mData.mFlags, duration); - - duration = static_cast (duration+0.5); + + duration = static_cast (duration+0.5); magnitude = static_cast (magnitude+0.5); if (magnitude>0 && duration>0) { ESM::ENAMstruct effect; effect.mEffectID = iter->mId; - + effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work - + effect.mRange = 0; - effect.mArea = 0; - + effect.mArea = 0; + effect.mDuration = duration; effect.mMagnMin = effect.mMagnMax = magnitude; mEffects.push_back (effect); - } + } } } @@ -204,14 +204,14 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const { if (iter->mEffects.mList.size() != mEffects.size()) continue; - - bool mismatch = false; + + bool mismatch = false; for (int i=0; i (iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; - + if (first.mEffectID!=second.mEffectID || first.mArea!=second.mArea || first.mRange!=second.mRange || @@ -225,29 +225,30 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const break; } } - + if (!mismatch) return &(*iter); } - + return 0; } void MWMechanics::Alchemy::removeIngredients() { bool needsUpdate = false; - + for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty()) { - iter->getRefData().setCount (iter->getRefData().getCount()-1); + iter->getContainerStore()->remove(*iter, 1, mAlchemist); + if (iter->getRefData().getCount()<1) { needsUpdate = true; *iter = MWWorld::Ptr(); } } - + if (needsUpdate) updateEffects(); } @@ -255,39 +256,38 @@ void MWMechanics::Alchemy::removeIngredients() void MWMechanics::Alchemy::addPotion (const std::string& name) { const ESM::Potion *record = getRecord(); - + if (!record) { ESM::Potion newRecord; - + newRecord.mData.mWeight = 0; - + for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) if (!iter->isEmpty()) newRecord.mData.mWeight += iter->get()->mBase->mData.mWeight; - + newRecord.mData.mWeight /= countIngredients(); - + newRecord.mData.mValue = mValue; newRecord.mData.mAutoCalc = 0; - + newRecord.mName = name; - int index = static_cast (std::rand()/static_cast (RAND_MAX)*6); + int index = static_cast (std::rand()/(static_cast (RAND_MAX)+1)*6); assert (index>=0 && index<6); - + static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; - + newRecord.mModel = "m\\misc_potion_" + std::string (name[index]) + "_01.nif"; newRecord.mIcon = "m\\tx_potion_" + std::string (name[index]) + "_01.dds"; - + newRecord.mEffects.mList = mEffects; - + record = MWBase::Environment::get().getWorld()->createRecord (newRecord); } - - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId); - MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr(), mAlchemist); + + mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist); } void MWMechanics::Alchemy::increaseSkill() @@ -299,7 +299,7 @@ float MWMechanics::Alchemy::getChance() const { const CreatureStats& creatureStats = MWWorld::Class::get (mAlchemist).getCreatureStats (mAlchemist); const NpcStats& npcStats = MWWorld::Class::get (mAlchemist).getNpcStats (mAlchemist); - + return (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + 0.1 * creatureStats.getAttribute (1).getModified() @@ -320,34 +320,34 @@ int MWMechanics::Alchemy::countIngredients() const void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) { mAlchemist = npc; - + mIngredients.resize (4); std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr()); - + mTools.resize (4); - + std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr()); - + mEffects.clear(); - + MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc); - + for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus)); iter!=store.end(); ++iter) - { + { MWWorld::LiveCellRef* ref = iter->get(); - + int type = ref->mBase->mData.mType; - + if (type<0 || type>=static_cast (mTools.size())) throw std::runtime_error ("invalid apparatus type"); - + if (!mTools[type].isEmpty()) if (ref->mBase->mData.mQuality<=mTools[type].get()->mBase->mData.mQuality) continue; - - mTools[type] = *iter; + + mTools[type] = *iter; } } @@ -389,19 +389,19 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) { slot = i; break; - } - + } + if (slot==-1) return -1; - + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty() && ingredient.get()==iter->get()) return -1; - + mIngredients[slot] = ingredient; - + updateEffects(); - + return slot; } @@ -428,7 +428,7 @@ std::string MWMechanics::Alchemy::getPotionName() const { if (const ESM::Potion *potion = getRecord()) return potion->mName; - + return ""; } @@ -436,13 +436,13 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na { if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) return Result_NoMortarAndPestle; - + if (countIngredients()<2) return Result_LessThanTwoIngredients; if (name.empty() && getPotionName().empty()) return Result_NoName; - + if (beginEffects()==endEffects()) return Result_NoEffects; @@ -455,7 +455,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na addPotion (name); removeIngredients(); - + increaseSkill(); return Result_Success; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c4260d907..c2a26ced3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -32,25 +32,25 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/statemanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" namespace { -int getBestAttack (const ESM::Weapon* weapon) +std::string getBestAttack (const ESM::Weapon* weapon) { int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; if (slash >= chop && slash >= thrust) - return MWMechanics::CreatureStats::AT_Slash; + return "slash"; else if (chop >= slash && chop >= thrust) - return MWMechanics::CreatureStats::AT_Chop; + return "chop"; else - return MWMechanics::CreatureStats::AT_Thrust; + return "thrust"; } } @@ -63,16 +63,6 @@ struct StateInfo { const char groupname[32]; }; -static const StateInfo sDeathList[] = { - { CharState_Death1, "death1" }, - { CharState_Death2, "death2" }, - { CharState_Death3, "death3" }, - { CharState_Death4, "death4" }, - { CharState_Death5, "death5" }, - { CharState_SwimDeath, "swimdeath" }, -}; -static const StateInfo *sDeathListEnd = &sDeathList[sizeof(sDeathList)/sizeof(sDeathList[0])]; - static const StateInfo sMovementList[] = { { CharState_WalkForward, "walkforward" }, { CharState_WalkBack, "walkback" }, @@ -129,7 +119,7 @@ static const struct WeaponInfo { { WeapType_TwoWide, "2w", "weapontwowide" }, { WeapType_BowAndArrow, "1h", "bowandarrow" }, { WeapType_Crossbow, "crossbow", "crossbow" }, - { WeapType_ThowWeapon, "1h", "throwweapon" }, + { WeapType_Thrown, "1h", "throwweapon" }, { WeapType_PickProbe, "1h", "pickprobe" }, { WeapType_Spell, "spell", "spellcast" }, }; @@ -145,9 +135,73 @@ public: { return weap.type == type; } }; +std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) +{ + int numAnims=0; + while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1))) + ++numAnims; + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * numAnims + 1; // [1, numAnims] + if (num) + *num = roll; + return prefix + Ogre::StringConverter::toString(roll); +} void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force) { + // hit recoils/knockdown animations handling + if(mPtr.getClass().isActor()) + { + bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); + bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); + bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); + if(mHitState == CharState_None) + { + if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0) + { + mHitState = CharState_KnockOut; + mCurrentHit = "knockout"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul); + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); + } + else if(knockdown) + { + mHitState = CharState_KnockDown; + mCurrentHit = "knockdown"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); + } + else if (recovery) + { + mHitState = CharState_Hit; + mCurrentHit = chooseRandomGroup("hit"); + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); + } + else if (block) + { + mHitState = CharState_Block; + mCurrentHit = "shield"; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); + } + } + else if(!mAnimation->isPlaying(mCurrentHit)) + { + mCurrentHit.erase(); + if (knockdown) + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); + if (recovery) + mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); + if (block) + mPtr.getClass().getCreatureStats(mPtr).setBlock(false); + mHitState = CharState_None; + } + else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + { + mHitState = CharState_KnockDown; + mAnimation->disable(mCurrentHit); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0); + } + } + const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); if(force || idle != mIdleState) @@ -203,14 +257,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mAnimation->disable(mCurrentJump); mCurrentJump = jump; - mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, + if (mAnimation->hasAnimation("jump")) + mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, 1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } else { mAnimation->disable(mCurrentJump); mCurrentJump.clear(); - mAnimation->play(jump, Priority_Jump, jumpgroup, true, + if (mAnimation->hasAnimation("jump")) + mAnimation->play(jump, Priority_Jump, jumpgroup, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -259,16 +315,29 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if(!mCurrentMovement.empty()) { float vel, speedmult = 1.0f; + + bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) speedmult = mMovementSpeed / vel; + else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed + else if (mMovementSpeed > 0.0f) + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + + mMovementAnimVelocity = vel; } + else mMovementAnimVelocity = 0.0f; } } -void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group) +void getWeaponGroup(WeaponType weaptype, std::string &group) { const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); if(info != sWeaponTypeListEnd) @@ -276,7 +345,7 @@ void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group } -MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) +MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) { if(stats.getDrawState() == DrawState_Spell) { @@ -324,7 +393,7 @@ MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &s *weaptype = WeapType_Crossbow; break; case ESM::Weapon::MarksmanThrown: - *weaptype = WeapType_ThowWeapon; + *weaptype = WeapType_Thrown; break; } } @@ -336,6 +405,23 @@ MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &s return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); } +void CharacterController::playRandomDeath(float startpoint) +{ + if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) + { + mDeathState = CharState_SwimDeath; + mCurrentDeath = "swimdeath"; + } + else + { + int selected=0; + mCurrentDeath = chooseRandomGroup("death", &selected); + mDeathState = static_cast(CharState_Death1 + (selected-1)); + } + + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + false, 1.0f, "start", "stop", startpoint, 0); +} CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) @@ -343,7 +429,9 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mIdleState(CharState_None) , mMovementState(CharState_None) , mMovementSpeed(0.0f) + , mMovementAnimVelocity(0.0f) , mDeathState(CharState_None) + , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) , mWeaponType(WeapType_None) @@ -361,14 +449,16 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim * handle knockout and death which moves the character down. */ mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f)); - if(mPtr.getTypeName() == typeid(ESM::NPC).name()) + if (cls.hasInventoryStore(mPtr)) { - getActiveWeapon(cls.getNpcStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); + getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); if(mWeaponType != WeapType_None) { getWeaponGroup(mWeaponType, mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; + mAnimation->showWeapons(true); } + mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand); } if(!cls.getCreatureStats(mPtr).isDead()) @@ -388,15 +478,10 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } refreshCurrentAnims(mIdleState, mMovementState, true); + if(mDeathState != CharState_None) { - const StateInfo *state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState)); - if(state == sDeathListEnd) - throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); - - mCurrentDeath = state->groupname; - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, - false, 1.0f, "start", "stop", 1.0f, 0); + playRandomDeath(1.0f); } } @@ -410,21 +495,59 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) mPtr = ptr; } +bool CharacterController::updateCreatureState() +{ + const MWWorld::Class &cls = mPtr.getClass(); + CreatureStats &stats = cls.getCreatureStats(mPtr); -bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak) + if(stats.getAttackingOrSpell()) + { + if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) + { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + + // These are unique animations and not linked to movement type. Just pick one randomly. + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 3; // [0, 2] + if (roll == 0) + mCurrentWeapon = "attack1"; + else if (roll == 1) + mCurrentWeapon = "attack2"; + else + mCurrentWeapon = "attack3"; + + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_All, true, + 1, "start", "stop", + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + } + } + + bool animPlaying = mAnimation->getInfo(mCurrentWeapon); + if (!animPlaying) + mUpperBodyState = UpperCharState_Nothing; + return false; +} + +bool CharacterController::updateWeaponState() { const MWWorld::Class &cls = MWWorld::Class::get(mPtr); - NpcStats &stats = cls.getNpcStats(mPtr); + CreatureStats &stats = cls.getCreatureStats(mPtr); WeaponType weaptype = WeapType_None; MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); - const bool isWerewolf = stats.isWerewolf(); + const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); bool forcestateupdate = false; - if(weaptype != mWeaponType) + if(weaptype != mWeaponType && mHitState != CharState_KnockDown) { forcestateupdate = true; + // Shields/torches shouldn't be visible during spellcasting or hand-to-hand + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + mAnimation->showCarriedLeft(weaptype != WeapType_Spell && weaptype != WeapType_HandToHand); + std::string weapgroup; if(weaptype == WeapType_None) { @@ -438,10 +561,13 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun { getWeaponGroup(weaptype, weapgroup); mAnimation->showWeapons(false); + mAnimation->setWeaponGroup(weapgroup); + mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; + if(isWerewolf) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -473,7 +599,9 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun if(isWerewolf) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(isrunning && !inwater && mWeaponType == WeapType_None) + if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) + && mWeaponType == WeapType_None) { if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, @@ -488,19 +616,46 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun if(isWeapon) weapSpeed = weapon->get()->mBase->mData.mSpeed; + // Cancel attack if we no longer have ammunition + bool ammunition = true; + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (mWeaponType == WeapType_Crossbow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); + else if (mWeaponType == WeapType_BowAndArrow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); + if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + float complete; bool animPlaying; if(stats.getAttackingOrSpell()) { - if(mUpperBodyState == UpperCharState_WeapEquiped) + if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None) { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackType.clear(); if(mWeaponType == WeapType_Spell) { + // Unset casting flag, otherwise pressing the mouse button down would + // continue casting every frame if there is no animation + stats.setAttackingOrSpell(false); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const std::string spellid = stats.getSpells().getSelectedSpell(); - if(!spellid.empty()) + // For the player, set the spell we want to cast + // This has to be done at the start of the casting animation, + // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) + if (mPtr.getRefData().getHandle() == "player") + { + std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); + stats.getSpells().setSelectedSpell(selectedSpell); + } + std::string spellid = stats.getSpells().getSelectedSpell(); + + if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" @@ -512,6 +667,20 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun const ESM::MagicEffect *effect; effect = store.get().find(effectentry.mEffectID); + const ESM::Static* castStatic; + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); + switch(effectentry.mRange) { case 0: mAttackType = "self"; break; @@ -531,6 +700,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun else sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } + if (inv.getSelectedEnchantItem() != inv.end()) + { + // Enchanted items cast immediately (no animation) + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } } else if(mWeaponType == WeapType_PickProbe) { @@ -555,44 +729,38 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun if(!resultSound.empty()) MWBase::Environment::get().getSoundManager()->playSound(resultSound, 1.0f, 1.0f); - // tool used up? - if(!item.getRefData().getCount()) - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - else + // Set again, just to update the charge bar + if(item.getRefData().getCount()) MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item); } - else + else if (ammunition) { if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_ThowWeapon) + mWeaponType == WeapType_Thrown) mAttackType = "shoot"; else { - int attackType = stats.getAttackType(); - if(isWeapon && Settings::Manager::getBool("best attack", "Game")) - attackType = getBestAttack(weapon->get()->mBase); - - if (attackType == MWMechanics::CreatureStats::AT_Chop) - mAttackType = "chop"; - else if (attackType == MWMechanics::CreatureStats::AT_Slash) - mAttackType = "slash"; + if(isWeapon && mPtr.getRefData().getHandle() == "player" && + Settings::Manager::getBool("best attack", "Game")) + mAttackType = getBestAttack(weapon->get()->mBase); else - mAttackType = "thrust"; + determineAttackType(); } mAnimation->play(mCurrentWeapon, Priority_Weapon, MWRender::Animation::Group_UpperBody, false, weapSpeed, mAttackType+" start", mAttackType+" min attack", 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; + mUpperBodyState = UpperCharState_StartToMinAttack; } } + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); } else { animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack) + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) { if(mAttackType != "shoot") { @@ -625,6 +793,55 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun 1.0f-complete, 0); mUpperBodyState = UpperCharState_MaxAttackToMinHit; } + else if (mHitState == CharState_KnockDown) + { + mUpperBodyState = UpperCharState_WeapEquiped; + mAnimation->disable(mCurrentWeapon); + } + } + + mAnimation->setPitchFactor(0.f); + if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + { + switch (mUpperBodyState) + { + case UpperCharState_StartToMinAttack: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + mAnimation->setPitchFactor(1.f); + break; + case UpperCharState_FollowStartToFollowStop: + if (animPlaying) + mAnimation->setPitchFactor(1.f-complete); + break; + default: + break; + } + } + else if (mWeaponType == WeapType_Crossbow) + { + switch (mUpperBodyState) + { + case UpperCharState_EquipingWeap: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_UnEquipingWeap: + mAnimation->setPitchFactor(1.f-complete); + break; + case UpperCharState_WeapEquiped: + case UpperCharState_StartToMinAttack: + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + case UpperCharState_FollowStartToFollowStop: + mAnimation->setPitchFactor(1.f); + break; + default: + break; + } } if(!animPlaying) @@ -632,72 +849,119 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun if(mUpperBodyState == UpperCharState_EquipingWeap || mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) + { + if (ammunition && mWeaponType == WeapType_Crossbow) + mAnimation->attachArrow(); + mUpperBodyState = UpperCharState_WeapEquiped; + //don't allow to continue playing hit animation on UpperBody after actor had attacked during it + if(mHitState == CharState_Hit) + { + mAnimation->changeGroups(mCurrentHit, MWRender::Animation::Group_LowerBody); + //commenting out following 2 lines will give a bit different combat dynamics(slower) + mHitState = CharState_None; + mCurrentHit.clear(); + mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); + } + } else if(mUpperBodyState == UpperCharState_UnEquipingWeap) mUpperBodyState = UpperCharState_Nothing; } else if(complete >= 1.0f) { - if(mUpperBodyState == UpperCharState_StartToMinAttack) + std::string start, stop; + switch(mUpperBodyState) { - mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, - weapSpeed, mAttackType+" min attack", mAttackType+" max attack", - 0.0f, 0); - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + case UpperCharState_StartToMinAttack: + start = mAttackType+" min attack"; + stop = mAttackType+" max attack"; + mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + break; + case UpperCharState_MinAttackToMaxAttack: + //hack to avoid body pos desync when jumping/sneaking in 'max attack' state + if(!mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, false, + 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); + break; + case UpperCharState_MaxAttackToMinHit: + if(mAttackType == "shoot") + { + start = mAttackType+" min hit"; + stop = mAttackType+" release"; + } + else + { + start = mAttackType+" min hit"; + stop = mAttackType+" hit"; + } + mUpperBodyState = UpperCharState_MinHitToHit; + break; + case UpperCharState_MinHitToHit: + if(mAttackType == "shoot") + { + start = mAttackType+" follow start"; + stop = mAttackType+" follow stop"; + } + else + { + float str = stats.getAttackStrength(); + start = mAttackType+((str < 0.5f) ? " small follow start" + : (str < 1.0f) ? " medium follow start" + : " large follow start"); + stop = mAttackType+((str < 0.5f) ? " small follow stop" + : (str < 1.0f) ? " medium follow stop" + : " large follow stop"); + } + mUpperBodyState = UpperCharState_FollowStartToFollowStop; + break; + default: + break; } - else if(mUpperBodyState == UpperCharState_MaxAttackToMinHit) + + if(!start.empty()) { mAnimation->disable(mCurrentWeapon); - if(mAttackType == "shoot") + if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, - weapSpeed, mAttackType+" min hit", mAttackType+" follow start", - 0.0f, 0); + MWRender::Animation::Group_UpperBody, true, + weapSpeed, start, stop, 0.0f, 0); else mAnimation->play(mCurrentWeapon, Priority_Weapon, MWRender::Animation::Group_UpperBody, false, - weapSpeed, mAttackType+" min hit", mAttackType+" hit", - 0.0f, 0); - mUpperBodyState = UpperCharState_MinHitToHit; + weapSpeed, start, stop, 0.0f, 0); } - else if(mUpperBodyState == UpperCharState_MinHitToHit) + } + + //if playing combat animation and lowerbody is not busy switch to whole body animation + if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying) + { + if( mMovementState != CharState_None || + mJumpState != JumpState_None || + mHitState != CharState_None || + MWBase::Environment::get().getWorld()->isSwimming(mPtr) || + cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak)) { - mAnimation->disable(mCurrentWeapon); - if(mAttackType == "shoot") - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, - weapSpeed, mAttackType+" follow start", mAttackType+" follow stop", - 0.0f, 0); - else - { - float str = stats.getAttackStrength(); - std::string start = mAttackType+((str < 0.5f) ? " small follow start" - : (str < 1.0f) ? " medium follow start" - : " large follow start"); - std::string stop = mAttackType+((str < 0.5f) ? " small follow stop" - : (str < 1.0f) ? " medium follow stop" - : " large follow stop"); - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, - weapSpeed, start, stop, 0.0f, 0); - } - mUpperBodyState = UpperCharState_FollowStartToFollowStop; + mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody); + } + else + { + mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All); } } - MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() + && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + { - if(!mAnimation->isPlaying("torch")) - mAnimation->play("torch", Priority_Torch, - MWRender::Animation::Group_LeftArm, false, - 1.0f, "start", "stop", 0.0f, (~(size_t)0)); + mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, + false, 1.0f, "start", "stop", 0.0f, (~(size_t)0)); } - else if(mAnimation->isPlaying("torch")) + else if (mAnimation->isPlaying("torch")) + { mAnimation->disable("torch"); + } return forcestateupdate; } @@ -708,6 +972,8 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = MWWorld::Class::get(mPtr); Ogre::Vector3 movement(0.0f); + updateVisibility(); + if(!cls.isActor()) { if(mAnimQueue.size() > 1) @@ -727,10 +993,14 @@ void CharacterController::update(float duration) { 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); + bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); + bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool flying = world->isFlying(mPtr); - Ogre::Vector3 vec = cls.getMovementVector(mPtr); + //Ogre::Vector3 vec = cls.getMovementVector(mPtr); + Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); + vec.normalise(); + if(mHitState != CharState_None && mJumpState == JumpState_None) + vec = Ogre::Vector3(0.0f); Ogre::Vector3 rot = cls.getRotationVector(mPtr); mMovementSpeed = cls.getSpeed(mPtr); @@ -743,6 +1013,7 @@ void CharacterController::update(float duration) isrunning = isrunning && std::abs(vec[0])+std::abs(vec[1]) > 0.0f; + // advance athletics if(std::abs(vec[0])+std::abs(vec[1]) > 0.0f && mPtr.getRefData().getHandle() == "player") { @@ -766,28 +1037,57 @@ void CharacterController::update(float duration) } } - if(sneak || inwater || flying) + // reduce fatigue + const MWWorld::Store &gmst = world->getStore().get(); + float fatigueLoss = 0; + static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat(); + static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat(); + static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat(); + static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat(); + static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat(); + static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat(); + static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat(); + static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat(); + + const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); + if (encumbrance < 1) { - vec.z = 0.0f; - mFallHeight = mPtr.getRefData().getPosition().pos[2]; + if (sneak) + fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; + else + { + if (inwater) + { + if (!isrunning) + fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; + else + fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; + } + if (isrunning) + fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; + } } + fatigueLoss *= duration; + DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); + cls.getCreatureStats(mPtr).setFatigue(fatigue); + + if(sneak || inwater || flying) + vec.z = 0.0f; + + if (inwater || flying) + cls.getCreatureStats(mPtr).land(); if(!onground && !flying && !inwater) { - // The player is in the air (either getting up —ascending part of jump— or falling). + // In the air (either getting up —ascending part of jump— or falling). if (world->isSlowFalling(mPtr)) { // SlowFalling spell effect is active, do not keep previous fall height - mFallHeight = mPtr.getRefData().getPosition().pos[2]; - } - else - { - mFallHeight = std::max(mFallHeight, mPtr.getRefData().getPosition().pos[2]); + cls.getCreatureStats(mPtr).land(); } - const MWWorld::Store &gmst = world->getStore().get(); - forcestateupdate = (mJumpState != JumpState_Falling); mJumpState = JumpState_Falling; @@ -809,8 +1109,7 @@ void CharacterController::update(float duration) } else if(vec.z > 0.0f && mJumpState == JumpState_None) { - // The player has started a jump. - + // Started a jump. float z = cls.getJump(mPtr); if(vec.x == 0 && vec.y == 0) vec = Ogre::Vector3(0.0f, 0.0f, z); @@ -821,7 +1120,8 @@ void CharacterController::update(float duration) } // advance acrobatics - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue const MWWorld::Store &gmst = world->getStore().get(); @@ -835,13 +1135,12 @@ void CharacterController::update(float duration) } else if(mJumpState == JumpState_Falling) { - // The player is landing. - forcestateupdate = true; mJumpState = JumpState_Landing; vec.z = 0.0f; - float healthLost = cls.getFallDamage(mPtr, mFallHeight - mPtr.getRefData().getPosition().pos[2]); + float height = cls.getCreatureStats(mPtr).land(); + float healthLost = cls.getFallDamage(mPtr, height); if (healthLost > 0.0f) { const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); @@ -851,22 +1150,24 @@ void CharacterController::update(float duration) int realHealthLost = healthLost * (1.0f - 0.25 * fatigueTerm); health.setCurrent(health.getCurrent() - realHealthLost); cls.getCreatureStats(mPtr).setHealth(health); + cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), true); - // report acrobatics progression - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); - - const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified(); + const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); if (healthLost > (acrobaticsSkill * fatigueTerm)) { - //TODO: actor falls over + cls.getCreatureStats(mPtr).setKnockedDown(true); + } + else + { + // report acrobatics progression + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } - - mFallHeight = mPtr.getRefData().getPosition().pos[2]; } else { - if(!(vec.z > 0.0f)) + if(!(vec.z > 0.0f)) mJumpState = JumpState_None; vec.z = 0.0f; @@ -901,6 +1202,9 @@ void CharacterController::update(float duration) } } + if (onground) + cls.getCreatureStats(mPtr).land(); + if(movestate != CharState_None) clearAnimQueue(); @@ -919,20 +1223,36 @@ void CharacterController::update(float duration) } } - if(cls.isNpc()) - forcestateupdate = updateNpcState(onground, inwater, isrunning, sneak) || forcestateupdate; + if(cls.hasInventoryStore(mPtr)) + forcestateupdate = updateWeaponState() || forcestateupdate; + else + forcestateupdate = updateCreatureState() || forcestateupdate; refreshCurrentAnims(idlestate, movestate, forcestateupdate); - rot *= duration * Ogre::Math::RadiansToDegrees(1.0f); - world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); + if (!mSkipAnim) + { + rot *= Ogre::Math::RadiansToDegrees(1.0f); + if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut) + { + world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); + } + else //avoid z-rotating for knockdown + world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true); + + // always control actual movement by animation unless this: + // FIXME: actor falling/landing should be controlled by physics engine + if(mMovementAnimVelocity == 0.0f && (vec.length() > 0.0f || mJumpState != JumpState_None)) + { + world->queueMovement(mPtr, vec); + } + } - world->queueMovement(mPtr, vec); movement = vec; + cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0; } else if(cls.getCreatureStats(mPtr).isDead()) { - MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false); world->queueMovement(mPtr, Ogre::Vector3(0.0f)); } @@ -944,9 +1264,11 @@ void CharacterController::update(float duration) else moved = Ogre::Vector3(0.0f); - // Ensure we're moving in generally the right direction + // Ensure we're moving in generally the right direction... if(mMovementSpeed > 0.f) { + float l = moved.length(); + if((movement.x < 0.0f && movement.x < moved.x*2.0f) || (movement.x > 0.0f && movement.x > moved.x*2.0f)) moved.x = movement.x; @@ -956,7 +1278,12 @@ void CharacterController::update(float duration) if((movement.z < 0.0f && movement.z < moved.z*2.0f) || (movement.z > 0.0f && movement.z > moved.z*2.0f)) moved.z = movement.z; + // but keep the original speed + float newLength = moved.length(); + if (newLength > 0) + moved *= (l / newLength); } + // Update movement if(moved.squaredLength() > 1.0f) world->queueMovement(mPtr, moved); @@ -1023,65 +1350,33 @@ void CharacterController::forceStateUpdate() refreshCurrentAnims(mIdleState, mMovementState, true); if(mDeathState != CharState_None) { - const StateInfo *state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState)); - if(state == sDeathListEnd) - throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); - - mCurrentDeath = state->groupname; - if(!mAnimation->getInfo(mCurrentDeath)) - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, - false, 1.0f, "start", "stop", 0.0f, 0); + playRandomDeath(); } } -void CharacterController::kill() +bool CharacterController::kill() { - if(mDeathState != CharState_None) - return; - - if(mPtr.getTypeName() == typeid(ESM::NPC).name()) + if( isDead() ) { - const StateInfo *state = NULL; - if(MWBase::Environment::get().getWorld()->isSwimming(mPtr)) - { - mDeathState = CharState_SwimDeath; - state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState)); - if(state == sDeathListEnd) - throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); - } - - static const CharacterState deathstates[5] = { - CharState_Death1, CharState_Death2, CharState_Death3, CharState_Death4, CharState_Death5 - }; - std::vector states(&deathstates[0], &deathstates[5]); - - while(states.size() > 1 && (!state || !mAnimation->hasAnimation(state->groupname))) + //player's death animation is over + if( mPtr.getRefData().getHandle()=="player" && !isAnimPlaying(mCurrentDeath) ) { - int pos = (int)(rand()/((double)RAND_MAX+1.0)*states.size()); - mDeathState = states[pos]; - states.erase(states.begin()+pos); - - state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState)); - if(state == sDeathListEnd) - throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); + MWBase::Environment::get().getStateManager()->askLoadRecent(); } - mCurrentDeath = state->groupname; - } - else - { - mDeathState = CharState_Death1; - mCurrentDeath = "death1"; + return false; } + playRandomDeath(); + if(mAnimation) { - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, - false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->disable(mCurrentIdle); } mIdleState = CharState_None; mCurrentIdle.clear(); + + return true; } void CharacterController::resurrect() @@ -1091,8 +1386,61 @@ void CharacterController::resurrect() if(mAnimation) mAnimation->disable(mCurrentDeath); - mCurrentDeath.empty(); + mCurrentDeath.clear(); mDeathState = CharState_None; } +void CharacterController::updateContinuousVfx() +{ + // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, + // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. + + // Stop any effects that are no longer active + std::vector effects; + mAnimation->getLoopingEffects(effects); + + for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + { + if (mPtr.getClass().getCreatureStats(mPtr).isDead() + || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).mMagnitude <= 0) + mAnimation->removeEffect(*it); + } +} + +void CharacterController::updateVisibility() +{ + if (!mPtr.getClass().isActor()) + return; + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude) + { + if (mPtr.getRefData().getHandle() == "player") + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude; + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + + mAnimation->setAlpha(alpha); +} + +void CharacterController::determineAttackType() +{ + float * move = mPtr.getClass().getMovementSettings(mPtr).mPosition; + + if(mPtr.getClass().hasInventoryStore(mPtr)) + { + if (move[0] && !move[1]) //sideway + mAttackType = "slash"; + else if (move[1]) //forward + mAttackType = "thrust"; + else + mAttackType = "chop"; + } +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 8670b385e..4009744ef 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -22,13 +22,15 @@ namespace MWMechanics { class Movement; -class NpcStats; +class CreatureStats; enum Priority { Priority_Default, Priority_Jump, Priority_Movement, + Priority_Hit, Priority_Weapon, + Priority_Knockdown, Priority_Torch, Priority_Death, @@ -87,7 +89,12 @@ enum CharacterState { CharState_Death3, CharState_Death4, CharState_Death5, - CharState_SwimDeath + CharState_SwimDeath, + + CharState_Hit, + CharState_KnockDown, + CharState_KnockOut, + CharState_Block }; enum WeaponType { @@ -99,7 +106,7 @@ enum WeaponType { WeapType_TwoWide, WeapType_BowAndArrow, WeapType_Crossbow, - WeapType_ThowWeapon, + WeapType_Thrown, WeapType_PickProbe, WeapType_Spell @@ -138,10 +145,14 @@ class CharacterController CharacterState mMovementState; std::string mCurrentMovement; float mMovementSpeed; + float mMovementAnimVelocity; CharacterState mDeathState; std::string mCurrentDeath; + CharacterState mHitState; + std::string mCurrentHit; + UpperBodyCharacterState mUpperBodyState; JumpingState mJumpState; @@ -156,27 +167,31 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; - // used for acrobatics progress and fall damages - float mFallHeight; - std::string mAttackType; // slash, chop or thrust + void determineAttackType(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); - static void getWeaponGroup(WeaponType weaptype, std::string &group); + void clearAnimQueue(); - static MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, - MWWorld::InventoryStore &inv, - WeaponType *weaptype); + bool updateWeaponState(); + bool updateCreatureState(); - void clearAnimQueue(); + void updateVisibility(); + + void playRandomDeath(float startpoint = 0.0f); - bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak); + /// choose a random animation group with \a prefix and numeric suffix + /// @param num if non-NULL, the chosen animation number will be written here + std::string chooseRandomGroup (const std::string& prefix, int* num = NULL); public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); + // Be careful when to call this, see comment in Actors + void updateContinuousVfx(); + void updatePtr(const MWWorld::Ptr &ptr); void update(float duration); @@ -185,7 +200,7 @@ public: void skipAnim(); bool isAnimPlaying(const std::string &groupName); - void kill(); + bool kill(); void resurrect(); bool isDead() const { return mDeathState != CharState_None; } @@ -193,6 +208,8 @@ public: void forceStateUpdate(); }; + void getWeaponGroup(WeaponType weaptype, std::string &group); + MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); } #endif /* GAME_MWMECHANICS_CHARACTER_HPP */ diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp new file mode 100644 index 000000000..204264106 --- /dev/null +++ b/apps/openmw/mwmechanics/combat.cpp @@ -0,0 +1,137 @@ +#include "combat.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/movement.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwbase/windowmanager.hpp" + +namespace +{ + +Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 n) +{ + return Ogre::Math::ATan2( + n.dotProduct( v1.crossProduct(v2) ), + v1.dotProduct(v2) + ); +} + +} + +namespace MWMechanics +{ + + bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage) + { + if (!blocker.getClass().hasInventoryStore(blocker)) + return false; + + if (blocker.getClass().getCreatureStats(blocker).getKnockedDown() + || blocker.getClass().getCreatureStats(blocker).getHitRecovery()) + return false; + + MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) + return false; + + Ogre::Degree angle = signedAngle (Ogre::Vector3(attacker.getRefData().getPosition().pos) - Ogre::Vector3(blocker.getRefData().getPosition().pos), + blocker.getRefData().getBaseNode()->getOrientation().yAxis(), Ogre::Vector3(0,0,1)); + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + if (angle.valueDegrees() < gmst.find("fCombatBlockLeftAngle")->getFloat()) + return false; + if (angle.valueDegrees() > gmst.find("fCombatBlockRightAngle")->getFloat()) + return false; + + MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); + if (blockerStats.getDrawState() == DrawState_Spell) + return false; + + MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); + + float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2 * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + + 0.1 * blockerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float enemySwing = attackerStats.getAttackStrength(); + float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->getFloat() + gmst.find("fSwingBlockBase")->getFloat(); + + float blockerTerm = blockTerm * swingTerm; + if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0) + blockerTerm *= gmst.find("fBlockStillBonus")->getFloat(); + blockerTerm *= blockerStats.getFatigueTerm(); + + float attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); + float attackerTerm = attackerSkill + 0.2 * attackerStats.getAttribute(ESM::Attribute::Agility).getModified() + + 0.1 * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); + attackerTerm *= attackerStats.getFatigueTerm(); + + int x = int(blockerTerm - attackerTerm); + int iBlockMaxChance = gmst.find("iBlockMaxChance")->getInt(); + int iBlockMinChance = gmst.find("iBlockMinChance")->getInt(); + x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < x) + { + // Reduce shield durability by incoming damage + if (shield->getCellRef().mCharge == -1) + shield->getCellRef().mCharge = shield->getClass().getItemMaxHealth(*shield); + shield->getCellRef().mCharge -= std::min(shield->getCellRef().mCharge, int(damage)); + if (!shield->getCellRef().mCharge) + inv.unequipItem(*shield, blocker); + + // Reduce blocker fatigue + const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat(); + const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat(); + const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->getFloat(); + MWMechanics::DynamicStat fatigue = blockerStats.getFatigue(); + float normalizedEncumbrance = blocker.getClass().getEncumbrance(blocker) / blocker.getClass().getCapacity(blocker); + normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); + float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult; + fatigueLoss += weapon.getClass().getWeight(weapon) * attackerStats.getAttackStrength() * fWeaponFatigueBlockMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + blockerStats.setFatigue(fatigue); + + blockerStats.setBlock(true); + + if (blocker.getClass().isNpc()) + blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); + + return true; + } + return false; + } + + void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage) + { + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + float resistance = std::min(100.f, stats.getMagicEffects().get(ESM::MagicEffect::ResistNormalWeapons).mMagnitude + - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).mMagnitude); + + float multiplier = 0; + if (resistance >= 0) + multiplier = 1 - resistance / 100.f; + else + multiplier = -(resistance-100) / 100.f; + + if (!(weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver + || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) + damage *= multiplier; + + if (weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver + & actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->getFloat(); + + if (damage == 0 && attacker.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); + } + +} diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp new file mode 100644 index 000000000..7f2415697 --- /dev/null +++ b/apps/openmw/mwmechanics/combat.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_MECHANICS_COMBAT_H +#define OPENMW_MECHANICS_COMBAT_H + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + +/// @return can we block the attack? +bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage); + +void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); + +} + +#endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 52ed43be3..8f890befb 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -10,45 +10,18 @@ namespace MWMechanics { CreatureStats::CreatureStats() - : mLevel (0), mLevelHealthBonus(0.f), mDead (false), mDied (false), mFriendlyHits (0), + : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), - mAttackingOrSpell(false), mAttackType(AT_Chop), - mIsWerewolf(false) + mAttackingOrSpell(false), + mIsWerewolf(false), + mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), + mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; } - float CreatureStats::getLevelHealthBonus () const - { - return mLevelHealthBonus; - } - - void CreatureStats::levelUp() - { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - - const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); - - // "When you gain a level, in addition to increasing three primary attributes, your Health - // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, - // the Health increase is calculated from the increased Endurance" - mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); - updateHealth(); - - mLevel++; - } - - void CreatureStats::updateHealth() - { - const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); - const int strength = getAttribute(ESM::Attribute::Strength).getBase(); - - setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); - } - const AiSequence& CreatureStats::getAiSequence() const { return mAiSequence; @@ -73,7 +46,7 @@ namespace MWMechanics - gmst.find ("fFatigueMult")->getFloat() * (1-normalised); } - const Stat &CreatureStats::getAttribute(int index) const + const AttributeValue &CreatureStats::getAttribute(int index) const { if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); @@ -121,20 +94,12 @@ namespace MWMechanics return mLevel; } - int CreatureStats::getAiSetting (int index) const + Stat CreatureStats::getAiSetting (AiSetting index) const { assert (index>=0 && index<4); return mAiSettings[index]; } - Stat &CreatureStats::getAttribute(int index) - { - if (index < 0 || index > 7) { - throw std::runtime_error("attribute index is out of range"); - } - return (!mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]); - } - const DynamicStat &CreatureStats::getDynamic(int index) const { if (index < 0 || index > 2) { @@ -163,11 +128,29 @@ namespace MWMechanics return mMagicEffects; } - void CreatureStats::setAttribute(int index, const Stat &value) + void CreatureStats::setAttribute(int index, int base) + { + AttributeValue current = getAttribute(index); + current.setBase(base); + setAttribute(index, current); + } + + void CreatureStats::setAttribute(int index, const AttributeValue &value) { if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); } + + const AttributeValue& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]; + + if (value != currentValue) + { + if (index != ESM::Attribute::Luck + && index != ESM::Attribute::Personality + && index != ESM::Attribute::Speed) + mRecalcDynamicStats = true; + } + if(!mIsWerewolf) mAttributes[index] = value; else @@ -217,6 +200,10 @@ namespace MWMechanics void CreatureStats::setMagicEffects(const MagicEffects &effects) { + if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude + != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude) + mRecalcDynamicStats = true; + mMagicEffects = effects; } @@ -225,12 +212,19 @@ namespace MWMechanics mAttackingOrSpell = attackingOrSpell; } - void CreatureStats::setAiSetting (int index, int value) + void CreatureStats::setAiSetting (AiSetting index, Stat value) { assert (index>=0 && index<4); mAiSettings[index] = value; } + void CreatureStats::setAiSetting (AiSetting index, int base) + { + Stat stat = getAiSetting(index); + stat.setBase(base); + setAiSetting(index, stat); + } + bool CreatureStats::isDead() const { return mDead; @@ -251,8 +245,10 @@ namespace MWMechanics if (mDead) { if (mDynamic[0].getCurrent()<1) - mDynamic[0].setCurrent (1); - + { + mDynamic[0].setModified(mDynamic[0].getModified(), 1); + mDynamic[0].setCurrent(1); + } if (mDynamic[0].getCurrent()>=1) mDead = false; } @@ -320,6 +316,13 @@ namespace MWMechanics bool CreatureStats::getCreatureTargetted() const { + std::string target; + if (mAiSequence.getCombatTarget(target)) + { + MWWorld::Ptr targetPtr; + targetPtr = MWBase::Environment::get().getWorld()->getPtr(target, true); + return targetPtr.getTypeName() == typeid(ESM::Creature).name(); + } return false; } @@ -328,7 +331,7 @@ namespace MWMechanics float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); evasion *= getFatigueTerm(); - evasion += mMagicEffects.get(EffectKey(ESM::MagicEffect::Sanctuary)).mMagnitude; + evasion += mMagicEffects.get(ESM::MagicEffect::Sanctuary).mMagnitude; return evasion; } @@ -342,4 +345,116 @@ namespace MWMechanics { return mLastHitObject; } + + bool CreatureStats::canUsePower(const std::string &power) const + { + std::map::const_iterator it = mUsedPowers.find(power); + if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) + return true; + else + return false; + } + + void CreatureStats::usePower(const std::string &power) + { + mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); + } + + void CreatureStats::addToFallHeight(float height) + { + mFallHeight += height; + } + + float CreatureStats::land() + { + float height = mFallHeight; + mFallHeight = 0; + return height; + } + + bool CreatureStats::needToRecalcDynamicStats() + { + if (mRecalcDynamicStats) + { + mRecalcDynamicStats = false; + return true; + } + return false; + } + + void CreatureStats::setKnockedDown(bool value) + { + mKnockdown = value; + } + + bool CreatureStats::getKnockedDown() const + { + return mKnockdown; + } + + void CreatureStats::setHitRecovery(bool value) + { + mHitRecovery = value; + } + + bool CreatureStats::getHitRecovery() const + { + return mHitRecovery; + } + + void CreatureStats::setBlock(bool value) + { + mBlock = value; + } + + bool CreatureStats::getBlock() const + { + return mBlock; + } + + bool CreatureStats::getMovementFlag (Flag flag) const + { + return mMovementFlags & flag; + } + + void CreatureStats::setMovementFlag (Flag flag, bool state) + { + if (state) + mMovementFlags |= flag; + else + mMovementFlags &= ~flag; + } + + bool CreatureStats::getStance(Stance flag) const + { + switch (flag) + { + case Stance_Run: + return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun); + case Stance_Sneak: + return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak); + } + return false; // shut up, compiler + } + + DrawState_ CreatureStats::getDrawState() const + { + return mDrawState; + } + + void CreatureStats::setDrawState(DrawState_ state) + { + mDrawState = state; + } + + float CreatureStats::getAttackStrength() const + { + return mAttackStrength; + } + + void CreatureStats::setAttackStrength(float value) + { + mAttackStrength = value; + } + } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 1a7cb5d69..bb9583301 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -10,6 +10,7 @@ #include "spells.hpp" #include "activespells.hpp" #include "aisequence.hpp" +#include "drawstate.hpp" namespace MWMechanics { @@ -18,15 +19,14 @@ namespace MWMechanics /// class CreatureStats { - Stat mAttributes[8]; + DrawState_ mDrawState; + AttributeValue mAttributes[8]; DynamicStat mDynamic[3]; // health, magicka, fatigue - int mLevel; Spells mSpells; ActiveSpells mActiveSpells; MagicEffects mMagicEffects; - int mAiSettings[4]; + Stat mAiSettings[4]; AiSequence mAiSequence; - float mLevelHealthBonus; bool mDead; bool mDied; int mFriendlyHits; @@ -34,20 +34,48 @@ namespace MWMechanics bool mAlarmed; bool mAttacked; bool mHostile; - bool mAttackingOrSpell;//for the player, this is true if the left mouse button is pressed, false if not. + bool mAttackingOrSpell; + bool mKnockdown; + bool mHitRecovery; + bool mBlock; + unsigned int mMovementFlags; + float mAttackStrength; // Note only some creatures attack with weapons - int mAttackType; + float mFallHeight; std::string mLastHitObject; // The last object to hit this actor + // Do we need to recalculate stats derived from attributes or other factors? + bool mRecalcDynamicStats; + + std::map mUsedPowers; protected: bool mIsWerewolf; - Stat mWerewolfAttributes[8]; + AttributeValue mWerewolfAttributes[8]; + int mLevel; public: CreatureStats(); - const Stat & getAttribute(int index) const; + DrawState_ getDrawState() const; + void setDrawState(DrawState_ state); + + /// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest) + float getAttackStrength() const; + void setAttackStrength(float value); + + bool needToRecalcDynamicStats(); + + void addToFallHeight(float height); + + /// Reset the fall height + /// @return total fall height + float land(); + + bool canUsePower (const std::string& power) const; + void usePower (const std::string& power); + + const AttributeValue & getAttribute(int index) const; const DynamicStat & getHealth() const; @@ -67,18 +95,15 @@ namespace MWMechanics int getLevel() const; - int getAiSetting (int index) const; - ///< 0: hello, 1 fight, 2 flee, 3 alarm - - Stat & getAttribute(int index); - Spells & getSpells(); ActiveSpells & getActiveSpells(); MagicEffects & getMagicEffects(); - void setAttribute(int index, const Stat &value); + void setAttribute(int index, const AttributeValue &value); + // Shortcut to set only the base + void setAttribute(int index, int base); void setHealth(const DynamicStat &value); @@ -96,19 +121,18 @@ namespace MWMechanics void setAttackingOrSpell(bool attackingOrSpell); - enum AttackType - { - AT_Slash, - AT_Thrust, - AT_Chop - }; - void setAttackType(int attackType) { mAttackType = attackType; } - int getAttackType() { return mAttackType; } - void setLevel(int level); - void setAiSetting (int index, int value); - ///< 0: hello, 1 fight, 2 flee, 3 alarm + enum AiSetting + { + AI_Hello = 0, + AI_Fight = 1, + AI_Flee = 2, + AI_Alarm = 3 + }; + void setAiSetting (AiSetting index, Stat value); + void setAiSetting (AiSetting index, int base); + Stat getAiSetting (AiSetting index) const; const AiSequence& getAiSequence() const; @@ -117,14 +141,6 @@ namespace MWMechanics float getFatigueTerm() const; ///< Return effective fatigue - float getLevelHealthBonus() const; - - void levelUp(); - - void updateHealth(); - ///< Calculate health based on endurance and strength. - /// Called at character creation and at level up. - bool isDead() const; bool hasDied() const; @@ -164,8 +180,38 @@ namespace MWMechanics float getEvasion() const; + void setKnockedDown(bool value); + bool getKnockedDown() const; + void setHitRecovery(bool value); + bool getHitRecovery() const; + void setBlock(bool value); + bool getBlock() const; + + enum Flag + { + Flag_ForceRun = 1, + Flag_ForceSneak = 2, + Flag_Run = 4, + Flag_Sneak = 8 + }; + enum Stance + { + Stance_Run, + Stance_Sneak + }; + + bool getMovementFlag (Flag flag) const; + void setMovementFlag (Flag flag, bool state); + /// Like getMovementFlag, but also takes into account if the flag is Forced + bool getStance (Stance flag) const; + void setLastHitObject(const std::string &objectid); const std::string &getLastHitObject() const; + + // Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else? + std::set mBoundItems; + // Same as above + std::map mSummonedCreatures; }; } diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp new file mode 100644 index 000000000..d3ea825cf --- /dev/null +++ b/apps/openmw/mwmechanics/disease.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_MECHANICS_DISEASE_H +#define OPENMW_MECHANICS_DISEASE_H + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/class.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" + +namespace MWMechanics +{ + + /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him) + inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) + { + if (!carrier.getClass().isActor()) + return; + + float fDiseaseXferChance = + MWBase::Environment::get().getWorld()->getStore().get().find( + "fDiseaseXferChance")->getFloat(); + + Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); + for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + if (spell->mData.mType == ESM::Spell::ST_Disease + || spell->mData.mType == ESM::Spell::ST_Blight) + { + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < fDiseaseXferChance) + { + // Contracted disease! + actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + + if (actor.getRefData().getHandle() == "player") + { + std::string msg = "sMagicContractDisease"; + msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); + if (msg.find("%s") != std::string::npos) + msg.replace(msg.find("%s"), 2, spell->mName); + MWBase::Environment::get().getWindowManager()->messageBox(msg); + } + } + } + } + } +} + + +#endif diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 4e26b5027..87337cdd7 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -1,5 +1,4 @@ #include "enchanting.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -7,14 +6,12 @@ #include "creaturestats.hpp" #include "npcstats.hpp" -#include namespace MWMechanics { Enchanting::Enchanting() : mCastStyle(ESM::Enchantment::CastOnce) , mSelfEnchanting(false) - , mOldItemCount(0) {} void Enchanting::setOldItem(MWWorld::Ptr oldItem) @@ -24,7 +21,6 @@ namespace MWMechanics { mObjectType = mOldItemPtr.getTypeName(); mOldItemId = mOldItemPtr.getCellRef().mRefID; - mOldItemCount = mOldItemPtr.getRefData().getCount(); } else { @@ -55,25 +51,23 @@ namespace MWMechanics bool Enchanting::create() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); ESM::Enchantment enchantment; enchantment.mData.mCharge = getGemCharge(); - mSoulGemPtr.getRefData().setCount (mSoulGemPtr.getRefData().getCount()-1); + store.remove(mSoulGemPtr, 1, player); //Exception for Azura Star, new one will be added after enchanting - if(boost::iequals(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) - { - MWWorld::ManualRef azura (MWBase::Environment::get().getWorld()->getStore(), "Misc_SoulGem_Azura"); - MWWorld::Class::get (player).getContainerStore (player).add (azura.getPtr(), player); - } + if(Misc::StringUtils::ciEqual(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) + store.add("Misc_SoulGem_Azura", 1, player); if(mSelfEnchanting) { if(getEnchantChance() (RAND_MAX)*100) return false; - MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 1); + MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); } if(mCastStyle==ESM::Enchantment::ConstantEffect) @@ -84,16 +78,18 @@ namespace MWMechanics enchantment.mData.mCost = getEnchantPoints(); enchantment.mEffects = mEffectList; - const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); - - MWWorld::Class::get(mOldItemPtr).applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); + // Create a new item + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId, 1); + const MWWorld::Ptr& newItemPtr = ref.getPtr(); - mOldItemPtr.getRefData().setCount(1); + // Apply the enchantment + const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); + MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId); - ref.getPtr().getRefData().setCount (mOldItemCount-1); + // Add the new item to player inventory and remove the old one + store.remove(mOldItemPtr, 1, player); + store.add(newItemPtr, 1, player); - MWWorld::Class::get (player).getContainerStore (player).add (ref.getPtr(), player); if(!mSelfEnchanting) payForEnchantment(); @@ -215,7 +211,7 @@ namespace MWMechanics return 0; const float enchantCost = getEnchantPoints(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::NpcStats &stats = MWWorld::Class::get(player).getNpcStats(player); int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); @@ -254,7 +250,10 @@ namespace MWMechanics { if (itemEmpty()) return 0; - return MWWorld::Class::get(mOldItemPtr).getEnchantmentPoints(mOldItemPtr); + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + return mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get().find("fEnchantmentMult")->getFloat(); } bool Enchanting::soulEmpty() const { @@ -278,41 +277,26 @@ namespace MWMechanics float Enchanting::getEnchantChance() const { - /* - Formula from http://www.uesp.net/wiki/Morrowind:Enchant - */ - const CreatureStats& creatureStats = MWWorld::Class::get (mEnchanter).getCreatureStats (mEnchanter); const NpcStats& npcStats = MWWorld::Class::get (mEnchanter).getNpcStats (mEnchanter); float chance1 = (npcStats.getSkill (ESM::Skill::Enchant).getModified() + - (0.25 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()) - + (0.125 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())); + (0.25 * npcStats.getAttribute (ESM::Attribute::Intelligence).getModified()) + + (0.125 * npcStats.getAttribute (ESM::Attribute::Luck).getModified())); + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + float chance2 = 7.5 / (gmst.find("fEnchantmentChanceMult")->getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ? + gmst.find("fEnchantmentConstantChanceMult")->getFloat() : 1 )) + * getEnchantPoints(); - float chance2 = 2.5 * getEnchantPoints(); - if(mCastStyle==ESM::Enchantment::ConstantEffect) - { - float constantChance = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentConstantChanceMult")->getFloat(); - chance2 /= constantChance; - } return (chance1-chance2); } void Enchanting::payForEnchantment() const { - MWWorld::Ptr gold; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); - for (MWWorld::ContainerStoreIterator it = store.begin(); - it != store.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) - { - gold = *it; - } - } - - gold.getRefData().setCount(gold.getRefData().getCount() - getEnchantPrice()); + store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); } } diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index a25fd43ab..ae0b25a4a 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -22,13 +22,14 @@ namespace MWMechanics std::string mNewItemName; std::string mObjectType; std::string mOldItemId; - int mOldItemCount; public: Enchanting(); void setEnchanter(MWWorld::Ptr enchanter); void setSelfEnchanting(bool selfEnchanting); void setOldItem(MWWorld::Ptr oldItem); + MWWorld::Ptr getOldItem() { return mOldItemPtr; } + MWWorld::Ptr getGem() { return mSoulGemPtr; } void setNewItemName(const std::string& s); void setEffect(ESM::EffectList effectList); void setSoulGem(MWWorld::Ptr soulGem); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp new file mode 100644 index 000000000..120616f9f --- /dev/null +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -0,0 +1,83 @@ +#ifndef OPENMW_MECHANICS_LEVELLEDLIST_H +#define OPENMW_MECHANICS_LEVELLEDLIST_H + +#include "../mwworld/ptr.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/class.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + +namespace MWMechanics +{ + + /// @return ID of resulting item, or empty if none + inline std::string getLevelledItem (const ESM::LeveledListBase* levItem, bool creature, unsigned char failChance=0) + { + const std::vector& items = levItem->mList; + + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int playerLevel = player.getClass().getCreatureStats(player).getLevel(); + + failChance += levItem->mChanceNone; + + int random = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (random < failChance) + return std::string(); + + std::vector candidates; + int highestLevel = 0; + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + { + if (it->mLevel > highestLevel && it->mLevel <= playerLevel) + highestLevel = it->mLevel; + } + + // For levelled creatures, the flags are swapped. This file format just makes so much sense. + bool allLevels = levItem->mFlags & ESM::ItemLevList::AllLevels; + if (creature) + allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels; + + std::pair highest = std::make_pair(-1, ""); + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + { + if (playerLevel >= it->mLevel + && (allLevels || it->mLevel == highestLevel)) + { + candidates.push_back(it->mId); + if (it->mLevel >= highest.first) + highest = std::make_pair(it->mLevel, it->mId); + } + } + if (candidates.empty()) + return std::string(); + std::string item = candidates[std::rand()%candidates.size()]; + + // Is this another levelled item or a real item? + try + { + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); + if (ref.getPtr().getTypeName() != typeid(ESM::ItemLevList).name() + && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name()) + { + return item; + } + else + { + if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) + return getLevelledItem(ref.getPtr().get()->mBase, failChance); + else + return getLevelledItem(ref.getPtr().get()->mBase, failChance); + } + } + catch (std::logic_error& e) + { + // Vanilla doesn't fail on nonexistent items in levelled lists + std::cerr << "Warning: ignoring nonexistent item '" << item << "'" << std::endl; + return std::string(); + } + } + +} + +#endif diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 3ed458c3f..5be0854ab 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -68,28 +68,6 @@ namespace MWMechanics } } - void MagicEffects::add (const ESM::EffectList& list, float magnitude) - { - for (std::vector::const_iterator iter (list.mList.begin()); iter!=list.mList.end(); - ++iter) - { - EffectParam param; - - 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)*magnitude + iter->mMagnMin); - } - - add (*iter, param); - } - } - MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) { if (this==&effects) diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index b80b5a863..45abda21d 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_MAGICEFFECTS_H #include +#include namespace ESM { @@ -27,10 +28,13 @@ namespace MWMechanics struct EffectParam { - int mMagnitude; + // Note usually this would be int, but applying partial resistance might introduce decimal point. + float mMagnitude; EffectParam(); + EffectParam(float magnitude) : mMagnitude(magnitude) {} + EffectParam& operator+= (const EffectParam& param); EffectParam& operator-= (const EffectParam& param); @@ -48,6 +52,14 @@ namespace MWMechanics return param -= right; } + // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display + struct EffectSourceVisitor + { + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime = -1) = 0; + }; + /// \brief Effects currently affecting a NPC or creature class MagicEffects { @@ -67,9 +79,6 @@ namespace MWMechanics void add (const EffectKey& key, const EffectParam& param); - void add (const ESM::EffectList& list, float magnitude = -1); - ///< \param magnitude normalised magnitude (-1: random) - MagicEffects& operator+= (const MagicEffects& effects); EffectParam get (const EffectKey& key) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8c13db790..4c8f35edb 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -12,11 +12,39 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include + +#include "spellcasting.hpp" + +namespace +{ + /// @return is \a ptr allowed to take/use \a item or is it a crime? + bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim) + { + const std::string& owner = item.getCellRef().mOwner; + bool isOwned = !owner.empty(); + + const std::string& faction = item.getCellRef().mFaction; + bool isFactionOwned = false; + if (!faction.empty()) + { + const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); + if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end()) + isFactionOwned = true; + } + + if (!item.getCellRef().mOwner.empty()) + victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().mOwner, true); + + return (!isOwned && !isFactionOwned); + } +} + namespace MWMechanics { void MechanicsManager::buildPlayer() { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = MWWorld::Class::get (ptr).getNpcStats (ptr); @@ -31,15 +59,14 @@ namespace MWMechanics for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->mNpdt52.mSkills[i]); - creatureStats.getAttribute(0).setBase (player->mNpdt52.mStrength); - creatureStats.getAttribute(1).setBase (player->mNpdt52.mIntelligence); - creatureStats.getAttribute(2).setBase (player->mNpdt52.mWillpower); - creatureStats.getAttribute(3).setBase (player->mNpdt52.mAgility); - creatureStats.getAttribute(4).setBase (player->mNpdt52.mSpeed); - creatureStats.getAttribute(5).setBase (player->mNpdt52.mEndurance); - creatureStats.getAttribute(6).setBase (player->mNpdt52.mPersonality); - creatureStats.getAttribute(7).setBase (player->mNpdt52.mLuck); - + creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt52.mStrength); + creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt52.mIntelligence); + creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt52.mWillpower); + creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt52.mAgility); + creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt52.mSpeed); + creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt52.mEndurance); + creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt52.mPersonality); + creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt52.mLuck); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -55,7 +82,7 @@ namespace MWMechanics { const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i]; - creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale); + creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); } for (int i=0; i<27; ++i) @@ -106,7 +133,7 @@ namespace MWMechanics int attribute = class_->mData.mAttribute[i]; if (attribute>=0 && attribute<8) { - creatureStats.getAttribute(attribute).setBase ( + creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10); } } @@ -124,6 +151,19 @@ namespace MWMechanics npcStats.getSkill (index).setBase ( npcStats.getSkill (index).getBase() + bonus); } + + if (i==1) + { + // Major skill - add starting spells for this skill if existing + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::Store::iterator it = store.get().begin(); + for (; it != store.get().end(); ++it) + { + if (it->mData.mFlags & ESM::Spell::F_PCStart + && spellSchoolToSkill(getSpellSchool(&*it, ptr)) == index) + creatureStats.getSpells().add(it->mId); + } + } } } @@ -156,18 +196,18 @@ namespace MWMechanics creatureStats.setDynamic (i, stat); } - // auto-equip again. we need this for when the race is changed to a beast race - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); + // auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer equippable + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); for (int i=0; igetTimeScaleFactor(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + player.getClass().getInventoryStore(player).rechargeItems(duration); + } + void MechanicsManager::update(float duration, bool paused) { if(!mWatched.isEmpty()) @@ -290,7 +335,7 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager(); const ESM::NPC *player = - world->getPlayer().getPlayer().get()->mBase; + world->getPlayerPtr().get()->mBase; const ESM::Race *race = world->getStore().get().find(player->mRace); @@ -316,7 +361,7 @@ namespace MWMechanics // 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(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); mActors.removeActor(ptr); mActors.addActor(ptr); } @@ -325,9 +370,14 @@ namespace MWMechanics mObjects.update(duration, paused); } - void MechanicsManager::restoreDynamicStats() + void MechanicsManager::rest(bool sleep) { - mActors.restoreDynamicStats (); + mActors.restoreDynamicStats (sleep); + } + + int MechanicsManager::getHoursToRest() const + { + return mActors.getHoursToRest(mWatched); } void MechanicsManager::setPlayerName (const std::string& name) @@ -335,7 +385,7 @@ namespace MWMechanics MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = - *world->getPlayer().getPlayer().get()->mBase; + *world->getPlayerPtr().get()->mBase; player.mName = name; world->createRecord(player); @@ -348,7 +398,7 @@ namespace MWMechanics MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = - *world->getPlayer().getPlayer().get()->mBase; + *world->getPlayerPtr().get()->mBase; player.mRace = race; player.mHead = head; @@ -374,7 +424,7 @@ namespace MWMechanics MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = - *world->getPlayer().getPlayer().get()->mBase; + *world->getPlayerPtr().get()->mBase; player.mClass = id; world->createRecord(player); @@ -391,7 +441,7 @@ namespace MWMechanics const ESM::Class *ptr = world->createRecord(cls); ESM::NPC player = - *world->getPlayer().getPlayer().get()->mBase; + *world->getPlayerPtr().get()->mBase; player.mClass = ptr->mId; world->createRecord(player); @@ -403,15 +453,15 @@ namespace MWMechanics int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr) { - MWMechanics::NpcStats npcSkill = MWWorld::Class::get(ptr).getNpcStats(ptr); + const MWMechanics::NpcStats& npcSkill = MWWorld::Class::get(ptr).getNpcStats(ptr); float x = npcSkill.getBaseDisposition(); MWWorld::LiveCellRef* npc = ptr.get(); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::LiveCellRef* player = playerPtr.get(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); - if (Misc::StringUtils::lowerCase(npc->mBase->mRace) == Misc::StringUtils::lowerCase(player->mBase->mRace)) + if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispRaceMod")->getFloat(); x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispPersonalityMult")->getFloat() @@ -422,23 +472,28 @@ namespace MWMechanics std::string npcFaction = ""; if(!npcSkill.getFactionRanks().empty()) npcFaction = npcSkill.getFactionRanks().begin()->first; - if (playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction)) != playerStats.getFactionRanks().end()) + Misc::StringUtils::toLower(npcFaction); + + if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { - 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) + for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.begin(); + it != MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.end(); ++it) { - if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)) reaction = it->mReaction; + if(Misc::StringUtils::ciEqual(it->mFaction, npcFaction) + && !playerStats.getExpelled(it->mFaction)) + reaction = it->mReaction; } - rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second; + rank = playerStats.getFactionRanks().find(npcFaction)->second; } else if (npcFaction != "") { - 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) + for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.begin(); + it != MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.end();++it) { if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() ) { - if(it->mReactionmReaction; + if(it->mReaction < reaction) + reaction = it->mReaction; } } rank = 0; @@ -459,6 +514,8 @@ namespace MWMechanics if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispWeaponDrawn")->getFloat(); + x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).mMagnitude; + int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used return effective_disposition; } @@ -470,17 +527,17 @@ namespace MWMechanics const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(ptr).getNpcStats(ptr); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); // I suppose the temporary disposition change _has_ to be considered here, // otherwise one would get different prices when exiting and re-entering the dialogue window... int clampedDisposition = std::max(0, std::min(getDerivedDisposition(ptr) + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(),100)); - float a = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float a = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float d = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); @@ -513,9 +570,9 @@ namespace MWMechanics const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - MWMechanics::NpcStats npcStats = MWWorld::Class::get(npc).getNpcStats(npc); + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(npc).getNpcStats(npc); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); float persTerm = playerStats.getAttribute(ESM::Attribute::Personality).getModified() @@ -583,8 +640,14 @@ namespace MWMechanics { float s = int(r * fPerDieRollMult * fPerTempMult); - npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + int(std::max(iPerMinChange, s))))); - npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + int(std::min(-iPerMinChange, -s))))); + int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase(); + int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase(); + npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, + std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s))))); + // TODO: initiate combat and quit dialogue if fight rating is too high + // or should setAiSetting handle this? + npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, + std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s))))); } float c = -std::abs(int(r * fPerDieRollMult)); @@ -614,12 +677,15 @@ namespace MWMechanics float c = std::abs(int(target1 - roll)); - if (roll <= target1) + if (success) { float s = c * fPerDieRollMult * fPerTempMult; - - npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + std::min(-int(iPerMinChange), int(-s))))); - npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + std::max(int(iPerMinChange), int(s))))); + int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase(); + int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase(); + npcStats.setAiSetting (CreatureStats::AI_Flee, + std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s))))); + npcStats.setAiSetting (CreatureStats::AI_Fight, + std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s))))); } x = int(-c * fPerDieRollMult); @@ -679,4 +745,228 @@ namespace MWMechanics return false; } + void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) + { + mActors.updateMagicEffects(ptr); + } + + void MechanicsManager::toggleAI() + { + mAI = !mAI; + } + + bool MechanicsManager::isAIActive() + { + return mAI; + } + + void MechanicsManager::playerLoaded() + { + mUpdatePlayer = true; + mClassSelected = true; + mRaceSelected = true; + mAI = true; + } + + bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) + { + MWWorld::Ptr victim; + if (isAllowedToUse(ptr, bed, victim)) + return false; + + if(commitCrime(ptr, victim, OT_SleepingInOwnedBed)) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage64}"); + return true; + } + else + return false; + } + + void MechanicsManager::objectOpened(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item) + { + MWWorld::Ptr victim; + if (isAllowedToUse(ptr, item, victim)) + return; + commitCrime(ptr, victim, OT_Trespassing); + } + + void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, int count) + { + MWWorld::Ptr victim; + if (isAllowedToUse(ptr, item, victim)) + return; + commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); + } + + bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + { + if (ptr.getRefData().getHandle() != "player") + return false; + + bool reported=false; + for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it) + { + if (it->first != ptr && + MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) && + awarenessCheck(ptr, it->first)) + { + // NPCs will always curse you when they notice you steal their items, even if they don't report the crime + if (it->first == victim && type == OT_Theft) + { + MWBase::Environment::get().getDialogueManager()->say(victim, "Thief"); + } + + // Actor has witnessed a crime. Will he report it? + // (not sure, is > 0 correct?) + if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0) + { + // TODO: stats.setAlarmed(true) on NPCs within earshot + // fAlarmRadius ? + reported=true; + break; + } + } + } + + if (reported) + reportCrime(ptr, victim, type, arg); + return reported; + } + + void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + { + const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + // Bounty for each type of crime + if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + arg = store.find("iCrimeTresspass")->getInt(); + else if (type == OT_Pickpocket) + arg = store.find("iCrimePickPocket")->getInt(); + else if (type == OT_Assault) + arg = store.find("iCrimeAttack")->getInt(); + else if (type == OT_Murder) + arg = store.find("iCrimeKilling")->getInt(); + else if (type == OT_Theft) + arg *= store.find("fCrimeStealing")->getFloat(); + + // TODO: In some cases (type == Assault), if no NPCs are within earshot, the report will have no effect. + // however other crime types seem to be always produce a bounty. + + MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); + ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() + + arg); + + if (!victim.isEmpty()) + { + int fight = 0; + // Increase in fight rating for each type of crime + if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + fight = store.find("iFightTrespass")->getFloat(); + else if (type == OT_Pickpocket) + fight = store.find("iFightPickpocket")->getInt(); + else if (type == OT_Assault) + fight = store.find("iFightAttack")->getInt(); + else if (type == OT_Murder) + fight = store.find("iFightKilling")->getInt(); + else if (type == OT_Theft) + fight = store.find("fFightStealing")->getFloat(); + // Not sure if this should be permanent? + fight = victim.getClass().getCreatureStats(victim).getAiSetting(CreatureStats::AI_Fight).getBase() + fight; + victim.getClass().getCreatureStats(victim).setAiSetting(CreatureStats::AI_Fight, fight); + } + + // If committing a crime against a faction member, expell from the faction + if (!victim.isEmpty() && victim.getClass().isNpc()) + { + std::string factionID; + if(!victim.getClass().getNpcStats(victim).getFactionRanks().empty()) + factionID = victim.getClass().getNpcStats(victim).getFactionRanks().begin()->first; + if (ptr.getClass().getNpcStats(ptr).isSameFaction(victim.getClass().getNpcStats(victim))) + { + ptr.getClass().getNpcStats(ptr).expell(factionID); + } + } + + // TODO: make any guards in the area try to arrest the player + } + + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) + { + if (observer.getClass().getCreatureStats(observer).isDead()) + return false; + + const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + + CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude; + if (invisibility > 0) + return false; + + float sneakTerm = 0; + if (ptr.getClass().getCreatureStats(ptr).getStance(CreatureStats::Stance_Sneak) + && !MWBase::Environment::get().getWorld()->isSwimming(ptr) + && MWBase::Environment::get().getWorld()->isOnGround(ptr)) + { + static float fSneakSkillMult = store.find("fSneakSkillMult")->getFloat(); + static float fSneakBootMult = store.find("fSneakBootMult")->getFloat(); + float sneak = ptr.getClass().getSkill(ptr, ESM::Skill::Sneak); + int agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); + int luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float bootWeight = 0; + if (ptr.getClass().isNpc()) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); + if (it != inv.end()) + bootWeight = it->getClass().getWeight(*it); + } + sneakTerm = fSneakSkillMult * sneak + 0.2 * agility + 0.1 * luck + bootWeight * fSneakBootMult; + } + + static float fSneakDistBase = store.find("fSneakDistanceBase")->getFloat(); + static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->getFloat(); + + Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); + Ogre::Vector3 pos2 (observer.getRefData().getPosition().pos); + float distTerm = fSneakDistBase + fSneakDistMult * pos1.distance(pos2); + + float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude; + float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility; + + CreatureStats& observerStats = observer.getClass().getCreatureStats(observer); + int obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified(); + int obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; + int obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak); + + float obsTerm = obsSneak + 0.2 * obsAgility + 0.1 * obsLuck - obsBlind; + + // is ptr behind the observer? + static float fSneakNoViewMult = store.find("fSneakNoViewMult")->getFloat(); + static float fSneakViewMult = store.find("fSneakViewMult")->getFloat(); + float y = 0; + Ogre::Vector3 vec = pos1 - pos2; + Ogre::Radian angle = observer.getRefData().getBaseNode()->getOrientation().yAxis().angleBetween(vec); + if (angle < Ogre::Degree(90)) + y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult; + else + y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult; + + float target = x - y; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + + return (roll >= target); + } + + void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) + { + mActors.getObjectsInRange(position, radius, objects); + mObjects.getObjectsInRange(position, radius, objects); + } + + std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) + { + return mActors.getActorsFollowing(actor); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index ad07562c7..761caf586 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -29,16 +29,17 @@ namespace MWMechanics bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; + bool mAI;///< is AI active? Objects mObjects; Actors mActors; + public: + void buildPlayer(); ///< build player according to stored class/race/birthsign information. Will /// default to the values of the ESM::NPC object, if no explicit information is given. - public: - MechanicsManager(); virtual void add (const MWWorld::Ptr& ptr); @@ -63,6 +64,8 @@ namespace MWMechanics /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). + virtual void advanceTime (float duration); + virtual void setPlayerName (const std::string& name); ///< Set player name. @@ -78,8 +81,12 @@ namespace MWMechanics virtual void setPlayerClass (const ESM::Class& class_); ///< Set player class to custom class. - virtual void restoreDynamicStats(); - ///< If the player is sleeping, this should be called every hour. + virtual void rest(bool sleep); + ///< If the player is sleeping or waiting, this should be called every hour. + /// @param sleep is the player sleeping or waiting? + + virtual int getHoursToRest() const; + ///< Calculate how many hours the player needs to rest in order to be fully healed virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying); ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. @@ -89,17 +96,51 @@ namespace MWMechanics virtual int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. - + 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 forceStateUpdate(const MWWorld::Ptr &ptr); - - virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); - virtual void skipAnimation(const MWWorld::Ptr& ptr); - virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! + virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer); + + /** + * @brief Commit a crime. If any actors witness the crime and report it, + * reportCrime will be called automatically. + * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. + * @return was the crime reported? + */ + virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + OffenseType type, int arg=0); + virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + OffenseType type, int arg=0); + /// Utility to check if taking this item is illegal and calling commitCrime if so + virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count); + /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so + virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item); + /// Attempt sleeping in a bed. If this is illegal, call commitCrime. + /// @return was it illegal, and someone saw you doing it? + virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed); + + virtual void forceStateUpdate(const MWWorld::Ptr &ptr); + + virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + virtual void skipAnimation(const MWWorld::Ptr& ptr); + virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr); + + virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); + + virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); + + virtual void toggleAI(); + virtual bool isAIActive(); + + virtual void playerLoaded(); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 0b3698289..63b4467f6 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -15,7 +15,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/player.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -23,42 +22,17 @@ #include "../mwbase/soundmanager.hpp" MWMechanics::NpcStats::NpcStats() -: mMovementFlags (0) -, mDrawState (DrawState_Nothing) -, mBounty (0) + : mBounty (0) , mLevelProgress(0) , mDisposition(0) -, mVampire (0) , mReputation(0) , mWerewolfKills (0) , mProfit(0) -, mAttackStrength(0.0f) , mTimeToStartDrowning(20.0) , mLastDrowningHit(0) +, mLevelHealthBonus(0) { - mSkillIncreases.resize (ESM::Attribute::Length); - for (int i=0; i& MWMechanics::NpcStats::getSkill (int index) const +const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const { - if (index<0 || index>=27) + if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); } -MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) +MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) { - if (index<0 || index>=27) + if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); @@ -110,14 +71,26 @@ std::map& MWMechanics::NpcStats::getFactionRanks() return mFactionRank; } -const std::set& MWMechanics::NpcStats::getExpelled() const +bool MWMechanics::NpcStats::getExpelled(const std::string& factionID) const +{ + return mExpelled.find(Misc::StringUtils::lowerCase(factionID)) != mExpelled.end(); +} + +void MWMechanics::NpcStats::expell(const std::string& factionID) { - return mExpelled; + std::string lower = Misc::StringUtils::lowerCase(factionID); + if (mExpelled.find(lower) == mExpelled.end()) + { + std::string message = "#{sExpelledMessage}"; + message += MWBase::Environment::get().getWorld()->getStore().get().find(factionID)->mName; + MWBase::Environment::get().getWindowManager()->messageBox(message); + mExpelled.insert(lower); + } } -std::set& MWMechanics::NpcStats::getExpelled() +void MWMechanics::NpcStats::clearExpelled(const std::string& factionID) { - return mExpelled; + mExpelled.erase(Misc::StringUtils::lowerCase(factionID)); } bool MWMechanics::NpcStats::isSameFaction (const NpcStats& npcStats) const @@ -197,69 +170,73 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, if(mIsWerewolf) return; - float base = getSkill (skillIndex).getBase(); + MWMechanics::SkillValue& value = getSkill (skillIndex); - int level = static_cast (base); + value.setProgress(value.getProgress() + getSkillGain (skillIndex, class_, usageType)); - base += getSkillGain (skillIndex, class_, usageType); - - if (static_cast (base)!=level) + if (value.getProgress()>=1) { // skill leveled up increaseSkill(skillIndex, class_, false); } - else - getSkill (skillIndex).setBase (base); } void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress) { - float base = getSkill (skillIndex).getBase(); + int base = getSkill (skillIndex).getBase(); - int level = static_cast (base); - - if (level >= 100) + if (base >= 100) return; - if (preserveProgress) - base += 1; - else - base = level+1; + base += 1; - // if this is a major or minor skill of the class, increase level progress - bool levelProgress = false; - for (int i=0; i<2; ++i) - for (int j=0; j<5; ++j) + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // is this a minor or major skill? + int increase = gmst.find("iLevelupMiscMultAttriubte")->getInt(); // Note: GMST has a typo + for (int k=0; k<5; ++k) + { + if (class_.mData.mSkills[k][0] == skillIndex) { - int skill = class_.mData.mSkills[j][i]; - if (skill == skillIndex) - levelProgress = true; + mLevelProgress += gmst.find("iLevelUpMinorMult")->getInt(); + increase = gmst.find("iLevelUpMajorMultAttribute")->getInt(); } + } + for (int k=0; k<5; ++k) + { + if (class_.mData.mSkills[k][1] == skillIndex) + { + mLevelProgress += gmst.find("iLevelUpMajorMult")->getInt(); + increase = gmst.find("iLevelUpMinorMultAttribute")->getInt(); + } + } - mLevelProgress += levelProgress; - - // check the attribute this skill belongs to const ESM::Skill* skill = MWBase::Environment::get().getWorld ()->getStore ().get().find(skillIndex); - ++mSkillIncreases[skill->mData.mAttribute]; + mSkillIncreases[skill->mData.mAttribute] += increase; // Play sound & skill progress notification /// \todo check if character is the player, if levelling is ever implemented for NPCs MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1); + std::vector noButtons; + std::stringstream message; message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "")) % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}") % static_cast (base); - MWBase::Environment::get().getWindowManager ()->messageBox(message.str()); + MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), noButtons, MWGui::ShowInDialogueMode_Never); - if (mLevelProgress >= 10) + if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt()) { // levelup is possible now - MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}"); + MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", noButtons, MWGui::ShowInDialogueMode_Never); } getSkill (skillIndex).setBase (base); + if (!preserveProgress) + getSkill(skillIndex).setProgress(0); } int MWMechanics::NpcStats::getLevelProgress () const @@ -272,22 +249,43 @@ void MWMechanics::NpcStats::levelUp() mLevelProgress -= 10; for (int i=0; i &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); + + // "When you gain a level, in addition to increasing three primary attributes, your Health + // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, + // the Health increase is calculated from the increased Endurance" + mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); + updateHealth(); + + setLevel(getLevel()+1); +} + +void MWMechanics::NpcStats::updateHealth() +{ + const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); + const int strength = getAttribute(ESM::Attribute::Strength).getBase(); + + setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { - // Source: http://www.uesp.net/wiki/Morrowind:Level#How_to_Level_Up int num = mSkillIncreases[attribute]; - if (num <= 1) + + if (num == 0) return 1; - else if (num <= 4) - return 2; - else if (num <= 7) - return 3; - else if (num <= 9) - return 4; - else - return 5; + + num = std::min(10, num); + + // iLevelUp01Mult - iLevelUp10Mult + std::stringstream gmst; + gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult"; + + return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->getInt(); } void MWMechanics::NpcStats::flagAsUsed (const std::string& id) @@ -302,12 +300,16 @@ bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const int MWMechanics::NpcStats::getBounty() const { - return mBounty; + if (mIsWerewolf) + return MWBase::Environment::get().getWorld()->getStore().get().find("iWereWolfBounty")->getInt(); + else + return mBounty; } void MWMechanics::NpcStats::setBounty (int bounty) { - mBounty = bounty; + if (!mIsWerewolf) + mBounty = bounty; } int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const @@ -325,16 +327,6 @@ void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, in mFactionReputation[faction] = value; } -bool MWMechanics::NpcStats::isVampire() const -{ - return mVampire; -} - -void MWMechanics::NpcStats::setVampire (bool set) -{ - mVampire = set; -} - int MWMechanics::NpcStats::getReputation() const { return mReputation; @@ -387,7 +379,7 @@ void MWMechanics::NpcStats::setWerewolf (bool set) // Oh, Bethesda. It's "Intelligence". std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : ESM::Attribute::sAttributeNames[i]); - mWerewolfAttributes[i].setModified(int(gmst.find(name)->getFloat()), 0); + mWerewolfAttributes[i].setBase(int(gmst.find(name)->getFloat())); } for(size_t i = 0;i < ESM::Skill::Length;i++) @@ -401,7 +393,7 @@ void MWMechanics::NpcStats::setWerewolf (bool set) // "Mercantile"! >_< std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : ESM::Skill::sSkillNames[i]); - mWerewolfSkill[i].setModified(int(gmst.find(name)->getFloat()), 0); + mWerewolfSkill[i].setBase(int(gmst.find(name)->getFloat())); } } mIsWerewolf = set; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 6b7efa5b7..d7db999e4 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -7,7 +7,6 @@ #include #include "stat.hpp" -#include "drawstate.hpp" #include "creaturestats.hpp" @@ -25,32 +24,17 @@ namespace MWMechanics class NpcStats : public CreatureStats { - public: - - enum Flag - { - Flag_ForceRun = 1, - Flag_ForceSneak = 2, - Flag_Run = 4, - Flag_Sneak = 8 - }; - - private: - /// NPCs other than the player can only have one faction. But for the sake of consistency /// we use the same data structure for the PC and the NPCs. /// \note the faction key must be in lowercase std::map mFactionRank; - DrawState_ mDrawState; int mDisposition; - unsigned int mMovementFlags; - Stat mSkill[27]; - Stat mWerewolfSkill[27]; + SkillValue mSkill[27]; + SkillValue mWerewolfSkill[27]; int mBounty; std::set mExpelled; std::map mFactionReputation; - bool mVampire; int mReputation; int mWerewolfKills; int mProfit; @@ -67,6 +51,8 @@ namespace MWMechanics /// time since last hit from drowning float mLastDrowningHit; + float mLevelHealthBonus; + public: NpcStats(); @@ -75,13 +61,6 @@ namespace MWMechanics int getProfit() const; void modifyProfit(int diff); - DrawState_ getDrawState() const; - void setDrawState (DrawState_ state); - - /// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest) - float getAttackStrength() const; - void setAttackStrength(float value); - int getBaseDisposition() const; void setBaseDisposition(int disposition); @@ -90,18 +69,16 @@ namespace MWMechanics void setReputation(int reputation); - bool getMovementFlag (Flag flag) const; - - void setMovementFlag (Flag flag, bool state); - - const Stat& getSkill (int index) const; - Stat& getSkill (int index); + const SkillValue& getSkill (int index) const; + SkillValue& getSkill (int index); const std::map& getFactionRanks() const; std::map& getFactionRanks(); - const std::set& getExpelled() const; - std::set& getExpelled(); + const std::set& getExpelled() const { return mExpelled; } + bool getExpelled(const std::string& factionID) const; + void expell(const std::string& factionID); + void clearExpelled(const std::string& factionID); bool isSameFaction (const NpcStats& npcStats) const; ///< Do *this and \a npcStats share a faction? @@ -123,6 +100,10 @@ namespace MWMechanics void levelUp(); + void updateHealth(); + ///< Calculate health based on endurance and strength. + /// Called at character creation and at level up. + void flagAsUsed (const std::string& id); bool hasBeenUsed (const std::string& id) const; @@ -135,10 +116,6 @@ namespace MWMechanics void setFactionReputation (const std::string& faction, int value); - bool isVampire() const; - - void setVampire (bool set); - bool hasSkillsForRank (const std::string& factionId, int rank) const; bool isWerewolf() const; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 694987855..ba35af777 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -14,6 +14,16 @@ Objects::Objects() { } +Objects::~Objects() +{ + PtrControllerMap::iterator it(mObjects.begin()); + for (; it != mObjects.end();++it) + { + delete it->second; + it->second = NULL; + } +} + void Objects::addObject(const MWWorld::Ptr& ptr) { removeObject(ptr); @@ -45,7 +55,7 @@ void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) } } -void Objects::dropObjects (const MWWorld::Ptr::CellStore *cellStore) +void Objects::dropObjects (const MWWorld::CellStore *cellStore) { PtrControllerMap::iterator iter = mObjects.begin(); while(iter != mObjects.end()) @@ -82,4 +92,13 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr) iter->second->skipAnim(); } +void Objects::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out) +{ + for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius) + out.push_back(iter->first); + } +} + } diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 5cdcdaa0a..373a2a105 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -21,6 +21,7 @@ namespace MWMechanics public: Objects(); + ~Objects(); void addObject (const MWWorld::Ptr& ptr); ///< Register an animated object @@ -39,6 +40,8 @@ namespace MWMechanics void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); void skipAnimation(const MWWorld::Ptr& ptr); + + void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& out); }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 8ef0edab8..4407363a6 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -4,31 +4,13 @@ #include "../mwbase/environment.hpp" #include "OgreMath.h" +#include "OgreVector3.h" -#include -#include + +#include namespace { - struct found_path {}; - - typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS, - boost::property, boost::property > - PathGridGraph; - typedef boost::property_map::type WeightMap; - typedef PathGridGraph::vertex_descriptor PointID; - typedef PathGridGraph::edge_descriptor PointConnectionID; - - class goalVisited : public boost::default_dijkstra_visitor - { - public: - goalVisited(PointID goal) {mGoal = goal;}; - void examine_vertex(PointID u, const PathGridGraph g) {if(u == mGoal) throw found_path();}; - - private: - PointID mGoal; - }; - float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) { x -= point.mX; @@ -53,9 +35,9 @@ namespace return sqrt(x * x + y * y + z * z); } - static float sgn(float a) + static float sgn(Ogre::Radian a) { - if(a > 0) + if(a.valueRadians() > 0) return 1.0; return -1.0; } @@ -80,64 +62,106 @@ namespace return closestIndex; } - PathGridGraph buildGraph(const ESM::Pathgrid* pathgrid, float xCell = 0, float yCell = 0) + /*std::list reconstructPath(const std::vector& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell) { - PathGridGraph graph; - - for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) + std::list path; + while(graph[lastNode].parent != -1) { - PointID pID = boost::add_vertex(graph); - graph[pID].mX = pathgrid->mPoints[counter].mX + xCell; - graph[pID].mY = pathgrid->mPoints[counter].mY + yCell; - graph[pID].mZ = pathgrid->mPoints[counter].mZ; + //std::cout << "not empty" << xCell; + ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + lastNode = graph[lastNode].parent; } + return path; + }*/ - for(unsigned int counterTwo = 0; counterTwo < pathgrid->mEdges.size(); counterTwo++) - { - PointID u = pathgrid->mEdges[counterTwo].mV0; - PointID v = pathgrid->mEdges[counterTwo].mV1; - - PointConnectionID edge; - bool done; - boost::tie(edge, done) = boost::add_edge(u, v, graph); - WeightMap weightmap = boost::get(boost::edge_weight, graph); - weightmap[edge] = distance(graph[u], graph[v]); - } - return graph; - } - std::list findPath(PointID start, PointID end, PathGridGraph graph) + /*std::list buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0) { - std::vector p(boost::num_vertices(graph)); - std::vector d(boost::num_vertices(graph)); - std::list shortest_path; - - try + std::vector graph; + for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++) { - boost::dijkstra_shortest_paths(graph, start, - boost::predecessor_map(&p[0]).distance_map(&d[0]).visitor(goalVisited(end))); + Node node; + node.label = i; + node.parent = -1; + graph.push_back(node); } + for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++) + { + Edge edge; + edge.destination = pathgrid->mEdges[i].mV1; + edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]); + graph[pathgrid->mEdges[i].mV0].edges.push_back(edge); + edge.destination = pathgrid->mEdges[i].mV0; + graph[pathgrid->mEdges[i].mV1].edges.push_back(edge); + } + + std::vector g_score(pathgrid->mPoints.size(),-1.); + std::vector f_score(pathgrid->mPoints.size(),-1.); + + g_score[start] = 0; + f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; - catch(found_path& fg) + while(!openset.empty()) { - for(PointID v = end; ; v = p[v]) + current = openset.front(); + openset.pop_front(); + + if(current == goal) break; + + closedset.push_back(current); + + for(int j = 0;jmPoints[dest],pathgrid->mPoints[goal]); + if(!isInOpenSet) + { + std::list::iterator it = openset.begin(); + for(it = openset.begin();it!= openset.end();it++) + { + if(g_score[*it]>g_score[dest]) + break; + } + openset.insert(it,dest); + } + } + } } + } + return reconstructPath(graph,pathgrid,current,xCell,yCell); + + }*/ - return shortest_path; - } } namespace MWMechanics { PathFinder::PathFinder() + : mIsPathConstructed(false), + mIsGraphConstructed(false), + mCell(NULL) { - mIsPathConstructed = false; } void PathFinder::clearPath() @@ -147,9 +171,124 @@ namespace MWMechanics mIsPathConstructed = false; } + void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) + { + mGraph.clear(); + mGScore.resize(pathGrid->mPoints.size(),-1); + mFScore.resize(pathGrid->mPoints.size(),-1); + Node defaultNode; + defaultNode.label = -1; + defaultNode.parent = -1; + mGraph.resize(pathGrid->mPoints.size(),defaultNode); + for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) + { + Node node; + node.label = i; + node.parent = -1; + mGraph[i] = node; + } + for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) + { + Edge edge; + edge.destination = pathGrid->mEdges[i].mV1; + edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); + edge.destination = pathGrid->mEdges[i].mV0; + mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); + } + mIsGraphConstructed = true; + } + + void PathFinder::cleanUpAStar() + { + for(int i=0;i (mGraph.size());i++) + { + mGraph[i].parent = -1; + mGScore[i] = -1; + mFScore[i] = -1; + } + } + + std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) + { + cleanUpAStar(); + mGScore[start] = 0; + mFScore[start] = distance(pathGrid->mPoints[start],pathGrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); + openset.pop_front(); + + if(current == goal) break; + + closedset.push_back(current); + + for(int j = 0;j (mGraph[current].edges.size());j++) + { + //int next = mGraph[current].edges[j].destination + if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) + { + int dest = mGraph[current].edges[j].destination; + float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); + if(!isInOpenSet + || tentative_g < mGScore[dest] ) + { + mGraph[dest].parent = current; + mGScore[dest] = tentative_g; + mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); + if(!isInOpenSet) + { + std::list::iterator it = openset.begin(); + for(it = openset.begin();it!= openset.end();it++) + { + if(mGScore[*it]>mGScore[dest]) + break; + } + openset.insert(it,dest); + } + } + } + } + + } + + std::list path; + while(mGraph[current].parent != -1) + { + //std::cout << "not empty" << xCell; + ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = mGraph[current].parent; + } + + if(path.empty()) + { + ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + } + + return path; + } + void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const ESM::Pathgrid *pathGrid, float xCell, float yCell, bool allowShortcuts) + const MWWorld::CellStore* cell, bool allowShortcuts) { + mPath.clear(); + if(mCell != cell) mIsGraphConstructed = false; + mCell = cell; + if(allowShortcuts) { if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, @@ -159,13 +298,24 @@ namespace MWMechanics if(!allowShortcuts) { + const ESM::Pathgrid *pathGrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->mCell); + float xCell = 0; + float yCell = 0; + + if (mCell->isExterior()) + { + xCell = mCell->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = mCell->mCell->mData.mY * ESM::Land::REAL_SIZE; + } int startNode = getClosestPoint(pathGrid, startPoint.mX - xCell, startPoint.mY - yCell,startPoint.mZ); int endNode = getClosestPoint(pathGrid, endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ); if(startNode != -1 && endNode != -1) { - PathGridGraph graph = buildGraph(pathGrid, xCell, yCell); - mPath = findPath(startNode, endNode, graph); + if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); + + mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph); if(!mPath.empty()) { @@ -189,14 +339,35 @@ namespace MWMechanics // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call // if otherwise). if(mPath.empty()) - return 0; + return 0.; const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); float directionX = nextPoint.mX - x; float directionY = nextPoint.mY - y; float directionResult = sqrt(directionX * directionX + directionY * directionY); - return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees(); + return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees(); + } + + float PathFinder::getDistToNext(float x, float y, float z) + { + ESM::Pathgrid::Point nextPoint = *mPath.begin(); + return distance(nextPoint, x, y, z); + } + + bool PathFinder::checkWaypoint(float x, float y, float z) + { + if(mPath.empty()) + return true; + + ESM::Pathgrid::Point nextPoint = *mPath.begin(); + if(distanceZCorrected(nextPoint, x, y, z) < 64) + { + mPath.pop_front(); + if(mPath.empty()) mIsPathConstructed = false; + return true; + } + return false; } bool PathFinder::checkPathCompleted(float x, float y, float z) @@ -217,5 +388,22 @@ namespace MWMechanics return false; } + + void PathFinder::syncStart(const std::list &path) + { + std::list::const_iterator oldStart = path.begin(); + std::list::iterator iter = ++mPath.begin(); + + if( (*iter).mX == oldStart->mX + && (*iter).mY == oldStart->mY + && (*iter).mZ == oldStart->mZ + && (*iter).mAutogenerated == oldStart->mAutogenerated + && (*iter).mConnectionNum == oldStart->mConnectionNum ) + { + mPath.pop_front(); + } + + } + } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 35e0fa908..8771ef0ca 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -4,6 +4,11 @@ #include #include +namespace MWWorld +{ + class CellStore; +} + namespace MWMechanics { class PathFinder @@ -12,22 +17,74 @@ namespace MWMechanics PathFinder(); void clearPath(); + + void buildPathgridGraph(const ESM::Pathgrid* pathGrid); + void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const ESM::Pathgrid* pathGrid, float xCell = 0, float yCell = 0, - bool allowShortcuts = true); + const MWWorld::CellStore* cell, bool allowShortcuts = true); bool checkPathCompleted(float x, float y, float z); ///< \Returns true if the last point of the path has been reached. + + bool checkWaypoint(float x, float y, float z); + ///< \Returns true if a way point was reached + float getZAngleToNext(float x, float y) const; + float getDistToNext(float x, float y, float z); + bool isPathConstructed() const { return mIsPathConstructed; } + int getPathSize() const + { + return mPath.size(); + } + + std::list getPath() const + { + return mPath; + } + + //When first point of newly created path is the nearest to actor point, then + //the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path) + //This functions deletes that point. + void syncStart(const std::list &path); + + void addPointToPath(ESM::Pathgrid::Point &point) + { + mPath.push_back(point); + } + private: - std::list mPath; + + struct Edge + { + int destination; + float cost; + }; + struct Node + { + int label; + std::vector edges; + int parent;//used in pathfinding + }; + + std::vector mGScore; + std::vector mFScore; + + std::list aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell = 0, float yCell = 0); + void cleanUpAStar(); + + std::vector mGraph; bool mIsPathConstructed; + + + std::list mPath; + bool mIsGraphConstructed; + const MWWorld::CellStore* mCell; }; } diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp new file mode 100644 index 000000000..53681caf8 --- /dev/null +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -0,0 +1,66 @@ +#include "pickpocket.hpp" + +#include "../mwworld/class.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "npcstats.hpp" + +namespace MWMechanics +{ + + Pickpocket::Pickpocket(const MWWorld::Ptr &thief, const MWWorld::Ptr &victim) + : mThief(thief) + , mVictim(victim) + { + } + + float Pickpocket::getChanceModifier(const MWWorld::Ptr &ptr, float add) + { + NpcStats& stats = ptr.getClass().getNpcStats(ptr); + float agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float sneak = ptr.getClass().getSkill(ptr, ESM::Skill::Sneak); + return (add + 0.2 * agility + 0.1 * luck + sneak) * stats.getFatigueTerm(); + } + + bool Pickpocket::getDetected(float valueTerm) + { + float x = getChanceModifier(mThief); + float y = getChanceModifier(mVictim, valueTerm); + + float t = 2*x - y; + + float pcSneak = mThief.getClass().getSkill(mThief, ESM::Skill::Sneak); + int iPickMinChance = MWBase::Environment::get().getWorld()->getStore().get() + .find("iPickMinChance")->getInt(); + int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() + .find("iPickMaxChance")->getInt(); + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (t < pcSneak / iPickMinChance) + { + return (roll > int(pcSneak / iPickMinChance)); + } + else + { + t = std::min(float(iPickMaxChance), t); + return (roll > int(t)); + } + } + + bool Pickpocket::pick(MWWorld::Ptr item, int count) + { + float stackValue = item.getClass().getValue(item) * count; + float fPickPocketMod = MWBase::Environment::get().getWorld()->getStore().get() + .find("fPickPocketMod")->getFloat(); + float valueTerm = 10 * fPickPocketMod * stackValue; + + return getDetected(valueTerm); + } + + bool Pickpocket::finish() + { + return getDetected(0.f); + } + +} diff --git a/apps/openmw/mwmechanics/pickpocket.hpp b/apps/openmw/mwmechanics/pickpocket.hpp new file mode 100644 index 000000000..4de1e37f8 --- /dev/null +++ b/apps/openmw/mwmechanics/pickpocket.hpp @@ -0,0 +1,30 @@ +#ifndef OPENMW_MECHANICS_PICKPOCKET_H +#define OPENMW_MECHANICS_PICKPOCKET_H + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + + class Pickpocket + { + public: + Pickpocket (const MWWorld::Ptr& thief, const MWWorld::Ptr& victim); + + /// Steal some items + /// @return Was the thief detected? + bool pick (MWWorld::Ptr item, int count); + /// End the pickpocketing process + /// @return Was the thief detected? + bool finish (); + + private: + bool getDetected(float valueTerm); + float getChanceModifier(const MWWorld::Ptr& ptr, float add=0); + MWWorld::Ptr mThief; + MWWorld::Ptr mVictim; + }; + +} + +#endif diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 66c492bf8..1b17f8305 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -8,7 +8,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" @@ -20,25 +19,17 @@ namespace MWMechanics void Repair::repair(const MWWorld::Ptr &itemToRepair) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::LiveCellRef *ref = mTool.get(); + // unstack tool if required + player.getClass().getContainerStore(player).unstack(mTool, player); + // reduce number of uses left int uses = (mTool.getCellRef().mCharge != -1) ? mTool.getCellRef().mCharge : ref->mBase->mData.mUses; mTool.getCellRef().mCharge = uses-1; - // unstack tool if required - if (mTool.getRefData().getCount() > 1 && uses == ref->mBase->mData.mUses) - { - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); - MWWorld::ContainerStoreIterator it = store.add(mTool, player); - it->getRefData().setCount(mTool.getRefData().getCount()-1); - it->getCellRef().mCharge = -1; - - mTool.getRefData().setCount(1); - } - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player); @@ -85,7 +76,10 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) // tool used up? if (mTool.getCellRef().mCharge == 0) { - mTool.getRefData().setCount(0); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + + store.remove(mTool, 1, player); std::string message = MWBase::Environment::get().getWorld()->getStore().get() .find("sNotifyMessage51")->getString(); @@ -93,8 +87,6 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % MWWorld::Class::get(mTool).getName(mTool)).str()); // try to find a new tool of the same ID - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index d19da6e2a..2e5eaecfd 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -1,11 +1,12 @@ #include "security.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" @@ -45,6 +46,7 @@ namespace MWMechanics resultMessage = "#{sLockImpossible}"; else { + MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, lock); int roll = static_cast (std::rand()) / RAND_MAX * 100; if (roll <= x) { @@ -61,7 +63,7 @@ namespace MWMechanics lockpick.getCellRef().mCharge = lockpick.get()->mBase->mData.mUses; --lockpick.getCellRef().mCharge; if (!lockpick.getCellRef().mCharge) - lockpick.getRefData().setCount(0); + lockpick.getContainerStore()->remove(lockpick, 1, mActor); } void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe, @@ -86,6 +88,7 @@ namespace MWMechanics resultMessage = "#{sTrapImpossible}"; else { + MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, trap); int roll = static_cast (std::rand()) / RAND_MAX * 100; if (roll <= x) { @@ -103,7 +106,7 @@ namespace MWMechanics probe.getCellRef().mCharge = probe.get()->mBase->mData.mUses; --probe.getCellRef().mCharge; if (!probe.getCellRef().mCharge) - probe.getRefData().setCount(0); + probe.getContainerStore()->remove(probe, 1, mActor); } } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp new file mode 100644 index 000000000..fe395e566 --- /dev/null +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -0,0 +1,736 @@ +#include "spellcasting.hpp" + +#include + +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/containerstore.hpp" +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" + +#include "../mwrender/animation.hpp" + +#include "magiceffects.hpp" +#include "npcstats.hpp" + +namespace MWMechanics +{ + + ESM::Skill::SkillEnum spellSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) + { + CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) + return 0; + + float y = FLT_MAX; + float lowestSkill = 0; + + for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + { + float x = it->mDuration; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + it->mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + x *= 0.1 * magicEffect->mData.mBaseCost; + x *= 0.5 * (it->mMagnMin + it->mMagnMax); + x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; + if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) + x *= 1.5; + static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fEffectCostMult")->getFloat(); + x *= fEffectCostMult; + + float s = 2 * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); + if (s - x < y) + { + y = s - x; + if (effectiveSchool) + *effectiveSchool = magicEffect->mData.mSchool; + lowestSkill = s; + } + } + + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100; + + int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; + + int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + + float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm(); + if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player") + castChance = 100; + + return std::max(0.f, std::min(100.f, castChance)); + } + + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool) + { + const ESM::Spell* spell = + MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + return getSpellSuccessChance(spell, actor, effectiveSchool); + } + + + int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spellId, actor, &school); + return school; + } + + int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spell, actor, &school); + return school; + } + + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectId); + + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude; + if (weaknessEffect != -1) + resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude; + + + float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); + + // This makes spells that are easy to cast harder to resist and vice versa + if (spell != NULL && caster.getClass().isActor()) + { + float castChance = getSpellSuccessChance(spell, caster); + if (castChance > 0) + x *= 50 / castChance; + } + + float roll = static_cast(std::rand()) / RAND_MAX * 100; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + roll -= resistance; + + if (x <= roll) + x = 0; + else + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + x = 100; + else + x = roll / std::min(x, 100.f); + } + + x = std::min(x + resistance, 100.f); + + resisted = x; + } + + return resisted; + } + + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell) + { + float resistance = getEffectResistance(effectId, actor, caster, spell); + if (resistance >= 0) + return 1 - resistance / 100.f; + else + return -(resistance-100) / 100.f; + } + + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) + : mCaster(caster) + , mTarget(target) + , mStack(false) + , mHitPosition(0,0,0) + { + } + + void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, + const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) + { + // If none of the effects need to apply, we can early-out + bool found = false; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != range) + continue; + found = true; + } + if (!found) + return; + + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); + if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + { + float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? + target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude + : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll <= x) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + return; + } + } + + ESM::EffectList reflectedEffects; + std::vector appliedLastingEffects; + bool firstAppliedEffect = true; + bool anyHarmfulEffect = false; + + for (std::vector::const_iterator effectIt (effects.mList.begin()); + effectIt!=effects.mList.end(); ++effectIt) + { + if (effectIt->mRange != range) + continue; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate) + { + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + continue; + } + + if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() && + (effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention || + effectIt->mEffectID == ESM::MagicEffect::DivineIntervention || + effectIt->mEffectID == ESM::MagicEffect::Mark || + effectIt->mEffectID == ESM::MagicEffect::Recall)) + { + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + continue; + } + + // If player is healing someone, show the target's HP bar + if (caster.getRefData().getHandle() == "player" && target != caster + && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth + && target.getClass().isActor()) + MWBase::Environment::get().getWindowManager()->setEnemy(target); + + float magnitudeMult = 1; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) + { + anyHarmfulEffect = true; + + // If player is attempting to cast a harmful spell, show the target's HP bar + if (caster.getRefData().getHandle() == "player" && target != caster) + MWBase::Environment::get().getWindowManager()->setEnemy(target); + + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + if (spell && caster != target) + { + int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + bool isAbsorbed = (roll < absorb); + if (isAbsorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + magnitudeMult = 0; + } + } + + // Try reflecting + if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) + { + int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + bool isReflected = (roll < reflect); + if (isReflected) + { + const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, ""); + reflectedEffects.mList.push_back(*effectIt); + magnitudeMult = 0; + } + } + + // Try resisting + if (magnitudeMult > 0 && target.getClass().isActor()) + { + + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } + } + } + + + if (magnitudeMult > 0) + { + float random = std::rand() / static_cast(RAND_MAX); + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + magnitude *= magnitudeMult; + + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + if (target.getClass().isActor() && hasDuration) + { + ActiveSpells::Effect effect; + effect.mKey = MWMechanics::EffectKey(*effectIt); + effect.mDuration = effectIt->mDuration; + effect.mMagnitude = magnitude; + + appliedLastingEffects.push_back(effect); + + // For absorb effects, also apply the effect to the caster - but with a negative + // magnitude, since we're transfering stats from the target to the caster + for (int i=0; i<5; ++i) + { + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::Effect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + // Also make sure to set casterHandle = target, so that the effect on the caster gets purged when the target dies + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + effects, mSourceName, target.getRefData().getHandle()); + } + } + } + else + applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); + + // HACK: Damage attribute/skill actually has a duration, even though the actual effect is instant and permanent. + // This was probably just done to have the effect visible in the magic menu for a while + // to notify the player they've been damaged? + if (effectIt->mEffectID == ESM::MagicEffect::DamageAttribute + || effectIt->mEffectID == ESM::MagicEffect::DamageSkill + || effectIt->mEffectID == ESM::MagicEffect::RestoreAttribute + || effectIt->mEffectID == ESM::MagicEffect::RestoreSkill + ) + applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); + + if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + { + // Play sound, only for the first effect + if (firstAppliedEffect) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + firstAppliedEffect = false; + } + + // Add VFX + const ESM::Static* castStatic; + if (!magicEffect->mHit.empty()) + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + else + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + // Note: in case of non actor, a free effect should be fine as well + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if (anim) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } + } + } + + if (!exploded) + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName); + + if (reflectedEffects.mList.size()) + inflict(caster, target, reflectedEffects, range, true); + + if (appliedLastingEffects.size()) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, caster.getRefData().getHandle()); + + if (anyHarmfulEffect && target.getClass().isActor() && target != caster + && target.getClass().getCreatureStats(target).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30) + MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault); + } + + void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) + { + short effectId = effect.mId; + if (!target.getClass().isActor()) + { + if (effectId == ESM::MagicEffect::Lock) + { + if (target.getCellRef().mLockLevel < magnitude) + target.getCellRef().mLockLevel = magnitude; + } + else if (effectId == ESM::MagicEffect::Open) + { + if (target.getCellRef().mLockLevel <= magnitude) + { + if (target.getCellRef().mLockLevel > 0) + { + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); + MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); + } + target.getCellRef().mLockLevel = 0; + } + else + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + } + } + else + { + if (effectId == ESM::MagicEffect::DamageAttribute || effectId == ESM::MagicEffect::RestoreAttribute) + { + int attribute = effect.mArg; + AttributeValue value = target.getClass().getCreatureStats(target).getAttribute(attribute); + if (effectId == ESM::MagicEffect::DamageAttribute) + value.damage(magnitude); + else + value.restore(magnitude); + target.getClass().getCreatureStats(target).setAttribute(attribute, value); + } + else if (effectId == ESM::MagicEffect::DamageSkill || effectId == ESM::MagicEffect::RestoreSkill) + { + if (target.getTypeName() != typeid(ESM::NPC).name()) + return; + int skill = effect.mArg; + SkillValue& value = target.getClass().getNpcStats(target).getSkill(skill); + if (effectId == ESM::MagicEffect::DamageSkill) + value.damage(magnitude); + else + value.restore(magnitude); + } + + if (effectId == ESM::MagicEffect::CurePoison) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); + else if (effectId == ESM::MagicEffect::CureParalyzation) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); + else if (effectId == ESM::MagicEffect::CureCommonDisease) + target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); + else if (effectId == ESM::MagicEffect::CureBlightDisease) + target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); + else if (effectId == ESM::MagicEffect::CureCorprusDisease) + target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); + else if (effectId == ESM::MagicEffect::Dispel) + target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude); + else if (effectId == ESM::MagicEffect::RemoveCurse) + target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + + if (target.getRefData().getHandle() != "player") + return; + if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) + return; + + if (effectId == ESM::MagicEffect::DivineIntervention) + { + MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker"); + } + else if (effectId == ESM::MagicEffect::AlmsiviIntervention) + { + MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker"); + } + + else if (effectId == ESM::MagicEffect::Mark) + { + MWBase::Environment::get().getWorld()->getPlayer().markPosition( + target.getCell(), target.getRefData().getPosition()); + } + else if (effectId == ESM::MagicEffect::Recall) + { + MWWorld::CellStore* markedCell = NULL; + ESM::Position markedPosition; + + MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell) + { + MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->mCell->mName, + markedPosition); + action.execute(target); + } + } + } + } + + bool CastSpell::cast(const std::string &id) + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(spell); + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(potion); + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(ingredient); + + throw std::runtime_error("ID type cannot be casted"); + } + + bool CastSpell::cast(const MWWorld::Ptr &item) + { + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + throw std::runtime_error("can't cast an item without an enchantment"); + + mSourceName = item.getClass().getName(item); + mId = item.getCellRef().mRefID; + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + + mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce); + + // Check if there's enough charge left + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + const float enchantCost = enchantment->mData.mCost; + int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant); + const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + + if (item.getCellRef().mEnchantmentCharge == -1) + item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + + if (item.getCellRef().mEnchantmentCharge < castCost) + { + // TODO: Should there be a sound here? + if (mCaster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + return false; + } + // Reduce charge + item.getCellRef().mEnchantmentCharge -= castCost; + } + + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + { + if (mCaster.getRefData().getHandle() == "player") + mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); + } + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + item.getContainerStore()->remove(item, 1, mCaster); + else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) + { + if (mCaster.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); + } + } + + inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); + + if (!mTarget.isEmpty()) + { + if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); + } + + MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName); + + return true; + } + + bool CastSpell::cast(const ESM::Potion* potion) + { + mSourceName = potion->mName; + mId = potion->mId; + mStack = true; + + inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); + + return true; + } + + bool CastSpell::cast(const ESM::Spell* spell) + { + mSourceName = spell->mName; + mId = spell->mId; + mStack = false; + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + int school = 0; + + if (mCaster.getClass().isActor()) + { + school = getSpellSchool(spell, mCaster); + + CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster); + + // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) + static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->getFloat(); + static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->getFloat(); + DynamicStat fatigue = stats.getFatigue(); + const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster); + float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); + + bool fail = false; + + // Check success + int successChance = getSpellSuccessChance(spell, mCaster); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (!fail && roll >= successChance) + { + if (mCaster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + fail = true; + } + + if (fail) + { + // Failure sound + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); + return false; + } + } + + if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell) + mCaster.getClass().skillUsageSucceeded(mCaster, + spellSchoolToSkill(school), 0); + + inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); + + if (!mTarget.isEmpty()) + { + if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) + { + inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); + } + } + + MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName); + return true; + } + + bool CastSpell::cast (const ESM::Ingredient* ingredient) + { + mId = ingredient->mId; + mStack = true; + mSourceName = ingredient->mName; + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[0]; + effect.mSkill = ingredient->mData.mSkills[0]; + effect.mAttribute = ingredient->mData.mAttributes[0]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effect.mEffectID); + + const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster); + const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); + + float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + + 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll > x) + { + // "X has no effect on you" + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->getString(); + message = boost::str(boost::format(message) % ingredient->mName); + MWBase::Environment::get().getWindowManager()->messageBox(message); + return false; + } + + float magnitude = 0; + float y = roll / std::min(x, 100.f); + y *= 0.25 * x; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + effect.mDuration = int(y); + else + effect.mDuration = 1; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost)); + else + magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost)); + magnitude = std::max(1.f, magnitude); + } + else + magnitude = 1; + + effect.mMagnMax = magnitude; + effect.mMagnMin = magnitude; + + ESM::EffectList effects; + effects.mList.push_back(effect); + + inflict(mCaster, mCaster, effects, ESM::RT_Self); + + return true; + } + +} diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp new file mode 100644 index 000000000..74dc490ea --- /dev/null +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -0,0 +1,62 @@ +#ifndef MWMECHANICS_SPELLSUCCESS_H +#define MWMECHANICS_SPELLSUCCESS_H + +#include "../mwworld/ptr.hpp" + +#include + +namespace MWMechanics +{ + class EffectKey; + + ESM::Skill::SkillEnum spellSchoolToSkill(int school); + + /** + * @param spell spell to cast + * @param actor calculate spell success chance for this actor (depends on actor's skills) + * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here + * @attention actor has to be an NPC and not a creature! + * @return success chance from 0 to 100 (in percent) + */ + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL); + + int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); + int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); + + /// @return >=100 for fully resisted. can also return negative value for damage amplification. + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL); + + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL); + + class CastSpell + { + private: + MWWorld::Ptr mCaster; + MWWorld::Ptr mTarget; + public: + bool mStack; + std::string mId; // ID of spell, potion, item etc + std::string mSourceName; // Display name for spell, potion, etc + Ogre::Vector3 mHitPosition; // Used for spawning area orb + + public: + CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + + bool cast (const ESM::Spell* spell); + bool cast (const MWWorld::Ptr& item); + bool cast (const ESM::Ingredient* ingredient); + bool cast (const ESM::Potion* potion); + + /// @note Auto detects if spell, ingredient or potion + bool cast (const std::string& id); + + void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); + + void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); + }; + +} + +#endif diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index e10dcdc93..21781c530 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -27,12 +27,21 @@ namespace MWMechanics void Spells::add (const std::string& spellId) { if (mSpells.find (spellId)==mSpells.end()) - mSpells.insert (std::make_pair (spellId, static_cast (std::rand()) / RAND_MAX)); + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + + std::vector random; + random.resize(spell->mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; + mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random)); + } } void Spells::remove (const std::string& spellId) { - TContainer::iterator iter = mSpells.find (spellId); + std::string lower = Misc::StringUtils::lowerCase(spellId); + TContainer::iterator iter = mSpells.find (lower); if (iter!=mSpells.end()) mSpells.erase (iter); @@ -43,6 +52,8 @@ namespace MWMechanics MagicEffects Spells::getMagicEffects() const { + // TODO: These are recalculated every frame, no need to do that + MagicEffects effects; for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) @@ -52,7 +63,14 @@ namespace MWMechanics 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) - effects.add (spell->mEffects, iter->second); + { + int i=0; + for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + { + effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * iter->second[i]); + ++i; + } + } } return effects; @@ -100,4 +118,83 @@ namespace MWMechanics return false; } + + void Spells::purgeCommonDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType == ESM::Spell::ST_Disease) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeBlightDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType == ESM::Spell::ST_Blight) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeCorprusDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (Misc::StringUtils::ciEqual(spell->mId, "corprus")) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeCurses() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType == ESM::Spell::ST_Curse) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TIterator it = begin(); it != 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; + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, "", magnitude); + } + } + } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index e00ac259f..cc239a650 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -4,6 +4,10 @@ #include #include +#include "../mwworld/ptr.hpp" + +#include "magiceffects.hpp" + namespace ESM { struct Spell; @@ -16,12 +20,12 @@ namespace MWMechanics /// \brief Spell list /// /// This class manages known spells as well as abilities, powers and permanent negative effects like - /// diseaes. + /// diseases. class Spells { public: - typedef std::map TContainer; // ID, normalised magnitude + typedef std::map > TContainer; // ID, normalised magnitudes typedef TContainer::const_iterator TIterator; private: @@ -31,10 +35,17 @@ namespace MWMechanics public: + void purgeCommonDisease(); + void purgeBlightDisease(); + void purgeCorprusDisease(); + void purgeCurses(); + TIterator begin() const; TIterator end() const; + bool hasSpell(const std::string& spell) { return mSpells.find(Misc::StringUtils::lowerCase(spell)) != mSpells.end(); } + void add (const std::string& spell); ///< Adding a spell that is already listed in *this is a no-op. @@ -57,6 +68,8 @@ namespace MWMechanics bool hasCommonDisease() const; bool hasBlightDisease() const; + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; }; } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp deleted file mode 100644 index 57c600df5..000000000 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef MWMECHANICS_SPELLSUCCESS_H -#define MWMECHANICS_SPELLSUCCESS_H - -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - -#include "../mwworld/ptr.hpp" -#include "../mwworld/class.hpp" -#include "../mwmechanics/creaturestats.hpp" - -#include "../mwworld/esmstore.hpp" - -#include "npcstats.hpp" - -namespace MWMechanics -{ - inline int spellSchoolToSkill(int school) - { - std::map schoolSkillMap; // maps spell school to skill id - schoolSkillMap[0] = 11; // alteration - schoolSkillMap[1] = 13; // conjuration - schoolSkillMap[3] = 12; // illusion - schoolSkillMap[2] = 10; // destruction - schoolSkillMap[4] = 14; // mysticism - schoolSkillMap[5] = 15; // restoration - assert(schoolSkillMap.find(school) != schoolSkillMap.end()); - return schoolSkillMap[school]; - } - - inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) - { - NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); - - // determine the spell's school - // this is always the school where the player's respective skill is the least advanced - // out of all the magic effects' schools - const std::vector& effects = spell->mEffects.mList; - int school = -1; - int skillLevel = -1; - for (std::vector::const_iterator it = effects.begin(); - it != effects.end(); ++it) - { - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(it->mEffectID); - int _school = effect->mData.mSchool; - int _skillLevel = stats.getSkill (spellSchoolToSkill(_school)).getModified(); - - if (school == -1) - { - school = _school; - skillLevel = _skillLevel; - } - else if (_skillLevel < skillLevel) - { - school = _school; - skillLevel = _skillLevel; - } - } - - return school; - } - - inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) - { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSchool(spell, actor); - } - - - // UESP wiki / Morrowind/Spells: - // Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2 - /** - * @param spell spell to cast - * @param actor calculate spell success chance for this actor (depends on actor's skills) - * @attention actor has to be an NPC and not a creature! - * @return success chance from 0 to 100 (in percent) - */ - inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor) - { - if (spell->mData.mFlags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells) - || spell->mData.mType == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day - return 100.0; - - if (spell->mEffects.mList.size() == 0) - return 0.0; - - NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); - CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); - - int skillLevel = stats.getSkill (getSpellSchool(spell, actor)).getModified(); - - // Sound magic effect (reduces spell casting chance) - int soundMagnitude = creatureStats.getMagicEffects().get (MWMechanics::EffectKey (48)).mMagnitude; - - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); - int luck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); - int currentFatigue = creatureStats.getFatigue().getCurrent(); - int maxFatigue = creatureStats.getFatigue().getModified(); - int spellCost = spell->mData.mCost; - - // There we go, all needed variables are there, lets go - float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0); - - chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100 - - return chance; - } - - inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor) - { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor); - } -} - -#endif diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 65d47c9c0..75ac6939a 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -162,14 +162,26 @@ namespace MWMechanics setCurrent (getCurrent()+diff); } - void setCurrent (const T& value) + void setCurrent (const T& value, bool allowDecreaseBelowZero = false) { - mCurrent = value; + if (value > mCurrent) + { + // increase + mCurrent = value; - if (mCurrent<0) + if (mCurrent > getModified()) + mCurrent = getModified(); + } + else if (value > 0 || allowDecreaseBelowZero) + { + // allowed decrease + mCurrent = value; + } + else if (mCurrent > 0) + { + // capped decrease mCurrent = 0; - else if (mCurrent>getModified()) - mCurrent = getModified(); + } } void setModifier (const T& modifier) @@ -193,6 +205,59 @@ namespace MWMechanics { return !(left==right); } + + class AttributeValue + { + int mBase; + int mModifier; + int mDamage; + + public: + AttributeValue() : mBase(0), mModifier(0), mDamage(0) {} + + int getModified() const { return std::max(0, mBase - mDamage + mModifier); } + int getBase() const { return mBase; } + int getModifier() const { return mModifier; } + + void setBase(int base) { mBase = std::max(0, base); } + void setModifier(int mod) { mModifier = mod; } + + void damage(int damage) { mDamage += damage; } + void restore(int amount) { mDamage -= std::min(mDamage, amount); } + int getDamage() const { return mDamage; } + }; + + class SkillValue : public AttributeValue + { + float mProgress; + public: + SkillValue() : mProgress(0) {} + float getProgress() const { return mProgress; } + void setProgress(float progress) { mProgress = progress; } + }; + + inline bool operator== (const AttributeValue& left, const AttributeValue& right) + { + return left.getBase() == right.getBase() + && left.getModifier() == right.getModifier() + && left.getDamage() == right.getDamage(); + } + inline bool operator!= (const AttributeValue& left, const AttributeValue& right) + { + return !(left == right); + } + + inline bool operator== (const SkillValue& left, const SkillValue& right) + { + return left.getBase() == right.getBase() + && left.getModifier() == right.getModifier() + && left.getDamage() == right.getDamage() + && left.getProgress() == right.getProgress(); + } + inline bool operator!= (const SkillValue& left, const SkillValue& right) + { + return !(left == right); + } } #endif diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp new file mode 100644 index 000000000..d911fd81b --- /dev/null +++ b/apps/openmw/mwmechanics/steering.cpp @@ -0,0 +1,43 @@ +#include "steering.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/ptr.hpp" + +#include "../mwbase/environment.hpp" + +#include "movement.hpp" + +namespace MWMechanics +{ + +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) +{ + Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); + Ogre::Radian diff (targetAngle - currentAngle); + if (diff >= Ogre::Degree(180)) + { + // Turning the other way would be a better idea + diff = diff-Ogre::Degree(360); + } + else if (diff <= Ogre::Degree(-180)) + { + diff = Ogre::Degree(360)-diff; + } + Ogre::Radian absDiff = Ogre::Math::Abs(diff); + + // The turning animation actually moves you slightly, so the angle will be wrong again. + // Use epsilon to prevent jerkiness. + const Ogre::Degree epsilon (0.5); + if (absDiff < epsilon) + return true; + + // Max. speed of 10 radian per sec + Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration(); + if (absDiff > limit) + diff = Ogre::Math::Sign(diff) * limit; + + actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians(); + return false; +} + +} diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp new file mode 100644 index 000000000..504dc3ac3 --- /dev/null +++ b/apps/openmw/mwmechanics/steering.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_MECHANICS_STEERING_H + +#include + +namespace MWWorld +{ +class Ptr; +} + +namespace MWMechanics +{ + +/// configure rotation settings for an actor to reach this target angle (eventually) +/// @return have we reached the target angle? +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle); + +} + +#endif diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp index f3ea38f44..eb3e5815e 100644 --- a/apps/openmw/mwrender/activatoranimation.hpp +++ b/apps/openmw/mwrender/activatoranimation.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_ACTIVATORANIMATION_H -#define _GAME_RENDER_ACTIVATORANIMATION_H +#ifndef GAME_RENDER_ACTIVATORANIMATION_H +#define GAME_RENDER_ACTIVATORANIMATION_H #include "animation.hpp" diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 566b6fa81..820ba8acc 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -68,18 +68,22 @@ void Actors::insertBegin(const MWWorld::Ptr &ptr) ptr.getRefData().setBaseNode(insert); } -void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv) +void Actors::insertNPC(const MWWorld::Ptr& ptr) { insertBegin(ptr); - NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), inv, RV_Actors); + NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); delete mAllActors[ptr]; mAllActors[ptr] = anim; mRendering->addWaterRippleEmitter (ptr); } -void Actors::insertCreature (const MWWorld::Ptr& ptr) +void Actors::insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields) { insertBegin(ptr); - CreatureAnimation* anim = new CreatureAnimation(ptr); + Animation* anim = NULL; + if (weaponsShields) + anim = new CreatureWeaponAnimation(ptr); + else + anim = new CreatureAnimation(ptr); delete mAllActors[ptr]; mAllActors[ptr] = anim; mRendering->addWaterRippleEmitter (ptr); @@ -123,7 +127,7 @@ bool Actors::deleteObject (const MWWorld::Ptr& ptr) return true; } -void Actors::removeCell(MWWorld::Ptr::CellStore* store) +void Actors::removeCell(MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();) { @@ -147,9 +151,12 @@ void Actors::removeCell(MWWorld::Ptr::CellStore* store) } } -void Actors::update (float duration) +void Actors::update (Ogre::Camera* camera) { - // Nothing to do + for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end(); ++iter) + { + iter->second->preRender(camera); + } } Animation* Actors::getAnimation(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index 4547db9ad..d5d6c52bb 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_ACTORS_H -#define _GAME_RENDER_ACTORS_H +#ifndef GAME_RENDER_ACTORS_H +#define GAME_RENDER_ACTORS_H #include @@ -39,15 +39,15 @@ namespace MWRender void setRootNode(Ogre::SceneNode* root); - void insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv); - void insertCreature (const MWWorld::Ptr& ptr); + void insertNPC(const MWWorld::Ptr& ptr); + void insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields); void insertActivator (const MWWorld::Ptr& ptr); bool deleteObject (const MWWorld::Ptr& ptr); ///< \return found? void removeCell(MWWorld::CellStore* store); - void update (float duration); + void update (Ogre::Camera* camera); /// Updates containing cell for object rendering data void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 545060fe3..44bba90d0 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" +#include + #include "../mwmechanics/character.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" @@ -24,10 +26,11 @@ #include "renderconst.hpp" + namespace MWRender { -Ogre::Real Animation::AnimationValue::getValue() const +Ogre::Real Animation::AnimationTime::getValue() const { AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName); if(iter != mAnimation->mStates.end()) @@ -35,53 +38,38 @@ Ogre::Real Animation::AnimationValue::getValue() const return 0.0f; } -void Animation::AnimationValue::setValue(Ogre::Real) +void Animation::AnimationTime::setValue(Ogre::Real) { } +Ogre::Real Animation::EffectAnimationTime::getValue() const +{ + return mTime; +} -void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects) +void Animation::EffectAnimationTime::setValue(Ogre::Real) { - for(size_t i = 0;i < objects.mLights.size();i++) - { - Ogre::Light *light = objects.mLights[i]; - // If parent is a scene node, it was created specifically for this light. Destroy it now. - if(light->isAttached() && !light->isParentTagPoint()) - sceneMgr->destroySceneNode(light->getParentSceneNode()); - sceneMgr->destroyLight(light); - } - for(size_t i = 0;i < objects.mParticles.size();i++) - sceneMgr->destroyParticleSystem(objects.mParticles[i]); - for(size_t i = 0;i < objects.mEntities.size();i++) - sceneMgr->destroyEntity(objects.mEntities[i]); - objects.mControllers.clear(); - objects.mLights.clear(); - objects.mParticles.clear(); - objects.mEntities.clear(); - objects.mSkelBase = NULL; } Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) : mPtr(ptr) - , mCamera(NULL) , mInsert(node) , mSkelBase(NULL) , mAccumRoot(NULL) , mNonAccumRoot(NULL) , mNonAccumCtrl(NULL) , mAccumulate(0.0f) - , mNullAnimationValuePtr(OGRE_NEW NullAnimationValue) + , mNullAnimationTimePtr(OGRE_NEW NullAnimationTime) { for(size_t i = 0;i < sNumGroups;i++) - mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this)); + mAnimationTimePtr[i].bind(OGRE_NEW AnimationTime(this)); } Animation::~Animation() { - mAnimSources.clear(); + mEffects.clear(); - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - destroyObjectList(sceneMgr, mObjectRoot); + mAnimSources.clear(); } @@ -90,7 +78,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) OgreAssert(mAnimSources.empty(), "Setting object root while animation sources are set!"); mSkelBase = NULL; - destroyObjectList(mInsert->getCreator(), mObjectRoot); + mObjectRoot.setNull(); if(model.empty()) return; @@ -111,11 +99,11 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); - if(mObjectRoot.mSkelBase) + if(mObjectRoot->mSkelBase) { - mSkelBase = mObjectRoot.mSkelBase; + mSkelBase = mObjectRoot->mSkelBase; - Ogre::AnimationStateSet *aset = mObjectRoot.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateSet *aset = mObjectRoot->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { @@ -126,7 +114,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) // Set the bones as manually controlled since we're applying the // transformations manually - Ogre::SkeletonInstance *skelinst = mObjectRoot.mSkelBase->getSkeleton(); + Ogre::SkeletonInstance *skelinst = mObjectRoot->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); @@ -147,15 +135,36 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) else mAttachedObjects.clear(); - for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) + for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) { - if(mObjectRoot.mControllers[i].getSource().isNull()) - mObjectRoot.mControllers[i].setSource(mAnimationValuePtr[0]); + if(mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); } } +struct AddGlow +{ + Ogre::Vector3* mColor; + NifOgre::MaterialControllerManager* mMaterialControllerMgr; + AddGlow(Ogre::Vector3* col, NifOgre::MaterialControllerManager* materialControllerMgr) + : mColor(col) + , mMaterialControllerMgr(materialControllerMgr) + {} + + void operator()(Ogre::Entity* entity) const + { + if (!entity->getNumSubEntities()) + return; + Ogre::MaterialPtr writableMaterial = mMaterialControllerMgr->getWritableMaterial(entity); + sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(writableMaterial->getName()); + + instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); + } +}; -class VisQueueSet { +class VisQueueSet +{ Ogre::uint32 mVisFlags; Ogre::uint8 mSolidQueue, mTransQueue; Ogre::Real mDist; @@ -175,6 +184,7 @@ public: for(unsigned int i = 0;i < numsubs;++i) { Ogre::SubEntity* subEnt = entity->getSubEntity(i); + sh::Factory::getInstance()._ensureMaterial(subEnt->getMaterial()->getName(), "Default"); subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? mTransQueue : mSolidQueue); } } @@ -189,12 +199,16 @@ public: } }; -void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist) +void Animation::setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) { - std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), + std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); - std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(), + std::for_each(objlist->mParticles.begin(), objlist->mParticles.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); + + if (enchantedGlow) + std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), + AddGlow(glowColor, &objlist->mMaterialControllerMgr)); } @@ -271,7 +285,7 @@ void Animation::addAnimSource(const std::string &model) } } - ctrls[i].setSource(mAnimationValuePtr[grp]); + ctrls[i].setSource(mAnimationTimePtr[grp]); grpctrls[grp].push_back(ctrls[i]); } } @@ -281,7 +295,7 @@ void Animation::clearAnimSources() mStates.clear(); for(size_t i = 0;i < sNumGroups;i++) - mAnimationValuePtr[i]->setAnimName(std::string()); + mAnimationTimePtr[i]->setAnimName(std::string()); mNonAccumCtrl = NULL; @@ -292,7 +306,7 @@ void Animation::clearAnimSources() } -void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light) +void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light) { const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback(); @@ -305,8 +319,8 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList if((light->mData.mFlags&ESM::Light::Negative)) color *= -1; - objlist.mLights.push_back(sceneMgr->createLight()); - Ogre::Light *olight = objlist.mLights.back(); + objlist->mLights.push_back(sceneMgr->createLight()); + Ogre::Light *olight = objlist->mLights.back(); olight->setDiffuseColour(color); Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); @@ -318,7 +332,7 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList (light->mData.mFlags&ESM::Light::PulseSlow) ? OEngine::Render::LT_PulseSlow : OEngine::Render::LT_Normal )); - objlist.mControllers.push_back(Ogre::Controller(src, dest, func)); + objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->mCell->isExterior()); bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ? @@ -344,14 +358,14 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList } // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, - if(objlist.mSkelBase && objlist.mSkelBase->getSkeleton()->hasBone("AttachLight")) - objlist.mSkelBase->attachObjectToBone("AttachLight", olight); + if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) + objlist->mSkelBase->attachObjectToBone("AttachLight", olight); else { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - for(size_t i = 0;i < objlist.mEntities.size();i++) + for(size_t i = 0;i < objlist->mEntities.size();i++) { - Ogre::Entity *ent = objlist.mEntities[i]; + Ogre::Entity *ent = objlist->mEntities[i]; bounds.merge(ent->getBoundingBox()); } @@ -373,7 +387,6 @@ Ogre::Node *Animation::getNode(const std::string &name) return NULL; } - NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname) { NifOgre::TextKeyMap::const_iterator iter(keys.begin()); @@ -518,12 +531,6 @@ static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bo bone->setScale(Ogre::Vector3::UNIT_SCALE); } } - else - { - // 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()) @@ -570,7 +577,11 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s const std::string stoptag = groupname+": "+stop; NifOgre::TextKeyMap::const_iterator stopkey(groupstart); - while(stopkey != keys.end() && stopkey->second != stoptag) + while(stopkey != keys.end() + // We have to ignore extra garbage at the end. + // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". + // Why, just why? :( + && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) stopkey++; if(stopkey == keys.end()) return false; @@ -602,6 +613,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s return true; } +void split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key) { @@ -616,14 +634,29 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co } if(evt.compare(0, 10, "soundgen: ") == 0) { - std::string sound = MWWorld::Class::get(mPtr).getSoundIdFromSndGen(mPtr, evt.substr(10)); + std::string soundgen = evt.substr(10); + + // The event can optionally contain volume and pitch modifiers + float volume=1.f, pitch=1.f; + if (soundgen.find(" ") != std::string::npos) + { + std::vector tokens; + split(soundgen, ' ', tokens); + soundgen = tokens[0]; + if (tokens.size() >= 2) + volume = Ogre::StringConverter::parseReal(tokens[1]); + if (tokens.size() >= 3) + pitch = Ogre::StringConverter::parseReal(tokens[2]); + } + + std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx; if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0) type = MWBase::SoundManager::Play_TypeFoot; - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f, type); + sndMgr->playSound3D(mPtr, sound, volume, pitch, type); } return; } @@ -646,16 +679,50 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co else if(evt.compare(off, len, "unequip detach") == 0) showWeapons(false); else if(evt.compare(off, len, "chop hit") == 0) - MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Chop); + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); else if(evt.compare(off, len, "slash hit") == 0) - MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Slash); + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); else if(evt.compare(off, len, "thrust hit") == 0) - MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust); + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) - MWWorld::Class::get(mPtr).hit(mPtr); + { + if (groupname == "attack1") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); + else if (groupname == "attack2") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); + else if (groupname == "attack3") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); + else + mPtr.getClass().hit(mPtr); + } + else if (evt.compare(off, len, "shoot attach") == 0) + attachArrow(); + else if (evt.compare(off, len, "shoot release") == 0) + releaseArrow(); + else if (evt.compare(off, len, "shoot follow attach") == 0) + attachArrow(); + + else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") + MWBase::Environment::get().getWorld()->castSpell(mPtr); + + else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) + mPtr.getClass().block(mPtr); } - +void Animation::changeGroups(const std::string &groupname, int groups) +{ + AnimStateMap::iterator stateiter = mStates.begin(); + stateiter = mStates.find(groupname); + if(stateiter != mStates.end()) + { + if(stateiter->second.mGroups != groups) + { + stateiter->second.mGroups = groups; + resetActiveGroups(); + } + return; + } +} void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops) { if(!mSkelBase || mAnimSources.empty()) @@ -759,7 +826,7 @@ void Animation::resetActiveGroups() active = state; } - mAnimationValuePtr[grp]->setAnimName((active == mStates.end()) ? + mAnimationTimePtr[grp]->setAnimName((active == mStates.end()) ? std::string() : active->first); } mNonAccumCtrl = NULL; @@ -767,7 +834,7 @@ void Animation::resetActiveGroups() if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f)) return; - AnimStateMap::const_iterator state = mStates.find(mAnimationValuePtr[0]->getAnimName()); + AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName()); if(state == mStates.end()) return; @@ -808,6 +875,27 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp return true; } +float Animation::getStartTime(const std::string &groupname) const +{ + AnimSourceList::const_iterator iter(mAnimSources.begin()); + for(;iter != mAnimSources.end();iter++) + { + const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + return -1.f; +} + +float Animation::getCurrentTime(const std::string &groupname) const +{ + AnimStateMap::const_iterator iter = mStates.find(groupname); + if(iter == mStates.end()) + return -1.f; + + return iter->second.mTime; +} void Animation::disable(const std::string &groupname) { @@ -821,7 +909,6 @@ void Animation::disable(const std::string &groupname) Ogre::Vector3 Animation::runAnimation(float duration) { Ogre::Vector3 movement(0.0f); - AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { @@ -840,13 +927,13 @@ Ogre::Vector3 Animation::runAnimation(float duration) targetTime = state.mTime + timepassed; if(textkey == textkeys.end() || textkey->first > targetTime) { - if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName()) + if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) updatePosition(state.mTime, targetTime, movement); state.mTime = std::min(targetTime, state.mStopTime); } else { - if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName()) + if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) updatePosition(state.mTime, textkey->first, movement); state.mTime = textkey->first; } @@ -891,13 +978,13 @@ Ogre::Vector3 Animation::runAnimation(float duration) ++stateiter; } - for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) - mObjectRoot.mControllers[i].update(); + for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) + mObjectRoot->mControllers[i].update(); // Apply group controllers for(size_t grp = 0;grp < sNumGroups;grp++) { - const std::string &name = mAnimationValuePtr[grp]->getAnimName(); + const std::string &name = mAnimationTimePtr[grp]->getAnimName(); if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end()) { const Ogre::SharedPtr &src = stateiter->second.mSource; @@ -913,6 +1000,8 @@ Ogre::Vector3 Animation::runAnimation(float duration) mSkelBase->getAllAnimationStates()->_notifyDirty(); } + updateEffects(duration); + return movement; } @@ -933,7 +1022,7 @@ public: void Animation::enableLights(bool enable) { - std::for_each(mObjectRoot.mLights.begin(), mObjectRoot.mLights.end(), ToggleLight(enable)); + std::for_each(mObjectRoot->mLights.begin(), mObjectRoot->mLights.end(), ToggleLight(enable)); } @@ -952,7 +1041,7 @@ public: Ogre::AxisAlignedBox Animation::getWorldBounds() { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - std::for_each(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), MergeBounds(&bounds)); + std::for_each(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), MergeBounds(&bounds)); return bounds; } @@ -977,6 +1066,165 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } +bool Animation::allowSwitchViewMode() const +{ + for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) + { + if(stateiter->second.mPriority > MWMechanics::Priority_Movement + && stateiter->second.mPriority < MWMechanics::Priority_Torch) + return false; + } + return true; +} + +void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) +{ + // Early out if we already have this effect + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) + return; + + // fix texture extension to .dds + if (texture.size() > 4) + { + texture[texture.size()-3] = 'd'; + texture[texture.size()-2] = 'd'; + texture[texture.size()-1] = 's'; + } + + EffectParams params; + params.mModelName = model; + if (bonename.empty()) + params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + else + params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + + // TODO: turn off shadow casting + setRenderProperties(params.mObjects, RV_Misc, + RQG_Main, RQG_Alpha, 0.f, false, NULL); + + params.mLoop = loop; + params.mEffectId = effectId; + params.mBoneName = bonename; + + for(size_t i = 0;i < params.mObjects->mControllers.size();i++) + { + if(params.mObjects->mControllers[i].getSource().isNull()) + params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); + } + + if (!texture.empty()) + { + for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) + { + Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; + + Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); + + for (int t=0; tgetNumTechniques(); ++t) + { + Ogre::Technique* tech = mat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) + { + Ogre::Pass* pass = tech->getPass(p); + for (int tex=0; texgetNumTextureUnitStates(); ++tex) + { + Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); + tus->setTextureName("textures\\" + texture); + } + } + } + } + } + + mEffects.push_back(params); +} + +void Animation::removeEffect(int effectId) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mEffectId == effectId) + { + mEffects.erase(it); + return; + } + } +} + +void Animation::getLoopingEffects(std::vector &out) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mLoop) + out.push_back(it->mEffectId); + } +} + +void Animation::updateEffects(float duration) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + NifOgre::ObjectScenePtr objects = it->mObjects; + for(size_t i = 0; i < objects->mControllers.size() ;i++) + { + EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); + if (value) + value->addTime(duration); + + objects->mControllers[i].update(); + } + + if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) + { + if (it->mLoop) + { + // Start from the beginning again; carry over the remainder + float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength; + for(size_t i = 0; i < objects->mControllers.size() ;i++) + { + EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); + if (value) + value->resetTime(remainder); + } + } + else + { + it = mEffects.erase(it); + continue; + } + } + ++it; + } +} + +void Animation::preRender(Ogre::Camera *camera) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + NifOgre::ObjectScenePtr objects = it->mObjects; + objects->rotateBillboardNodes(camera); + } + mObjectRoot->rotateBillboardNodes(camera); +} + +// TODO: Should not be here +Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) +{ + Ogre::Vector3 result(1,1,1); + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + return result; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + assert (enchantment->mEffects.mList.size()); + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantment->mEffects.mList.front().mEffectID); + result.x = magicEffect->mData.mRed / 255.f; + result.y = magicEffect->mData.mGreen / 255.f; + result.z = magicEffect->mData.mBlue / 255.f; + return result; +} + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) @@ -993,9 +1241,10 @@ ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &mod small = false; float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; + Ogre::Vector3 col = getEnchantmentColor(ptr); setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, - RQG_Main, RQG_Alpha, dist); + RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); } void ObjectAnimation::addLight(const ESM::Light *light) @@ -1011,6 +1260,7 @@ public: unsigned int numsubs = ent->getNumSubEntities(); for(unsigned int i = 0;i < numsubs;++i) { + sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(i)->getMaterial()->getName(), "Default"); if(ent->getSubEntity(i)->getMaterial()->isTransparent()) return true; } @@ -1020,19 +1270,22 @@ public: bool ObjectAnimation::canBatch() const { - if(!mObjectRoot.mParticles.empty() || !mObjectRoot.mLights.empty() || !mObjectRoot.mControllers.empty()) + if(!mObjectRoot->mParticles.empty() || !mObjectRoot->mLights.empty() || !mObjectRoot->mControllers.empty()) + return false; + if (!mObjectRoot->mBillboardNodes.empty()) return false; - return std::find_if(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), - FindEntityTransparency()) == mObjectRoot.mEntities.end(); + return std::find_if(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), + FindEntityTransparency()) == mObjectRoot->mEntities.end(); } void ObjectAnimation::fillBatch(Ogre::StaticGeometry *sg) { - std::vector::reverse_iterator iter = mObjectRoot.mEntities.rbegin(); - for(;iter != mObjectRoot.mEntities.rend();++iter) + std::vector::reverse_iterator iter = mObjectRoot->mEntities.rbegin(); + for(;iter != mObjectRoot->mEntities.rend();++iter) { Ogre::Node *node = (*iter)->getParentNode(); - sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); + if ((*iter)->isVisible()) + sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2215fc582..c0cb18010 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_ANIMATION_H -#define _GAME_RENDER_ANIMATION_H +#ifndef GAME_RENDER_ANIMATION_H +#define GAME_RENDER_ANIMATION_H #include #include @@ -32,14 +32,14 @@ protected: /* This is the number of *discrete* groups. */ static const size_t sNumGroups = 4; - class AnimationValue : public Ogre::ControllerValue + class AnimationTime : public Ogre::ControllerValue { private: Animation *mAnimation; std::string mAnimationName; public: - AnimationValue(Animation *anim) + AnimationTime(Animation *anim) : mAnimation(anim) { } @@ -52,8 +52,22 @@ protected: virtual void setValue(Ogre::Real value); }; + class EffectAnimationTime : public Ogre::ControllerValue + { + private: + float mTime; + public: + EffectAnimationTime() : mTime(0) { } + void addTime(float time) { mTime += time; } + void resetTime(float value) { mTime = value; } + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value); + }; + - class NullAnimationValue : public Ogre::ControllerValue + + class NullAnimationTime : public Ogre::ControllerValue { public: virtual Ogre::Real getValue() const @@ -95,12 +109,22 @@ protected: typedef std::map ObjectAttachMap; + struct EffectParams + { + std::string mModelName; // Just here so we don't add the same effect twice + NifOgre::ObjectScenePtr mObjects; + int mEffectId; + bool mLoop; + std::string mBoneName; + }; + + std::vector mEffects; + MWWorld::Ptr mPtr; - Camera *mCamera; Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; - NifOgre::ObjectList mObjectRoot; + NifOgre::ObjectScenePtr mObjectRoot; AnimSourceList mAnimSources; Ogre::Node *mAccumRoot; Ogre::Node *mNonAccumRoot; @@ -109,11 +133,12 @@ protected: AnimStateMap mStates; - Ogre::SharedPtr mAnimationValuePtr[sNumGroups]; - Ogre::SharedPtr mNullAnimationValuePtr; + Ogre::SharedPtr mAnimationTimePtr[sNumGroups]; + Ogre::SharedPtr mNullAnimationTimePtr; ObjectAttachMap mAttachedObjects; + /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); @@ -161,18 +186,46 @@ protected: void addAnimSource(const std::string &model); /** Adds an additional light to the given object list using the specified ESM record. */ - void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light); - - static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); - - static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist=0.0f); + void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light); void clearAnimSources(); + // TODO: Should not be here + Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item); + public: + // FIXME: Move outside of this class + static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, + Ogre::uint8 transqueue, Ogre::Real dist=0.0f, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + + Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); + /** + * @brief Add an effect mesh attached to a bone or the insert scene node + * @param model + * @param effectId An ID for this effect. Note that adding the same ID again won't add another effect. + * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, + * you need to remove it manually using removeEffect when the effect should end. + * @param bonename Bone to attach to, or empty string to use the scene node instead + * @param texture override the texture specified in the model's materials + * @note Will not add an effect twice. + */ + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); + void removeEffect (int effectId); + void getLoopingEffects (std::vector& out); + + /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) + virtual void preRender (Ogre::Camera* camera); + + virtual void setAlpha(float alpha) {} +private: + void updateEffects(float duration); + + +public: void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(const std::string &anim); @@ -206,6 +259,9 @@ public: /** Returns true if the named animation group is playing. */ bool isPlaying(const std::string &groupname) const; + //Checks if playing any animation which shouldn't be stopped when switching camera view modes + bool allowSwitchViewMode() const; + /** Gets info about the given animation group. * \param groupname Animation group to check. * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. @@ -214,25 +270,37 @@ public: */ bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; + /// Get the absolute position in the animation track of the first text key with the given group. + float getStartTime(const std::string &groupname) const; + + /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. + float getCurrentTime(const std::string& groupname) const; + /** Disables the specified animation group; * \param groupname Animation group to disable. */ void disable(const std::string &groupname); + void changeGroups(const std::string &groupname, int group); + + virtual void setWeaponGroup(const std::string& group) {} /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(const std::string &groupname) const; + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) {} + virtual Ogre::Vector3 runAnimation(float duration); virtual void showWeapons(bool showWeapon); - + virtual void showCarriedLeft(bool show) {} + virtual void attachArrow() {} + virtual void releaseArrow() {} void enableLights(bool enable); Ogre::AxisAlignedBox getWorldBounds(); - void setCamera(Camera *cam) - { mCamera = cam; } - Ogre::Node *getNode(const std::string &name); // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9af3987a8..9ae9c5878 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -19,23 +19,27 @@ namespace MWRender Camera::Camera (Ogre::Camera *camera) : mCamera(camera), mCameraNode(NULL), + mAnimation(NULL), mFirstPersonView(true), mPreviewMode(false), mFreeLook(true), - mHeight(128.f), - mCameraDistance(300.f), - mDistanceAdjusted(false), - mAnimation(NULL), mNearest(30.f), mFurthest(800.f), mIsNearest(false), - mIsFurthest(false) + mIsFurthest(false), + mHeight(128.f), + mCameraDistance(300.f), + mDistanceAdjusted(false), + mVanityToggleQueued(false), + mViewModeToggleQueued(false) { mVanity.enabled = false; mVanity.allowed = true; + mPreviewCam.pitch = 0.f; mPreviewCam.yaw = 0.f; mPreviewCam.offset = 400.f; + mMainCam.pitch = 0.f; mMainCam.yaw = 0.f; mMainCam.offset = 400.f; } @@ -103,6 +107,23 @@ namespace MWRender void Camera::update(float duration, bool paused) { + if (mAnimation->allowSwitchViewMode()) + { + // Now process the view changes we queued earlier + if (mVanityToggleQueued) + { + toggleVanityMode(!mVanity.enabled); + mVanityToggleQueued = false; + } + if (mViewModeToggleQueued) + { + + togglePreviewMode(false); + toggleViewMode(); + mViewModeToggleQueued = false; + } + } + updateListener(); if (paused) return; @@ -121,6 +142,14 @@ namespace MWRender void Camera::toggleViewMode() { + // Changing the view will stop all playing animations, so if we are playing + // anything important, queue the view change for later + if (!mAnimation->allowSwitchViewMode()) + { + mViewModeToggleQueued = true; + return; + } + mFirstPersonView = !mFirstPersonView; processViewChange(); @@ -140,6 +169,14 @@ namespace MWRender bool Camera::toggleVanityMode(bool enable) { + // Changing the view will stop all playing animations, so if we are playing + // anything important, queue the view change for later + if (!mPreviewMode) + { + mVanityToggleQueued = true; + return false; + } + if(!mVanity.allowed && enable) return false; @@ -168,6 +205,9 @@ namespace MWRender void Camera::togglePreviewMode(bool enable) { + if (mFirstPersonView && !mAnimation->allowSwitchViewMode()) + return; + if(mPreviewMode == enable) return; @@ -184,13 +224,12 @@ namespace MWRender } mCamera->setPosition(0.f, 0.f, offset); - rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false); } - void Camera::setSneakOffset() + void Camera::setSneakOffset(float offset) { if(mAnimation) - mAnimation->addFirstPersonOffset(Ogre::Vector3(0.f, 0.f, -9.8f)); + mAnimation->addFirstPersonOffset(Ogre::Vector3(0.f, 0.f, -offset)); } float Camera::getYaw() @@ -241,6 +280,11 @@ namespace MWRender } } + float Camera::getCameraDistance() const + { + return mCamera->getPosition().z; + } + void Camera::setCameraDistance(float dist, bool adjust, bool override) { if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) @@ -294,11 +338,9 @@ namespace MWRender if(mAnimation && mAnimation != anim) { mAnimation->setViewMode(NpcAnimation::VM_Normal); - mAnimation->setCamera(NULL); mAnimation->detachObjectFromBone(mCamera); } mAnimation = anim; - mAnimation->setCamera(this); processViewChange(); } @@ -314,11 +356,12 @@ namespace MWRender Ogre::TagPoint *tag = mAnimation->attachObjectToBone("Head", mCamera); tag->setInheritOrientation(false); } - else + else { mAnimation->setViewMode(NpcAnimation::VM_Normal); mCameraNode->attachObject(mCamera); } + rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false); } void Camera::getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index baf2f3685..808f817cf 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -47,6 +47,9 @@ namespace MWRender bool mDistanceAdjusted; + bool mVanityToggleQueued; + bool mViewModeToggleQueued; + /// Updates sound manager listener data void updateListener(); @@ -77,13 +80,14 @@ namespace MWRender bool toggleVanityMode(bool enable); void allowVanityMode(bool allow); + /// @note this may be ignored if an important animation is currently playing void togglePreviewMode(bool enable); /// \brief Lowers the camera for sneak. /// As animation is tied to the camera, this needs /// to be set each frame after the animation is /// applied. - void setSneakOffset(); + void setSneakOffset(float offset); bool isFirstPerson() const { return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); } @@ -101,6 +105,8 @@ namespace MWRender /// Restore default camera distance for current mode. void setCameraDistance(); + float getCameraDistance() const; + void setAnimation(NpcAnimation *anim); /// Stores focal and camera world positions in passed arguments diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index e42218bc2..280828652 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -4,13 +4,13 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -35,7 +35,7 @@ namespace MWRender , mCamera(NULL) , mNode(NULL) { - + mCharacter.mCell = NULL; } void CharacterPreview::onSetup() @@ -70,8 +70,8 @@ namespace MWRender mNode = renderRoot->createChildSceneNode(); - mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter), - 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + mAnimation = new NpcAnimation(mCharacter, mNode, + 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); Ogre::Vector3 scale = mNode->getScale(); mCamera->setPosition(mPosition * scale); @@ -101,7 +101,6 @@ namespace MWRender { if (mSceneMgr) { - //Ogre::TextureManager::getSingleton().remove(mName); mSceneMgr->destroyAllCameras(); delete mAnimation; Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); @@ -112,10 +111,8 @@ namespace MWRender { assert(mAnimation); delete mAnimation; - mAnimation = 0; - - mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter), - 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + mAnimation = new NpcAnimation(mCharacter, mNode, + 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); float scale=1.f; MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale); @@ -143,7 +140,9 @@ namespace MWRender void InventoryPreview::update(int sizeX, int sizeY) { - MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter); + mAnimation->updateParts(); + + MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname; if(iter == inv.end()) @@ -161,7 +160,10 @@ namespace MWRender if(type == ESM::Weapon::ShortBladeOneHand || type == ESM::Weapon::LongBladeOneHand || type == ESM::Weapon::BluntOneHand || - type == ESM::Weapon::AxeOneHand) + type == ESM::Weapon::AxeOneHand || + type == ESM::Weapon::MarksmanThrown || + type == ESM::Weapon::MarksmanCrossbow || + type == ESM::Weapon::MarksmanBow) groupname = "inventoryweapononehand"; else if(type == ESM::Weapon::LongBladeTwoHand || type == ESM::Weapon::BluntTwoClose || @@ -177,11 +179,8 @@ namespace MWRender groupname = "inventoryhandtohand"; } - if(groupname != mCurrentAnimGroup) - { - mCurrentAnimGroup = groupname; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); - } + mCurrentAnimGroup = groupname; + mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) @@ -193,7 +192,6 @@ namespace MWRender else if(mAnimation->getInfo("torch")) mAnimation->disable("torch"); - mAnimation->updateParts(true); mAnimation->runAnimation(0.0f); mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); @@ -224,12 +222,12 @@ namespace MWRender // -------------------------------------------------------------------------------------------------- RaceSelectionPreview::RaceSelectionPreview() - : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayer().getPlayer(), + : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayerPtr(), 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 6, -35), Ogre::Vector3(0,125,0)) , mRef(&mBase) { mBase = *mCharacter.get()->mBase; - mCharacter = MWWorld::Ptr(&mRef, mCharacter.getCell()); + mCharacter = MWWorld::Ptr(&mRef, NULL); } void RaceSelectionPreview::update(float angle) diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index b2dfc9679..cd30cdf46 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -6,8 +6,6 @@ #include -#include "externalrendering.hpp" - #include "../mwworld/ptr.hpp" namespace OEngine diff --git a/apps/openmw/mwrender/compositors.cpp b/apps/openmw/mwrender/compositors.cpp deleted file mode 100644 index b1c98a306..000000000 --- a/apps/openmw/mwrender/compositors.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "compositors.hpp" - -#include -#include -#include -#include - -using namespace MWRender; - -Compositors::Compositors(Ogre::Viewport* vp) : - mViewport(vp) - , mEnabled(true) -{ -} - -Compositors::~Compositors() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); -} - -void Compositors::setEnabled (const bool enabled) -{ - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, it->first, enabled && it->second.first); - } - mEnabled = enabled; -} - -void Compositors::recreate() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); - - CompositorMap temp = mCompositors; - mCompositors.clear(); - - for (CompositorMap::iterator it=temp.begin(); - it != temp.end(); ++it) - { - addCompositor(it->first, it->second.second); - setCompositorEnabled(it->first, mEnabled && it->second.first); - } -} - -void Compositors::addCompositor (const std::string& name, const int priority) -{ - int id = 0; - - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - if (it->second.second > priority) - break; - ++id; - } - Ogre::CompositorManager::getSingleton().addCompositor (mViewport, name, id); - - mCompositors[name] = std::make_pair(false, priority); -} - -void Compositors::setCompositorEnabled (const std::string& name, const bool enabled) -{ - mCompositors[name].first = enabled; - Ogre::CompositorManager::getSingleton().setCompositorEnabled (mViewport, name, enabled && mEnabled); -} - -void Compositors::removeAll() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); - - mCompositors.clear(); -} - -bool Compositors::anyCompositorEnabled() -{ - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - if (it->second.first && mEnabled) - return true; - } - return false; -} - -void Compositors::countTrianglesBatches(unsigned int &triangles, unsigned int &batches) -{ - triangles = 0; - batches = 0; - - Ogre::CompositorInstance* c = NULL; - Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain (mViewport); - // accumulate tris & batches from all compositors with all their render targets - for (unsigned int i=0; i < chain->getNumCompositors(); ++i) - { - if (chain->getCompositor(i)->getEnabled()) - { - c = chain->getCompositor(i); - for (unsigned int j = 0; j < c->getTechnique()->getNumTargetPasses(); ++j) - { - std::string textureName = c->getTechnique()->getTargetPass(j)->getOutputName(); - Ogre::RenderTarget* rt = c->getRenderTarget(textureName); - triangles += rt->getTriangleCount(); - batches += rt->getBatchCount(); - } - } - } -} diff --git a/apps/openmw/mwrender/compositors.hpp b/apps/openmw/mwrender/compositors.hpp deleted file mode 100644 index e5dd7503c..000000000 --- a/apps/openmw/mwrender/compositors.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef GAME_MWRENDER_COMPOSITORS_H -#define GAME_MWRENDER_COMPOSITORS_H - -#include -#include - -namespace Ogre -{ - class Viewport; -} - -namespace MWRender -{ - typedef std::map < std::string, std::pair > CompositorMap; - - /// \brief Manages a set of compositors for one viewport - class Compositors - { - public: - Compositors(Ogre::Viewport* vp); - virtual ~Compositors(); - - /** - * enable or disable all compositors globally - */ - void setEnabled (const bool enabled); - - void setViewport(Ogre::Viewport* vp) { mViewport = vp; } - - /// recreate compositors (call this after viewport size changes) - void recreate(); - - bool toggle() { setEnabled(!mEnabled); return mEnabled; } - - /** - * enable or disable a specific compositor - * @note enable has no effect if all compositors are globally disabled - */ - void setCompositorEnabled (const std::string& name, const bool enabled); - - /** - * @param name of compositor - * @param priority, lower number will be first in the chain - */ - void addCompositor (const std::string& name, const int priority); - - bool anyCompositorEnabled(); - - void countTrianglesBatches(unsigned int &triangles, unsigned int &batches); - - void removeAll (); - - protected: - /// maps compositor name to its "enabled" state - CompositorMap mCompositors; - - bool mEnabled; - - Ogre::Viewport* mViewport; - }; - -} - -#endif diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index c3ad512dd..e2aa9a2b8 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -1,32 +1,144 @@ #include "creatureanimation.hpp" +#include +#include +#include + #include "renderconst.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" + namespace MWRender { -CreatureAnimation::~CreatureAnimation() -{ -} CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr) : Animation(ptr, ptr.getRefData().getBaseNode()) { MWWorld::LiveCellRef *ref = mPtr.get(); - assert (ref->mBase != NULL); - if(!ref->mBase->mModel.empty()) + std::string model = ptr.getClass().getModel(ptr); + if(!model.empty()) { - std::string model = "meshes\\"+ref->mBase->mModel; + setObjectRoot(model, false); + setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + if((ref->mBase->mFlags&ESM::Creature::Bipedal)) + addAnimSource("meshes\\base_anim.nif"); + addAnimSource(model); + } +} + + +CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) + : Animation(ptr, ptr.getRefData().getBaseNode()) + , mShowWeapons(false) + , mShowCarriedLeft(false) +{ + MWWorld::LiveCellRef *ref = mPtr.get(); + + std::string model = ptr.getClass().getModel(ptr); + if(!model.empty()) + { setObjectRoot(model, false); setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); - if((ref->mBase->mFlags&ESM::Creature::Biped)) + if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\base_anim.nif"); addAnimSource(model); + + mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); + + updateParts(); + } +} + +void CreatureWeaponAnimation::showWeapons(bool showWeapon) +{ + if (showWeapon != mShowWeapons) + { + mShowWeapons = showWeapon; + updateParts(); + } +} + +void CreatureWeaponAnimation::showCarriedLeft(bool show) +{ + if (show != mShowCarriedLeft) + { + mShowCarriedLeft = show; + updateParts(); + } +} + +void CreatureWeaponAnimation::updateParts() +{ + mWeapon.setNull(); + mShield.setNull(); + + if (mShowWeapons) + updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight); + if (mShowCarriedLeft) + updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft); +} + +void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slot) +{ + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator it = inv.getSlot(slot); + + if (it == inv.end()) + { + scene.setNull(); + return; + } + MWWorld::Ptr item = *it; + + std::string bonename; + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + bonename = "Weapon Bone"; + else + bonename = "Shield Bone"; + + scene = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, item.getClass().getModel(item)); + Ogre::Vector3 glowColor = getEnchantmentColor(item); + + setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, + !item.getClass().getEnchantment(item).empty(), &glowColor); + + if(scene->mSkelBase) + { + Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton(); + if(scene->mSkelBase->isParentTagPoint()) + { + Ogre::Node *root = scene->mSkelBase->getParentNode(); + if(skel->hasBone("BoneOffset")) + { + Ogre::Bone *offset = skel->getBone("BoneOffset"); + + root->translate(offset->getPosition()); + + // It appears that the BoneOffset rotation is completely bogus, at least for light models. + //root->rotate(offset->getOrientation()); + root->pitch(Ogre::Degree(-90.0f)); + + root->scale(offset->getScale()); + root->setInitialState(); + } + } + updateSkeletonInstance(mSkelBase->getSkeleton(), skel); + } + + // TODO: + // type == ESM::PRT_Weapon should get an animation source based on the current offset + // of the weapon attack animation (from its beginning, or start marker?) + std::vector >::iterator ctrl(scene->mControllers.begin()); + for(;ctrl != scene->mControllers.end();ctrl++) + { + if(ctrl->getSource().isNull()) + ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); } } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 0c277d198..37826673d 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -1,7 +1,8 @@ -#ifndef _GAME_RENDER_CREATUREANIMATION_H -#define _GAME_RENDER_CREATUREANIMATION_H +#ifndef GAME_RENDER_CREATUREANIMATION_H +#define GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" +#include "../mwworld/inventorystore.hpp" namespace MWWorld { @@ -14,7 +15,32 @@ namespace MWRender { public: CreatureAnimation(const MWWorld::Ptr& ptr); - virtual ~CreatureAnimation(); + virtual ~CreatureAnimation() {} + }; + + // For creatures with weapons and shields + // Animation is already virtual anyway, so might as well make a separate class. + // Most creatures don't need weapons/shields, so this will save some memory. + class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener + { + public: + CreatureWeaponAnimation(const MWWorld::Ptr& ptr); + virtual ~CreatureWeaponAnimation() {} + + virtual void equipmentChanged() { updateParts(); } + + virtual void showWeapons(bool showWeapon); + virtual void showCarriedLeft(bool show); + + void updateParts(); + + void updatePart(NifOgre::ObjectScenePtr& scene, int slot); + + private: + NifOgre::ObjectScenePtr mWeapon; + NifOgre::ObjectScenePtr mShield; + bool mShowWeapons; + bool mShowCarriedLeft; }; } diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index b318c2d56..2b61e109b 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -185,14 +185,14 @@ bool Debugging::toggleRenderMode (int mode){ return false; } -void Debugging::cellAdded(MWWorld::Ptr::CellStore *store) +void Debugging::cellAdded(MWWorld::CellStore *store) { mActiveCells.push_back(store); if (mPathgridEnabled) enableCellPathgrid(store); } -void Debugging::cellRemoved(MWWorld::Ptr::CellStore *store) +void Debugging::cellRemoved(MWWorld::CellStore *store) { mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); if (mPathgridEnabled) @@ -227,7 +227,7 @@ void Debugging::togglePathgrid() } } -void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store) +void Debugging::enableCellPathgrid(MWWorld::CellStore *store) { const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*store->mCell); @@ -254,7 +254,7 @@ void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store) } } -void Debugging::disableCellPathgrid(MWWorld::Ptr::CellStore *store) +void Debugging::disableCellPathgrid(MWWorld::CellStore *store) { if (store->mCell->isExterior()) { diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp index 4a574017c..39be34cb0 100644 --- a/apps/openmw/mwrender/debugging.hpp +++ b/apps/openmw/mwrender/debugging.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_MWSCENE_H -#define _GAME_RENDER_MWSCENE_H +#ifndef GAME_RENDER_MWSCENE_H +#define GAME_RENDER_MWSCENE_H #include #include diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp new file mode 100644 index 000000000..7d41525b7 --- /dev/null +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -0,0 +1,119 @@ +#include "effectmanager.hpp" + +#include +#include + +#include "animation.hpp" +#include "renderconst.hpp" + +namespace MWRender +{ + +class EffectAnimationTime : public Ogre::ControllerValue +{ +private: + float mTime; +public: + EffectAnimationTime() : mTime(0) { } + void addTime(float time) { mTime += time; } + + virtual Ogre::Real getValue() const { return mTime; } + virtual void setValue(Ogre::Real value) {} +}; + +EffectManager::EffectManager(Ogre::SceneManager *sceneMgr) + : mSceneMgr(sceneMgr) +{ +} + +void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition, float scale) +{ + Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition); + sceneNode->setScale(scale,scale,scale); + + // fix texture extension to .dds + if (textureOverride.size() > 4) + { + textureOverride[textureOverride.size()-3] = 'd'; + textureOverride[textureOverride.size()-2] = 'd'; + textureOverride[textureOverride.size()-1] = 's'; + } + + + NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model); + + // TODO: turn off shadow casting + MWRender::Animation::setRenderProperties(scene, RV_Misc, + RQG_Main, RQG_Alpha, 0.f, false, NULL); + + for(size_t i = 0;i < scene->mControllers.size();i++) + { + if(scene->mControllers[i].getSource().isNull()) + scene->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); + } + + if (!textureOverride.empty()) + { + for(size_t i = 0;i < scene->mParticles.size(); ++i) + { + Ogre::ParticleSystem* partSys = scene->mParticles[i]; + + Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(partSys); + + for (int t=0; tgetNumTechniques(); ++t) + { + Ogre::Technique* tech = mat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) + { + Ogre::Pass* pass = tech->getPass(p); + for (int tex=0; texgetNumTextureUnitStates(); ++tex) + { + Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); + tus->setTextureName("textures\\" + textureOverride); + } + } + } + } + } + + mEffects.push_back(std::make_pair(sceneNode, scene)); +} + +void EffectManager::update(float dt, Ogre::Camera* camera) +{ + for (std::vector >::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + NifOgre::ObjectScenePtr objects = it->second; + for(size_t i = 0; i < objects->mControllers.size() ;i++) + { + EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); + if (value) + value->addTime(dt); + + objects->mControllers[i].update(); + } + objects->rotateBillboardNodes(camera); + + // Finished playing? + if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) + { + Ogre::SceneNode* node = it->first; + it = mEffects.erase(it); + mSceneMgr->destroySceneNode(node); + continue; + } + ++it; + } +} + +void EffectManager::clear() +{ + for (std::vector >::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + Ogre::SceneNode* node = it->first; + it = mEffects.erase(it); + mSceneMgr->destroySceneNode(node); + } +} + +} diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp new file mode 100644 index 000000000..bc9e68d26 --- /dev/null +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_MWRENDER_EFFECTMANAGER_H +#define OPENMW_MWRENDER_EFFECTMANAGER_H + +#include + +namespace MWRender +{ + // Note: effects attached to another object should be managed by MWRender::Animation::addEffect. + // This class manages "free" effects, i.e. attached to a dedicated scene node in the world. + class EffectManager + { + public: + EffectManager(Ogre::SceneManager* sceneMgr); + ~EffectManager() { clear(); } + + /// Add an effect. When it's finished playing, it will be removed automatically. + void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition, float scale); + + void update(float dt, Ogre::Camera* camera); + + /// Remove all effects + void clear(); + + private: + std::vector > mEffects; + Ogre::SceneManager* mSceneMgr; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/externalrendering.hpp b/apps/openmw/mwrender/externalrendering.hpp deleted file mode 100644 index 33c9afd87..000000000 --- a/apps/openmw/mwrender/externalrendering.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef GAME_RENDERING_EXTERNALRENDERING_H -#define GAME_RENDERING_EXTERNALRENDERING_H - -namespace Ogre -{ - class SceneManager; -} - -namespace MWRender -{ - /// \brief Base class for out of world rendering - class ExternalRendering - { - public: - - virtual void setup (Ogre::SceneManager *sceneManager) = 0; - - virtual ~ExternalRendering() {} - }; -} - -#endif - diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 120a83fae..018dc082a 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -12,6 +12,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -60,8 +62,6 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); - mExploredBuffer.resize((mMaxX-mMinX+1) * (mMaxY-mMinY+1) * 4); - //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { @@ -170,21 +170,10 @@ namespace MWRender tex->load(); - - mOverlayTexture = Ogre::TextureManager::getSingleton().createManual("GlobalMapOverlay", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC_WRITE_ONLY); - - std::vector buffer; - buffer.resize(mWidth * mHeight); - - // initialize to (0, 0, 0, 0) - for (int p=0; pgetBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], mWidth*mHeight*4); - mOverlayTexture->getBuffer()->unlock(); + clear(); loadingListener->loadingOff(); } @@ -227,9 +216,124 @@ namespace MWRender if (!localMapTexture.isNull()) { - mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512), Ogre::Image::Box(originX,originY,originX+24,originY+24)); } } + + void GlobalMap::clear() + { + std::vector buffer; + // initialize to (0,0,0,0) + buffer.resize(mWidth * mHeight, 0); + + Ogre::PixelBox pb(mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, &buffer[0]); + + mOverlayTexture->getBuffer()->blitFromMemory(pb); + } + + void GlobalMap::write(ESM::ESMWriter &writer) + { + ESM::GlobalMap map; + map.mBounds.mMinX = mMinX; + map.mBounds.mMaxX = mMaxX; + map.mBounds.mMinY = mMinY; + map.mBounds.mMaxY = mMaxY; + + Ogre::Image image; + mOverlayTexture->convertToImage(image); + Ogre::DataStreamPtr encoded = image.encode("png"); + map.mImageData.resize(encoded->size()); + encoded->read(&map.mImageData[0], encoded->size()); + + writer.startRecord(ESM::REC_GMAP); + map.save(writer); + writer.endRecord(ESM::REC_GMAP); + } + + void GlobalMap::readRecord(ESM::ESMReader &reader, int32_t type, std::vector >& exploredCells) + { + if (type == ESM::REC_GMAP) + { + ESM::GlobalMap map; + map.load(reader); + + const ESM::GlobalMap::Bounds& bounds = map.mBounds; + + if (bounds.mMaxX-bounds.mMinX <= 0) + return; + if (bounds.mMaxY-bounds.mMinY <= 0) + return; + + if (bounds.mMinX > bounds.mMaxX + || bounds.mMinY > bounds.mMaxY) + throw std::runtime_error("invalid map bounds"); + + Ogre::Image image; + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size())); + image.load(stream, "png"); + + int xLength = (bounds.mMaxX-bounds.mMinX+1); + int yLength = (bounds.mMaxY-bounds.mMinY+1); + + // Size of one cell in image space + int cellImageSizeSrc = image.getWidth() / xLength; + if (int(image.getHeight() / yLength) != cellImageSizeSrc) + throw std::runtime_error("cell size must be quadratic"); + + // Determine which cells were explored by reading the image data + for (int x=0; x < xLength; ++x) + { + for (int y=0; y < yLength; ++y) + { + unsigned int imageX = (x) * cellImageSizeSrc; + // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is + unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc; + + assert(imageX < image.getWidth()); + assert(imageY < image.getHeight()); + + if (image.getColourAt(imageX, imageY, 0).a > 0) + exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY)); + } + } + + // If cell bounds of the currently loaded content and the loaded savegame do not match, + // we need to resize source/dest boxes to accommodate + // This means nonexisting cells will be dropped silently + int cellImageSizeDst = 24; + + // Completely off-screen? -> no need to blit anything + if (bounds.mMaxX < mMinX + || bounds.mMaxY < mMinY + || bounds.mMinX > mMaxX + || bounds.mMinY > mMaxY) + return; + + int leftDiff = (mMinX - bounds.mMinX); + int topDiff = (bounds.mMaxY - mMaxY); + int rightDiff = (bounds.mMaxX - mMaxX); + int bottomDiff = (mMinY - bounds.mMinY); + Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), + std::max(0, topDiff * cellImageSizeSrc), + std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc), + std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc)); + + Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), + std::max(0, -topDiff * cellImageSizeDst), + std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst), + std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst)); + + // Looks like there is no interface for blitting from memory with src/dst boxes. + // So we create a temporary texture for blitting. + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(), + image.getHeight(), 0, Ogre::PF_A8B8G8R8); + tex->loadImage(image); + + mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); + + Ogre::TextureManager::getSingleton().remove("@temp"); + } + } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index dd3787b62..5fe878cd4 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_GLOBALMAP_H -#define _GAME_RENDER_GLOBALMAP_H +#ifndef GAME_RENDER_GLOBALMAP_H +#define GAME_RENDER_GLOBALMAP_H #include @@ -10,6 +10,12 @@ namespace Loading class Listener; } +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + namespace MWRender { @@ -31,13 +37,18 @@ namespace MWRender void exploreCell (int cellX, int cellY); + /// Clears the overlay + void clear(); + + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type, std::vector >& exploredCells); + private: std::string mCacheDir; std::vector< std::pair > mExploredCells; Ogre::TexturePtr mOverlayTexture; - std::vector mExploredBuffer; int mWidth; int mHeight; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 5f4128978..f147ae7b7 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -79,7 +79,7 @@ std::string LocalMap::coordStr(const int x, const int y) return StringConverter::toString(x) + "_" + StringConverter::toString(y); } -void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) +void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { @@ -108,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) } } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax) +void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax) { mInterior = false; @@ -125,7 +125,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax) render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name); } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, +void LocalMap::requestMap(MWWorld::CellStore* cell, AxisAlignedBox bounds) { // if we're in an empty cell, don't bother rendering anything @@ -225,64 +225,54 @@ void LocalMap::render(const float x, const float y, tex = TextureManager::getSingleton().getByName(texture); if (tex.isNull()) { - // try loading from disk - //if (boost::filesystem::exists(texture+".jpg")) - //{ - /// \todo - //} - //else + // render + tex = TextureManager::getSingleton().createManual( + texture, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + xw*sMapResolution/sSize, yw*sMapResolution/sSize, + 0, + PF_R8G8B8, + 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); + vp->setMaterialScheme("local_map"); + + rtt->update(); + + // create "fog of war" texture + TexturePtr tex2 = TextureManager::getSingleton().createManual( + texture + "_fog", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize, + 0, + PF_A8R8G8B8, + TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + + // create a buffer to use for dynamic operations + std::vector buffer; + buffer.resize(sFogOfWarResolution*sFogOfWarResolution); + + // initialize to (0, 0, 0, 1) + for (int p=0; pgetBuffer()->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); - vp->setMaterialScheme("local_map"); - - rtt->update(); - - // create "fog of war" texture - TexturePtr tex2 = TextureManager::getSingleton().createManual( - texture + "_fog", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize, - 0, - PF_A8R8G8B8, - TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); - - // create a buffer to use for dynamic operations - std::vector buffer; - buffer.resize(sFogOfWarResolution*sFogOfWarResolution); - - // initialize to (0, 0, 0, 1) - for (int p=0; pgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); - tex2->getBuffer()->unlock(); + buffer[p] = (255 << 24); + } - mBuffers[texture] = buffer; + memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); + tex2->getBuffer()->unlock(); - // save to cache for next time - //rtt->writeContentsToFile("./" + texture + ".jpg"); - } + mBuffers[texture] = buffer; } + mRenderingManager->enableLights(true); mLight->setVisible(false); @@ -339,8 +329,6 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis(); - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); - if (!mInterior) { x = std::ceil(pos.x / sSize)-1; diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 538489640..638469d2d 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_LOCALMAP_H -#define _GAME_RENDER_LOCALMAP_H +#ifndef GAME_RENDER_LOCALMAP_H +#define GAME_RENDER_LOCALMAP_H #include diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 9ffe53eab..d07aad31d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -14,14 +16,82 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "renderconst.hpp" #include "camera.hpp" +namespace +{ + +std::string getVampireHead(const std::string& race, bool female) +{ + static std::map , const ESM::BodyPart* > sVampireMapping; + + std::pair thisCombination = std::make_pair(race, int(female)); + + if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + { + const ESM::BodyPart& bodypart = *it; + if (!bodypart.mData.mVampire) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) + continue; + if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + continue; + if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) + continue; + sVampireMapping[thisCombination] = &*it; + } + } + + assert(sVampireMapping[thisCombination]); + return "meshes\\" + sVampireMapping[thisCombination]->mModel; +} + +} + namespace MWRender { +float HeadAnimationTime::getValue() const +{ + // TODO: Handle eye blinking (time is in the text keys) + if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) + return 0; + else + // TODO: Use the loudness of the currently playing sound + return 1; +} + +float WeaponAnimationTime::getValue() const +{ + if (mWeaponGroup.empty()) + return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; +} + +void WeaponAnimationTime::setGroup(const std::string &group) +{ + mWeaponGroup = group; + mStartTime = mAnimation->getStartTime(mWeaponGroup); +} + +void WeaponAnimationTime::updateStartTime() +{ + setGroup(mWeaponGroup); +} + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -58,49 +128,46 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - for(size_t i = 0;i < ESM::PRT_Count;i++) - destroyObjectList(sceneMgr, mObjectParts[i]); + if (!mListenerDisabled) + mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWorld::InventoryStore& inv, int visibilityFlags, ViewMode viewMode) +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener, ViewMode viewMode) : Animation(ptr, node), - mStateID(-1), - mTimeToChange(0), mVisibilityFlags(visibilityFlags), - 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()), - mWeapon(inv.end()), - mShield(inv.end()), + mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), - mFirstPersonOffset(0.f, 0.f, 0.f) + mShowCarriedLeft(true), + mFirstPersonOffset(0.f, 0.f, 0.f), + mAlpha(1.f), + mNpcType(Type_Normal), + mPitchFactor(0) { mNpc = mPtr.get()->mBase; + mHeadAnimationTime = Ogre::SharedPtr(new HeadAnimationTime(mPtr)); + mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); + for(size_t i = 0;i < ESM::PRT_Count;i++) { mPartslots[i] = -1; //each slot is empty mPartPriorities[i] = 0; } + if (!disableListener) + mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); + updateNpcBase(); } void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) { assert(viewMode != VM_HeadOnly); + if(mViewMode == viewMode) + return; + mViewMode = viewMode; rebuild(); } @@ -118,17 +185,22 @@ void NpcAnimation::updateNpcBase() const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); - bool isWerewolf = MWWorld::Class::get(mPtr).getNpcStats(mPtr).isWerewolf(); + bool isWerewolf = (mNpcType == Type_Werewolf); + bool isVampire = (mNpcType == Type_Vampire); - if(!isWerewolf) + if (isWerewolf) { - mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; - mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; + mHeadModel = "meshes\\" + store.get().find("WerewolfHead")->mModel; + mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; } else { - mHeadModel = "meshes\\" + store.get().find("WerewolfHead")->mModel; - mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; + if (isVampire) + mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); + else + mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; + + mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; @@ -173,73 +245,78 @@ void NpcAnimation::updateNpcBase() for(size_t i = 0;i < ESM::PRT_Count;i++) removeIndividualPart((ESM::PartReferenceType)i); - updateParts(true); + updateParts(); + + mWeaponAnimationTime->updateStartTime(); } -void NpcAnimation::updateParts(bool forceupdate) -{ +void NpcAnimation::updateParts() +{ + mAlpha = 1.f; + const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + + NpcType curType = Type_Normal; + if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude > 0) + curType = Type_Vampire; + if (cls.getNpcStats(mPtr).isWerewolf()) + curType = Type_Werewolf; + + if (curType != mNpcType) + { + mNpcType = curType; + rebuild(); + return; + } + static const struct { - MWWorld::ContainerStoreIterator NpcAnimation::*mPart; int mSlot; int mBasePriority; } slotlist[] = { // FIXME: Priority is based on the number of reserved slots. There should be a better way. - { &NpcAnimation::mRobe, MWWorld::InventoryStore::Slot_Robe, 12 }, - { &NpcAnimation::mSkirtIter, MWWorld::InventoryStore::Slot_Skirt, 3 }, - { &NpcAnimation::mHelmet, MWWorld::InventoryStore::Slot_Helmet, 0 }, - { &NpcAnimation::mCuirass, MWWorld::InventoryStore::Slot_Cuirass, 0 }, - { &NpcAnimation::mGreaves, MWWorld::InventoryStore::Slot_Greaves, 0 }, - { &NpcAnimation::mPauldronL, MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, - { &NpcAnimation::mPauldronR, MWWorld::InventoryStore::Slot_RightPauldron, 0 }, - { &NpcAnimation::mBoots, MWWorld::InventoryStore::Slot_Boots, 0 }, - { &NpcAnimation::mGloveL, MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, - { &NpcAnimation::mGloveR, MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, - { &NpcAnimation::mShirt, MWWorld::InventoryStore::Slot_Shirt, 0 }, - { &NpcAnimation::mPants, MWWorld::InventoryStore::Slot_Pants, 0 }, - { &NpcAnimation::mShield, MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, - { &NpcAnimation::mWeapon, MWWorld::InventoryStore::Slot_CarriedRight, 0 } + { MWWorld::InventoryStore::Slot_Robe, 12 }, + { MWWorld::InventoryStore::Slot_Skirt, 3 }, + { MWWorld::InventoryStore::Slot_Helmet, 0 }, + { MWWorld::InventoryStore::Slot_Cuirass, 0 }, + { MWWorld::InventoryStore::Slot_Greaves, 0 }, + { MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, + { MWWorld::InventoryStore::Slot_RightPauldron, 0 }, + { MWWorld::InventoryStore::Slot_Boots, 0 }, + { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, + { MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, + { MWWorld::InventoryStore::Slot_Shirt, 0 }, + { MWWorld::InventoryStore::Slot_Pants, 0 }, + { MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, + { MWWorld::InventoryStore::Slot_CarriedRight, 0 } }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - for(size_t i = 0;!forceupdate && i < slotlistsize;i++) - { - MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].mSlot); - if(this->*slotlist[i].mPart != iter) - { - forceupdate = true; - break; - } - } - if(!forceupdate) - return; - for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { MWWorld::ContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); - this->*slotlist[i].mPart = store; removePartGroup(slotlist[i].mSlot); - if(this->*slotlist[i].mPart == inv.end()) + if(store == inv.end()) continue; if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet) removeIndividualPart(ESM::PRT_Hair); int prio = 1; + bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); + Ogre::Vector3 glowColor = getEnchantmentColor(*store); if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; const ESM::Clothing *clothes = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts); + addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); } else if(store->getTypeName() == typeid(ESM::Armor).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 1; const ESM::Armor *armor = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts); + addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); } if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) @@ -285,14 +362,17 @@ void NpcAnimation::updateParts(bool forceupdate) } showWeapons(mShowWeapons); + showCarriedLeft(mShowCarriedLeft); // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; + static std::map , std::vector > sVampireMapping; + static const int Flag_Female = 1<<0; static const int Flag_FirstPerson = 1<<1; - bool isWerewolf = cls.getNpcStats(mPtr).isWerewolf(); + bool isWerewolf = (mNpcType == Type_Werewolf); int flags = (isWerewolf ? -1 : 0); if(!mNpc->isMale()) flags |= Flag_Female; @@ -410,17 +490,18 @@ public: } }; -NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename) +NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) { - NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); - setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha); + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + enchantedGlow, glowColor); - std::for_each(objects.mEntities.begin(), objects.mEntities.end(), SetObjectGroup(group)); - std::for_each(objects.mParticles.begin(), objects.mParticles.end(), SetObjectGroup(group)); + std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); + std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); - if(objects.mSkelBase) + if(objects->mSkelBase) { - Ogre::AnimationStateSet *aset = objects.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { @@ -428,7 +509,7 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in state->setEnabled(false); state->setLoop(false); } - Ogre::SkeletonInstance *skelinst = objects.mSkelBase->getSkeleton(); + Ogre::SkeletonInstance *skelinst = objects->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); @@ -439,35 +520,39 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { - if(mTimeToChange <= 0.0f) - { - mTimeToChange = 0.2f; - updateParts(); - } - mTimeToChange -= timepassed; - Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); - if(mViewMode == VM_FirstPerson && mCamera) + if(mViewMode == VM_FirstPerson) { - float pitch = mCamera->getPitch(); + float pitch = mPtr.getRefData().getPosition().rot[0]; Ogre::Node *node = baseinst->getBone("Bip01 Neck"); - node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD); + node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD); // This has to be done before this function ends; // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } + else if (mPitchFactor > 0) + { + // In third person mode we may still need pitch for ranged weapon targeting + float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; + Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); + node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + node = baseinst->getBone("Bip01 Spine1"); + node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. for(size_t i = 0;i < ESM::PRT_Count;i++) { - std::vector >::iterator ctrl(mObjectParts[i].mControllers.begin()); - for(;ctrl != mObjectParts[i].mControllers.end();ctrl++) + if (mObjectParts[i].isNull()) + continue; + std::vector >::iterator ctrl(mObjectParts[i]->mControllers.begin()); + for(;ctrl != mObjectParts[i]->mControllers.end();ctrl++) ctrl->update(); - Ogre::Entity *ent = mObjectParts[i].mSkelBase; + Ogre::Entity *ent = mObjectParts[i]->mSkelBase; if(!ent) continue; updateSkeletonInstance(baseinst, ent->getSkeleton()); ent->getAllAnimationStates()->_notifyDirty(); @@ -481,7 +566,7 @@ void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) mPartPriorities[type] = 0; mPartslots[type] = -1; - destroyObjectList(mInsert->getCreator(), mObjectParts[type]); + mObjectParts[type].setNull(); } void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) @@ -503,7 +588,7 @@ void NpcAnimation::removePartGroup(int group) } } -bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh) +bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, Ogre::Vector3* glowColor) { if(priority <= mPartPriorities[type]) return false; @@ -512,20 +597,23 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartslots[type] = group; mPartPriorities[type] = priority; - mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type)); - if(mObjectParts[type].mSkelBase) + mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); + if(mObjectParts[type]->mSkelBase) { - Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton(); - if(mObjectParts[type].mSkelBase->isParentTagPoint()) + Ogre::SkeletonInstance *skel = mObjectParts[type]->mSkelBase->getSkeleton(); + if(mObjectParts[type]->mSkelBase->isParentTagPoint()) { - Ogre::Node *root = mObjectParts[type].mSkelBase->getParentNode(); + Ogre::Node *root = mObjectParts[type]->mSkelBase->getParentNode(); if(skel->hasBone("BoneOffset")) { Ogre::Bone *offset = skel->getBone("BoneOffset"); + root->translate(offset->getPosition()); - root->rotate(offset->getOrientation()); - // HACK: Why an extra -90 degree rotation? + + // It appears that the BoneOffset rotation is completely bogus, at least for light models. + //root->rotate(offset->getOrientation()); root->pitch(Ogre::Degree(-90.0f)); + root->scale(offset->getScale()); root->setInitialState(); } @@ -534,22 +622,24 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } - // TODO: - // type == ESM::PRT_Head should get an animation source based on the current output of - // the actor's 'say' sound (0 = silent, 1 = loud?). - // type == ESM::PRT_Weapon should get an animation source based on the current offset - // of the weapon attack animation (from its beginning, or start marker?) - std::vector >::iterator ctrl(mObjectParts[type].mControllers.begin()); - for(;ctrl != mObjectParts[type].mControllers.end();ctrl++) + std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); + for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) - ctrl->setSource(mNullAnimationValuePtr); + { + ctrl->setSource(mNullAnimationTimePtr); + + if (type == ESM::PRT_Head) + ctrl->setSource(mHeadAnimationTime); + else if (type == ESM::PRT_Weapon) + ctrl->setSource(mWeaponAnimationTime); + } } return true; } -void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts) +void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, Ogre::Vector3* glowColor) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); @@ -587,7 +677,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormPart, group, priority, "meshes\\"+bodypart->mModel); + addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); else reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority); } @@ -599,18 +689,191 @@ void NpcAnimation::showWeapons(bool showWeapon) if(showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - mWeapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(mWeapon != inv.end()) // special case for weapons + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon != inv.end()) // special case for weapons { - MWWorld::Ptr weapon = *mWeapon; - std::string mesh = MWWorld::Class::get(weapon).getModel(weapon); - addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh); + Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); + std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); + addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, + mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + + if (weapon->getTypeName() == typeid(ESM::Weapon).name() && + weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); } } else { removeIndividualPart(ESM::PRT_Weapon); } + mAlpha = 1.f; +} + +void NpcAnimation::showCarriedLeft(bool show) +{ + mShowCarriedLeft = show; + MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + if(show && iter != inv.end()) + { + Ogre::Vector3 glowColor = getEnchantmentColor(*iter); + std::string mesh = MWWorld::Class::get(*iter).getModel(*iter); + if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, + mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) + { + if (iter->getTypeName() == typeid(ESM::Light).name()) + addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], iter->get()->mBase); + } + } + else + removeIndividualPart(ESM::PRT_Shield); +} + +void NpcAnimation::attachArrow() +{ + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapons(true); + else + { + NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); + Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); + setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); + + std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); + std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); + } +} + +void NpcAnimation::releaseArrow() +{ + // Thrown weapons get detached now + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon != inv.end() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + showWeapons(false); + inv.remove(*weapon, 1, mPtr); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + inv.remove(*ammo, 1, mPtr); + mAmmunition.setNull(); + } +} + +void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) +{ + // During first auto equip, we don't play any sounds. + // Basically we don't want sounds when the actor is first loaded, + // the items should appear as if they'd always been equipped. + if (playSound) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + } + + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + // Don't play particle VFX unless the effect is new or it should be looping. + if (isNew || loop) + addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } +} + +void NpcAnimation::setAlpha(float alpha) +{ + if (alpha == mAlpha) + return; + mAlpha = alpha; + + for (int i=0; imEntities.size(); ++j) + { + Ogre::Entity* ent = mObjectParts[i]->mEntities[j]; + if (ent != mObjectParts[i]->mSkelBase) + applyAlpha(alpha, ent, mObjectParts[i]); + } + } +} + +void NpcAnimation::preRender(Ogre::Camera *camera) +{ + Animation::preRender(camera); + for (int i=0; irotateBillboardNodes(camera); + } +} + +void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene) +{ + sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(0)->getMaterial()->getName(), "Default"); + ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent() + ? RQG_Alpha : RQG_Main); + + + Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(ent); + if (mAlpha == 1.f) + { + // Don't bother remembering what the original values were. Just remove the techniques and let the factory restore them. + mat->removeAllTechniques(); + sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); + return; + } + + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = alpha; + pass->setDiffuse(diffuse); + pass->setVertexColourTracking(pass->getVertexColourTracking() &~Ogre::TVC_DIFFUSE); + } + } } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index b1abf97af..725fde01d 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -1,25 +1,53 @@ -#ifndef _GAME_RENDER_NPCANIMATION_H -#define _GAME_RENDER_NPCANIMATION_H +#ifndef GAME_RENDER_NPCANIMATION_H +#define GAME_RENDER_NPCANIMATION_H #include "animation.hpp" -#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" namespace ESM { struct NPC; } -namespace MWWorld +namespace MWRender { - class InventoryStore; -} -namespace MWRender +class HeadAnimationTime : public Ogre::ControllerValue { +private: + MWWorld::Ptr mReference; +public: + HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference) {} + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } +}; + +class WeaponAnimationTime : public Ogre::ControllerValue +{ +private: + Animation* mAnimation; + std::string mWeaponGroup; + float mStartTime; +public: + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} + void setGroup(const std::string& group); + void updateStartTime(); + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } +}; -class NpcAnimation : public Animation + +class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener { +public: + virtual void equipmentChanged() { updateParts(); } + virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound); + public: typedef std::map PartBoneMap; @@ -32,32 +60,25 @@ public: private: static const PartBoneMap sPartList; - int mStateID; + bool mListenerDisabled; // Bounded Parts - NifOgre::ObjectList mObjectParts[ESM::PRT_Count]; + NifOgre::ObjectScenePtr mObjectParts[ESM::PRT_Count]; const ESM::NPC *mNpc; std::string mHeadModel; std::string mHairModel; ViewMode mViewMode; bool mShowWeapons; + bool mShowCarriedLeft; - float mTimeToChange; - MWWorld::ContainerStoreIterator mRobe; - MWWorld::ContainerStoreIterator mHelmet; - MWWorld::ContainerStoreIterator mShirt; - MWWorld::ContainerStoreIterator mCuirass; - MWWorld::ContainerStoreIterator mGreaves; - MWWorld::ContainerStoreIterator mPauldronL; - MWWorld::ContainerStoreIterator mPauldronR; - MWWorld::ContainerStoreIterator mBoots; - MWWorld::ContainerStoreIterator mPants; - MWWorld::ContainerStoreIterator mGloveL; - MWWorld::ContainerStoreIterator mGloveR; - MWWorld::ContainerStoreIterator mSkirtIter; - MWWorld::ContainerStoreIterator mWeapon; - MWWorld::ContainerStoreIterator mShield; + enum NpcType + { + Type_Normal, + Type_Werewolf, + Type_Vampire + }; + NpcType mNpcType; int mVisibilityFlags; @@ -66,30 +87,62 @@ private: Ogre::Vector3 mFirstPersonOffset; + Ogre::SharedPtr mHeadAnimationTime; + Ogre::SharedPtr mWeaponAnimationTime; + + float mAlpha; + float mPitchFactor; + void updateNpcBase(); - NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename); + NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, + bool enchantedGlow, Ogre::Vector3* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); - bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh); + bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); void removePartGroup(int group); - void addPartGroup(int group, int priority, const std::vector &parts); + void addPartGroup(int group, int priority, const std::vector &parts, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + + void applyAlpha(float alpha, Ogre::Entity* ent, NifOgre::ObjectScenePtr scene); public: - NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, - MWWorld::InventoryStore& inv, int visibilityFlags, + /** + * @param ptr + * @param node + * @param visibilityFlags + * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports + * one listener at a time, so you shouldn't do this if creating several NpcAnimations + * for the same Ptr, eg preview dolls for the player. + * Those need to be manually rendered anyway. + * @param viewMode + */ + NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener = false, ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + virtual Ogre::Vector3 runAnimation(float timepassed); + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + virtual void showWeapons(bool showWeapon); + virtual void showCarriedLeft(bool showa); + + virtual void attachArrow(); + virtual void releaseArrow(); + + NifOgre::ObjectScenePtr mAmmunition; void setViewMode(ViewMode viewMode); - void updateParts(bool forceupdate = false); + void updateParts(); /// \brief Applies a translation to the arms and hands. /// This may be called multiple times before the animation @@ -98,6 +151,12 @@ public: /// Rebuilds the NPC, updating their root model, animation sources, and equipment. void rebuild(); + + /// Make the NPC only partially visible + virtual void setAlpha(float alpha); + + /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) + virtual void preRender (Ogre::Camera* camera); }; } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index fd81baf6e..e721477ee 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -172,7 +172,7 @@ bool Objects::deleteObject (const MWWorld::Ptr& ptr) } -void Objects::removeCell(MWWorld::Ptr::CellStore* store) +void Objects::removeCell(MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) { @@ -212,7 +212,7 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store) } } -void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell) +void Objects::buildStaticGeometry(MWWorld::CellStore& cell) { if(mStaticGeometry.find(&cell) != mStaticGeometry.end()) { @@ -226,7 +226,7 @@ void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell) } } -Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell) +Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell) { return mBounds[cell]; } @@ -245,11 +245,16 @@ void Objects::disableLights() it->second->enableLights(false); } -void Objects::update(const float dt) +void Objects::update(float dt, Ogre::Camera* camera) { PtrAnimationMap::const_iterator it = mObjects.begin(); for(;it != mObjects.end();it++) it->second->runAnimation(dt); + + it = mObjects.begin(); + for(;it != mObjects.end();it++) + it->second->preRender(camera); + } void Objects::rebuildStaticGeometry() @@ -278,6 +283,24 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) } else { node = mCellSceneNodes[newCell]; } + node->addChild(cur.getRefData().getBaseNode()); + + PtrAnimationMap::iterator iter = mObjects.find(old); + if(iter != mObjects.end()) + { + ObjectAnimation *anim = iter->second; + mObjects.erase(iter); + anim->updatePtr(cur); + mObjects[cur] = anim; + } +} + +ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) +{ + PtrAnimationMap::const_iterator iter = mObjects.find(ptr); + if(iter != mObjects.end()) + return iter->second; + return NULL; } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 22dd1e4f5..665a1e657 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_OBJECTS_H -#define _GAME_RENDER_OBJECTS_H +#ifndef GAME_RENDER_OBJECTS_H +#define GAME_RENDER_OBJECTS_H #include #include @@ -33,6 +33,8 @@ class Objects{ void insertBegin(const MWWorld::Ptr& ptr); + + public: Objects(OEngine::Render::OgreRenderer &renderer) : mRenderer(renderer) @@ -41,10 +43,12 @@ public: ~Objects(){} void insertModel(const MWWorld::Ptr& ptr, const std::string &model); + ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); + void enableLights(); void disableLights(); - void update (const float dt); + void update (float dt, Ogre::Camera* camera); ///< per-frame update Ogre::AxisAlignedBox getDimensions(MWWorld::CellStore*); diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index a69511acd..246103471 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "renderconst.hpp" diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 983361c18..6974f37b9 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_OCCLUSION_QUERY_H -#define _GAME_OCCLUSION_QUERY_H +#ifndef GAME_OCCLUSION_QUERY_H +#define GAME_OCCLUSION_QUERY_H #include #include diff --git a/apps/openmw/mwrender/renderinginterface.hpp b/apps/openmw/mwrender/renderinginterface.hpp index 8ae2c0f8f..02f3c804a 100644 --- a/apps/openmw/mwrender/renderinginterface.hpp +++ b/apps/openmw/mwrender/renderinginterface.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDERING_INTERFACE_H -#define _GAME_RENDERING_INTERFACE_H +#ifndef GAME_RENDERING_INTERFACE_H +#define GAME_RENDERING_INTERFACE_H namespace MWRender { diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 93425191d..0a82d67fb 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -8,10 +8,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -23,7 +19,6 @@ #include -#include #include #include @@ -34,21 +29,20 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" // FIXME #include "../mwbase/windowmanager.hpp" // FIXME +#include "../mwbase/statemanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/player.hpp" #include "shadows.hpp" #include "localmap.hpp" #include "water.hpp" -#include "compositors.hpp" #include "npcanimation.hpp" -#include "externalrendering.hpp" #include "globalmap.hpp" #include "videoplayer.hpp" #include "terrainstorage.hpp" +#include "effectmanager.hpp" using namespace MWRender; using namespace Ogre; @@ -60,14 +54,16 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b MWWorld::Fallback* fallback) : mRendering(_rend) , mFallback(fallback) - , mObjects(mRendering) - , mActors(mRendering, this) , mPlayerAnimation(NULL) , mAmbientMode(0) , mSunEnabled(0) , mPhysicsEngine(engine) , mTerrain(NULL) + , mEffectManager(NULL) { + mActors = new MWRender::Actors(mRendering, this); + mObjects = new MWRender::Objects(mRendering); + mEffectManager = new EffectManager(mRendering.getScene()); // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos); @@ -87,8 +83,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mRendering.getWindow()->addListener(this); mRendering.setWindowListener(this); - mCompositors = new Compositors(mRendering.getViewport()); - mWater = 0; // material system @@ -116,10 +110,13 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mFactory->loadAllFiles(); - // Set default mipmap level (NB some APIs ignore this) - // Mipmap generation is currently disabled because it causes issues on Intel/AMD - //TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); + // Compressed textures with 0 mip maps are bugged in 1.8, so disable mipmap generator in that case + // ( https://ogre3d.atlassian.net/browse/OGRE-259 ) +#if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); +#else TextureManager::getSingleton().setDefaultNumMipmaps(0); +#endif // Set default texture filtering options TextureFilterOptions tfo; @@ -157,13 +154,11 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false"); sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (new sh::Vector4(0,0,0,0))); - applyCompositors(); - mRootNode = mRendering.getScene()->getRootSceneNode(); mRootNode->createChildSceneNode("player"); - mObjects.setRootNode(mRootNode); - mActors.setRootNode(mRootNode); + mObjects->setRootNode(mRootNode); + mActors->setRootNode(mRootNode); mCamera = new MWRender::Camera(mRendering.getCamera()); @@ -198,9 +193,11 @@ RenderingManager::~RenderingManager () delete mTerrain; delete mLocalMap; delete mOcclusionQuery; - delete mCompositors; delete mWater; delete mVideoPlayer; + delete mActors; + delete mObjects; + delete mEffectManager; delete mFactory; } @@ -210,10 +207,10 @@ MWRender::SkyManager* RenderingManager::getSkyManager() } MWRender::Objects& RenderingManager::getObjects(){ - return mObjects; + return *mObjects; } MWRender::Actors& RenderingManager::getActors(){ - return mActors; + return *mActors; } OEngine::Render::Fader* RenderingManager::getFader() @@ -221,10 +218,15 @@ OEngine::Render::Fader* RenderingManager::getFader() return mRendering.getFader(); } -void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) + MWRender::Camera* RenderingManager::getCamera() const +{ + return mCamera; +} + +void RenderingManager::removeCell (MWWorld::CellStore *store) { - mObjects.removeCell(store); - mActors.removeCell(store); + mObjects->removeCell(store); + mActors->removeCell(store); mDebugging->cellRemoved(store); } @@ -238,9 +240,9 @@ void RenderingManager::toggleWater() mWater->toggle(); } -void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) +void RenderingManager::cellAdded (MWWorld::CellStore *store) { - mObjects.buildStaticGeometry (*store); + mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); waterAdded(store); @@ -254,8 +256,8 @@ void RenderingManager::addObject (const MWWorld::Ptr& ptr){ void RenderingManager::removeObject (const MWWorld::Ptr& ptr) { - if (!mObjects.deleteObject (ptr)) - mActors.deleteObject (ptr); + if (!mObjects->deleteObject (ptr)) + mActors->deleteObject (ptr); } void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position) @@ -295,9 +297,9 @@ RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr & parent->removeChild(child); if (MWWorld::Class::get(old).isActor()) { - mActors.updateObjectCell(old, cur); + mActors->updateObjectCell(old, cur); } else { - mObjects.updateObjectCell(old, cur); + mObjects->updateObjectCell(old, cur); } } @@ -315,7 +317,7 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) if(ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; else if(MWWorld::Class::get(ptr).isActor()) - anim = dynamic_cast(mActors.getAnimation(ptr)); + anim = dynamic_cast(mActors->getAnimation(ptr)); if(anim) { anim->rebuild(); @@ -329,11 +331,17 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) void RenderingManager::update (float duration, bool paused) { + mVideoPlayer->update (); + + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_NoGame) + return; + MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); - int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; + int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f))); setAmbientMode(); @@ -355,18 +363,18 @@ void RenderingManager::update (float duration, bool paused) } // Sink the camera while sneaking - bool isSneaking = MWWorld::Class::get(player).getStance(player, MWWorld::Class::Sneak); + bool isSneaking = player.getClass().getCreatureStats(player).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool isInAir = !world->isOnGround(player); bool isSwimming = world->isSwimming(player); + static const int i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get() + .find("i1stPersonSneakDelta")->getInt(); if(isSneaking && !(isSwimming || isInAir)) - mCamera->setSneakOffset(); + mCamera->setSneakOffset(i1stPersonSneakDelta); mOcclusionQuery->update(duration); - mVideoPlayer->update (); - mRendering.update(duration); Ogre::ControllerManager::getSingleton().setTimeFactor(paused ? 0.f : 1.f); @@ -380,9 +388,11 @@ void RenderingManager::update (float duration, bool paused) if(paused) return; - mActors.update (duration); - mObjects.update (duration); + mEffectManager->update(duration, mRendering.getCamera()); + mActors->update (mRendering.getCamera()); + mPlayerAnimation->preRender(mRendering.getCamera()); + mObjects->update (duration, mRendering.getCamera()); mSkyManager->update(duration); @@ -410,14 +420,9 @@ void RenderingManager::postRenderTargetUpdate(const RenderTargetEvent &evt) mOcclusionQuery->setActive(false); } -void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store) +void RenderingManager::waterAdded (MWWorld::CellStore *store) { - const MWWorld::Store &lands = - MWBase::Environment::get().getWorld()->getStore().get(); - - if(store->mCell->mData.mFlags & ESM::Cell::HasWater - || ((store->mCell->isExterior()) - && !lands.search(store->mCell->getGridX(),store->mCell->getGridY()) )) // always use water, if the cell does not have land. + if(store->mCell->mData.mFlags & ESM::Cell::HasWater) { mWater->changeCell(store->mCell); mWater->setActive(true); @@ -476,32 +481,24 @@ bool RenderingManager::toggleRenderMode(int mode) { if (mRendering.getCamera()->getPolygonMode() == PM_SOLID) { - mCompositors->setEnabled(false); - mRendering.getCamera()->setPolygonMode(PM_WIREFRAME); return true; } else { - mCompositors->setEnabled(true); - mRendering.getCamera()->setPolygonMode(PM_SOLID); return false; } } - else if (mode == MWBase::World::Render_BoundingBoxes) + else //if (mode == MWBase::World::Render_BoundingBoxes) { bool show = !mRendering.getScene()->getShowBoundingBoxes(); mRendering.getScene()->showBoundingBoxes(show); return show; } - else //if (mode == MWBase::World::Render_Compositors) - { - return mCompositors->toggle(); - } } -void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell) +void RenderingManager::configureFog(MWWorld::CellStore &mCell) { Ogre::ColourValue color; color.setAsABGR (mCell.mCell->mAmbi.mFog); @@ -554,7 +551,7 @@ void RenderingManager::setAmbientMode() } } -void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell) +void RenderingManager::configureAmbient(MWWorld::CellStore &mCell) { if (mCell.mCell->mData.mFlags & ESM::Cell::Interior) mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient); @@ -605,8 +602,8 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) { mAmbientColor = colour; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::NightEye)).mMagnitude; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).mMagnitude; Ogre::ColourValue final = colour; final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f)); @@ -651,13 +648,13 @@ void RenderingManager::setGlare(bool glare) mSkyManager->setGlare(glare); } -void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) +void RenderingManager::requestMap(MWWorld::CellStore* cell) { if (cell->mCell->isExterior()) { assert(mTerrain); - Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell); + Ogre::AxisAlignedBox dims = mObjects->getDimensions(cell); Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); @@ -667,23 +664,23 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else - mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); + mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); } -void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) +void RenderingManager::preCellChange(MWWorld::CellStore* cell) { mLocalMap->saveFogOfWar(cell); } void RenderingManager::disableLights(bool sun) { - mObjects.disableLights(); + mObjects->disableLights(); sunDisable(sun); } void RenderingManager::enableLights(bool sun) { - mObjects.enableLights(); + mObjects->enableLights(); sunEnable(sun); } @@ -694,14 +691,14 @@ Shadows* RenderingManager::getShadows() void RenderingManager::switchToInterior() { - // causes light flicker in opengl when moving.. - //mRendering.getScene()->setCameraRelativeRendering(false); + // TODO: also do this when switching worldspace + mEffectManager->clear(); } void RenderingManager::switchToExterior() { - // causes light flicker in opengl when moving.. - //mRendering.getScene()->setCameraRelativeRendering(true); + // TODO: also do this when switching worldspace + mEffectManager->clear(); } Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) @@ -743,11 +740,6 @@ Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) return Vector4(min_x, min_y, max_x, max_y); } -Compositors* RenderingManager::getCompositors() -{ - return mCompositors; -} - void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) { bool changeRes = false; @@ -762,7 +754,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec else if (it->second == "max viewing distance" && it->first == "Viewing distance") { if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior()) - configureFog(*MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()); + configureFog(*MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()); } else if (it->first == "Video" && ( it->second == "resolution x" @@ -793,7 +785,6 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec } else if (it->second == "shader" && it->first == "Water") { - applyCompositors(); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); rebuild = true; mRendering.getViewport ()->setClearEveryFrame (true); @@ -859,7 +850,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec if (rebuild) { - mObjects.rebuildStaticGeometry(); + mObjects->rebuildStaticGeometry(); if (mTerrain) mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); @@ -878,29 +869,19 @@ void RenderingManager::setMenuTransparency(float val) void RenderingManager::windowResized(int x, int y) { + Settings::Manager::setInt("resolution x", "Video", x); + Settings::Manager::setInt("resolution y", "Video", y); mRendering.adjustViewport(); - mCompositors->recreate(); mVideoPlayer->setResolution (x, y); MWBase::Environment::get().getWindowManager()->windowResized(x,y); } -void RenderingManager::applyCompositors() -{ -} - void RenderingManager::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) { - if (mCompositors->anyCompositorEnabled()) - { - mCompositors->countTrianglesBatches(triangles, batches); - } - else - { - triangles = mRendering.getWindow()->getTriangleCount(); - batches = mRendering.getWindow()->getBatchCount(); - } + batches = mRendering.getWindow()->getBatchCount(); + triangles = mRendering.getWindow()->getTriangleCount(); } void RenderingManager::setupPlayer(const MWWorld::Ptr &ptr) @@ -913,18 +894,15 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr) { if(!mPlayerAnimation) { - mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), - MWWorld::Class::get(ptr).getInventoryStore(ptr), - RV_Actors); + mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); } else { // Reconstruct the NpcAnimation in-place mPlayerAnimation->~NpcAnimation(); - new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), - MWWorld::Class::get(ptr).getInventoryStore(ptr), - RV_Actors); + new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); } + mCamera->setAnimation(mPlayerAnimation); mWater->removeEmitter(ptr); mWater->addEmitter(ptr); @@ -968,19 +946,51 @@ bool RenderingManager::isPositionExplored (float nX, float nY, int x, int y, boo return mLocalMap->isPositionExplored(nX, nY, x, y, interior); } -void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rendering) -{ - rendering.setup (mRendering.getScene()); -} - Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) { - Animation *anim = mActors.getAnimation(ptr); + Animation *anim = mActors->getAnimation(ptr); + if(!anim && ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; + + if (!anim) + anim = mObjects->getAnimation(ptr); + return anim; } +void RenderingManager::screenshot(Image &image, int w, int h) +{ + // Create a temporary render target. We do not use the RenderWindow since we want a specific size. + // Also, the GUI should not be visible (and it is only rendered on the RenderWindow's primary viewport) + const std::string tempName = "@temp"; + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(tempName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, w, h, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + + float oldAspect = mRendering.getCamera()->getAspectRatio(); + + mRendering.getCamera()->setAspectRatio(w / static_cast(h)); + + Ogre::RenderTarget* rt = texture->getBuffer()->getRenderTarget(); + Ogre::Viewport* vp = rt->addViewport(mRendering.getCamera()); + vp->setBackgroundColour(mRendering.getViewport()->getBackgroundColour()); + vp->setOverlaysEnabled(false); + vp->setVisibilityMask(mRendering.getViewport()->getVisibilityMask()); + rt->update(); + + Ogre::PixelFormat pf = rt->suggestPixelFormat(); + + std::vector data; + data.resize(w * h * Ogre::PixelUtil::getNumElemBytes(pf)); + + Ogre::PixelBox pb(w, h, 1, pf, &data[0]); + rt->copyContentsToMemory(pb); + + image.loadDynamicImage(&data[0], w, h, pf); + + Ogre::TextureManager::getSingleton().remove(tempName); + mRendering.getCamera()->setAspectRatio(oldAspect); +} void RenderingManager::playVideo(const std::string& name, bool allowSkipping) { @@ -1051,4 +1061,14 @@ void RenderingManager::enableTerrain(bool enable) mTerrain->setVisible(false); } +float RenderingManager::getCameraDistance() const +{ + return mCamera->getCameraDistance(); +} + +void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale) +{ + mEffectManager->addEffect(model, "", worldPosition, scale); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 2d0813912..64ec029ce 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDERING_MANAGER_H -#define _GAME_RENDERING_MANAGER_H +#ifndef GAME_RENDERING_MANAGER_H +#define GAME_RENDERING_MANAGER_H #include "sky.hpp" #include "debugging.hpp" @@ -21,10 +21,7 @@ namespace Ogre { - class SceneManager; class SceneNode; - class Quaternion; - class Vector3; } namespace MWWorld @@ -48,11 +45,10 @@ namespace MWRender class Shadows; class LocalMap; class Water; - class Compositors; - class ExternalRendering; class GlobalMap; class VideoPlayer; class Animation; + class EffectManager; class RenderingManager: private RenderingInterface, public Ogre::RenderTargetListener, public OEngine::Render::WindowSizeListener { @@ -91,12 +87,14 @@ public: bool vanityRotateCamera(const float *rot); void setCameraDistance(float dist, bool adjust = false, bool override = true); + float getCameraDistance() const; void setupPlayer(const MWWorld::Ptr &ptr); void renderPlayer(const MWWorld::Ptr &ptr); SkyManager* getSkyManager(); - Compositors* getCompositors(); + + MWRender::Camera* getCamera() const; void toggleLight(); bool toggleRenderMode(int mode); @@ -205,13 +203,14 @@ public: bool isPositionExplored (float nX, float nY, int x, int y, bool interior); ///< see MWRender::LocalMap::isPositionExplored - 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, bool paused); + void screenshot(Ogre::Image& image, int w, int h); + + void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition, float scale=1.f); protected: virtual void windowResized(int x, int y); @@ -224,8 +223,6 @@ private: void setMenuTransparency(float val); - void applyCompositors(); - bool mSunEnabled; MWWorld::Fallback* mFallback; @@ -242,8 +239,10 @@ private: OEngine::Render::OgreRenderer &mRendering; - MWRender::Objects mObjects; - MWRender::Actors mActors; + MWRender::Objects* mObjects; + MWRender::Actors* mActors; + + MWRender::EffectManager* mEffectManager; MWRender::NpcAnimation *mPlayerAnimation; @@ -269,8 +268,6 @@ private: MWRender::Shadows* mShadows; - MWRender::Compositors* mCompositors; - VideoPlayer* mVideoPlayer; }; diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 47fbc8ddf..e5db8346f 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -10,8 +10,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" - namespace MWRender { @@ -154,11 +152,11 @@ void RippleSimulation::addImpulses() /// \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 ()) + if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) { // 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 (); + it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } float* _currentPos = it->mPtr.getRefData().getPosition().pos; Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]); diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 21bbe51b6..9ebb0ab08 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 03e14dc07..39f7ccc85 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -285,10 +285,10 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); - for(size_t i = 0, matidx = 0;i < objects.mEntities.size();i++) + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++) { - Entity* night1_ent = objects.mEntities[i]; + Entity* night1_ent = objects->mEntities[i]; night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); @@ -307,14 +307,14 @@ void SkyManager::create() night1_ent->getSubEntity(j)->setMaterialName(matName); } } - + mObjects.push_back(objects); // Atmosphere (day) mAtmosphereDay = mRootNode->createChildSceneNode(); objects = NifOgre::Loader::createObjects(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); - for(size_t i = 0;i < objects.mEntities.size();i++) + for(size_t i = 0;i < objects->mEntities.size();i++) { - Entity* atmosphere_ent = objects.mEntities[i]; + Entity* atmosphere_ent = objects->mEntities[i]; atmosphere_ent->setCastShadows(false); atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); atmosphere_ent->setVisibilityFlags(RV_Sky); @@ -325,14 +325,14 @@ void SkyManager::create() // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions atmosphere_ent->getMesh()->_setBounds (aabInf); } - + mObjects.push_back(objects); // Clouds SceneNode* clouds_node = mRootNode->createChildSceneNode(); objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif"); - for(size_t i = 0;i < objects.mEntities.size();i++) + for(size_t i = 0;i < objects->mEntities.size();i++) { - Entity* clouds_ent = objects.mEntities[i]; + Entity* clouds_ent = objects->mEntities[i]; clouds_ent->setVisibilityFlags(RV_Sky); clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); for(unsigned int j = 0;j < clouds_ent->getNumSubEntities();j++) @@ -341,6 +341,7 @@ void SkyManager::create() // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions clouds_ent->getMesh()->_setBounds (aabInf); } + mObjects.push_back(objects); mCreated = true; } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 3df8846cd..965907a97 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -11,6 +11,8 @@ #include +#include + #include "../mwworld/weather.hpp" @@ -196,6 +198,8 @@ namespace MWRender Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; + std::vector mObjects; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 318627fc7..197572db9 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,5 +1,14 @@ #include "terrainstorage.hpp" +#include +#include +#include +#include +#include +#include + +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" @@ -53,4 +62,509 @@ namespace MWRender return esmStore.get().find(index, plugin); } + bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + { + assert (size <= 1 && "TerrainStorage::getMinMaxHeights, chunk size should be <= 1 cell"); + + /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int cellX = origin.x; + int cellY = origin.y; + + const ESM::Land* land = getLand(cellX, cellY); + if (!land) + return false; + + min = std::numeric_limits().max(); + max = -std::numeric_limits().max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } + } + return true; + } + + void TerrainStorage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + } + + void TerrainStorage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + { + Ogre::Vector3 n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row); + fixNormal(n2, cellX, cellY, col-1, row); + fixNormal(n3, cellX, cellY, col, row+1); + fixNormal(n4, cellX, cellY, col, row-1); + normal = (n1+n2+n3+n4); + normal.normalise(); + } + + void TerrainStorage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mLandData->mUsingColours) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + } + + void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + Ogre::HardwareVertexBufferSharedPtr vertexBuffer, + Ogre::HardwareVertexBufferSharedPtr normalBuffer, + Ogre::HardwareVertexBufferSharedPtr colourBuffer) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = 1 << lodLevel; + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int startX = origin.x; + int startY = origin.y; + + size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; + + std::vector colors; + colors.resize(numVerts*numVerts*4); + std::vector positions; + positions.resize(numVerts*numVerts*3); + std::vector normals; + normals.resize(numVerts*numVerts*3); + + Ogre::Vector3 normal; + Ogre::ColourValue color; + + float vertY; + float vertX; + + float vertY_ = 0; // of current cell corner + for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + { + ESM::Land* land = getLand(cellX, cellY); + if (land && !land->mHasData) + land = NULL; + bool hasColors = land && land->mLandData->mUsingColours; + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + if (colStart == 0 && vertY_ != 0) + colStart += increment; + if (rowStart == 0 && vertX_ != 0) + rowStart += increment; + + vertY = vertY_; + for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + else + positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; + + if (land) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row); + + assert(normal.z > 0); + + normals[vertX*numVerts*3 + vertY*3] = normal.x; + normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; + normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; + + if (hasColors) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row); + + color.a = 1; + Ogre::uint32 rsColor; + Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); + memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area + + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true); + } + + TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY, + int x, int y) + { + // For the first/last row/column, we need to get the texture from the neighbour cell + // to get consistent blending at the borders + --x; + if (x < 0) + { + --cellX; + x += ESM::Land::LAND_TEXTURE_SIZE; + } + if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? + { + ++cellY; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + + assert(xisDataLoaded(ESM::Land::DATA_VTEX)) + land->loadData(ESM::Land::DATA_VTEX); + + int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin + return std::make_pair(tex, land->mPlugin); + } + else + return std::make_pair(0,0); + } + + std::string TerrainStorage::getTextureName(UniqueTextureId id) + { + if (id.first == 0) + return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded? + + // NB: All vtex ids are +1 compared to the ltex ids + const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + + std::string texture = ltex->mTexture; + //TODO this is needed due to MWs messed up texture handling + texture = texture.substr(0, texture.rfind(".")) + ".dds"; + + return texture; + } + + void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + // TODO - blending isn't completely right yet; the blending radius appears to be + // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap + // and interpolate the rest of the cell by hand? :/ + + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); + int cellX = origin.x; + int cellY = origin.y; + + // Save the used texture indices so we know the total number of textures + // and number of required blend maps + std::set textureIndices; + // Due to the way the blending works, the base layer will always shine through in between + // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). + // To get a consistent look, we need to make sure to use the same base layer in all cells. + // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. + textureIndices.insert(std::make_pair(0,0)); + + for (int y=0; y textureIndicesMap; + for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) + { + int size = textureIndicesMap.size(); + textureIndicesMap[*it] = size; + layerList.push_back(getLayerInfo(getTextureName(*it))); + } + + int numTextures = textureIndices.size(); + // numTextures-1 since the base layer doesn't need blending + int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); + + int channels = pack ? 4 : 1; + + // Second iteration - create and fill in the blend maps + const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; + std::vector data; + data.resize(blendmapSize * blendmapSize * channels, 0); + + for (int i=0; isecond; + int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); + int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; + + if (blendIndex == i) + data[y*blendmapSize*channels + x*channels + channel] = 255; + else + data[y*blendmapSize*channels + x*channels + channel] = 0; + } + } + + // All done, upload to GPU + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + map->loadRawData(stream, blendmapSize, blendmapSize, format); + blendmaps.push_back(map); + } + } + + float TerrainStorage::getHeightAt(const Ogre::Vector3 &worldPos) + { + int cellX = std::floor(worldPos.x / 8192.f); + int cellY = std::floor(worldPos.y / 8192.f); + + ESM::Land* land = getLand(cellX, cellY); + if (!land) + return -2048; + + // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition + + // Normalized position in the cell + float nX = (worldPos.x - (cellX * 8192))/8192.f; + float nY = (worldPos.y - (cellY * 8192))/8192.f; + + // get left / bottom points (rounded down) + float factor = ESM::Land::LAND_SIZE - 1.0f; + float invFactor = 1.0f / factor; + + int startX = static_cast(nX * factor); + int startY = static_cast(nY * factor); + int endX = startX + 1; + int endY = startY + 1; + + assert(endX < ESM::Land::LAND_SIZE); + assert(endY < ESM::Land::LAND_SIZE); + + // now get points in terrain space (effectively rounding them to boundaries) + float startXTS = startX * invFactor; + float startYTS = startY * invFactor; + float endXTS = endX * invFactor; + float endYTS = endY * invFactor; + + // get parametric from start coord to next point + float xParam = (nX - startXTS) * factor; + float yParam = (nY - startYTS) * factor; + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + + // Build all 4 positions in normalized cell space, using point-sampled height + Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + // define this plane in terrain space + Ogre::Plane plane; + // (At the moment, all rows have the same triangle alignment) + if (true) + { + // odd row + bool secondTri = ((1.0 - yParam) > xParam); + if (secondTri) + plane.redefine(v0, v1, v3); + else + plane.redefine(v1, v2, v3); + } + else + { + // even row + bool secondTri = (yParam > xParam); + if (secondTri) + plane.redefine(v0, v2, v3); + else + plane.redefine(v0, v1, v2); + } + + // Solve plane equation for z + return (-plane.normal.x * nX + -plane.normal.y * nY + - plane.d) / plane.normal.z * 8192; + + } + + float TerrainStorage::getVertexHeight(const ESM::Land *land, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + + Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + Terrain::LayerInfo info; + info.mParallax = false; + info.mSpecular = false; + info.mDiffuseMap = "textures\\" + texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mNormalMap = "textures\\" + texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + info.mNormalMap = "textures\\" + texture_; + } + + texture_ = texture; + boost::replace_last(texture_, ".", "_diffusespec."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mDiffuseMap = "textures\\" + texture_; + info.mSpecular = true; + } + + mLayerInfoMap[texture] = info; + + return info; + } + + Terrain::LayerInfo TerrainStorage::getDefaultLayer() + { + Terrain::LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + info.mSpecular = false; + return info; + } + + float TerrainStorage::getCellWorldSize() + { + return ESM::Land::REAL_SIZE; + } + + int TerrainStorage::getCellVertices() + { + return ESM::Land::LAND_SIZE; + } + } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index ebf5e26ab..5c2035952 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -12,8 +12,75 @@ namespace MWRender virtual ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: + + /// Get bounds of the whole terrain in cell units virtual Ogre::AxisAlignedBox getBounds(); - ///< Get bounds in cell units + + /// Get the minimum and maximum heights of a terrain chunk. + /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Larger chunks can simply merge AABB of children. + /// @param size size of the chunk in cell units + /// @param center center of the chunk in cell units + /// @param min min height will be stored here + /// @param max max height will be stored here + /// @return true if there was data available for this terrain chunk + virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + + /// Fill vertex buffers for a terrain chunk. + /// @param lodLevel LOD level, 0 = most detailed + /// @param size size of the terrain chunk in cell units + /// @param center center of the chunk in cell units + /// @param vertexBuffer buffer to write vertices + /// @param normalBuffer buffer to write vertex normals + /// @param colourBuffer buffer to write vertex colours + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + Ogre::HardwareVertexBufferSharedPtr vertexBuffer, + Ogre::HardwareVertexBufferSharedPtr normalBuffer, + Ogre::HardwareVertexBufferSharedPtr colourBuffer); + + /// Create textures holding layer blend values for a terrain chunk. + /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @param chunkSize size of the terrain chunk in cell units + /// @param chunkCenter center of the chunk in cell units + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + /// @param blendmaps created blendmaps will be written here + /// @param layerList names of the layer textures used will be written here + virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + + virtual float getHeightAt (const Ogre::Vector3& worldPos); + + virtual Terrain::LayerInfo getDefaultLayer(); + + /// Get the transformation factor for mapping cell units to world units. + virtual float getCellWorldSize(); + + /// Get the number of vertices on one side for each cell. Should be (power of two)+1 + virtual int getCellVertices(); + + private: + void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); + void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + float getVertexHeight (const ESM::Land* land, int x, int y); + + // Since plugins can define new texture palettes, we need to know the plugin index too + // in order to retrieve the correct texture name. + // pair + typedef std::pair UniqueTextureId; + + UniqueTextureId getVtexIndexAt(int cellX, int cellY, + int x, int y); + std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + Terrain::LayerInfo getLayerInfo(const std::string& texture); }; } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index ee2b80f73..adf20dc63 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -949,9 +949,25 @@ void VideoState::init(const std::string& resourceName) this->format_ctx->pb = ioCtx; // Open video file - /// \todo leak here, ffmpeg or valgrind bug ? + /// + /// format_ctx->pb->buffer must be freed by hand, + /// if not, valgrind will show memleak, see: + /// + /// https://trac.ffmpeg.org/ticket/1357 + /// if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) { + if (this->format_ctx != NULL) + { + if (this->format_ctx->pb != NULL) + { + av_free(this->format_ctx->pb->buffer); + this->format_ctx->pb->buffer = NULL; + + av_free(this->format_ctx->pb); + this->format_ctx->pb = NULL; + } + } // "Note that a user-supplied AVFormatContext will be freed on failure." this->format_ctx = NULL; av_free(ioCtx); @@ -989,9 +1005,12 @@ void VideoState::deinit() this->audioq.cond.notify_one(); this->videoq.cond.notify_one(); - this->parse_thread.join(); - this->video_thread.join(); - this->refresh_thread.join(); + if (this->parse_thread.joinable()) + this->parse_thread.join(); + if (this->video_thread.joinable()) + this->video_thread.join(); + if (this->refresh_thread.joinable()) + this->refresh_thread.join(); if(this->audio_st) avcodec_close((*this->audio_st)->codec); @@ -1006,9 +1025,21 @@ void VideoState::deinit() if(this->format_ctx) { - AVIOContext *ioContext = this->format_ctx->pb; + /// + /// format_ctx->pb->buffer must be freed by hand, + /// if not, valgrind will show memleak, see: + /// + /// https://trac.ffmpeg.org/ticket/1357 + /// + if (this->format_ctx->pb != NULL) + { + av_free(this->format_ctx->pb->buffer); + this->format_ctx->pb->buffer = NULL; + + av_free(this->format_ctx->pb); + this->format_ctx->pb = NULL;; + } avformat_close_input(&this->format_ctx); - av_free(ioContext); } } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 082551f37..9e3105168 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "sky.hpp" #include "renderingmanager.hpp" @@ -292,6 +293,7 @@ Water::~Water() delete mReflection; delete mRefraction; + delete mSimulation; } void Water::changeCell(const ESM::Cell* cell) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index bc15b4980..481a41297 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -133,8 +133,6 @@ namespace MWRender { RenderingManager* mRendering; SkyManager* mSky; - std::string mCompositorName; - Ogre::MaterialPtr mMaterial; bool mUnderwaterEffect; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index fac44c08f..8314d011a 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -16,12 +16,18 @@ #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" +#include "../mwmechanics/aicombat.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "interpretercontext.hpp" #include "ref.hpp" #include +#include "../mwbase/mechanicsmanager.hpp" + namespace MWScript { namespace Ai @@ -220,7 +226,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex)); + runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting ( + (MWMechanics::CreatureStats::AiSetting)mIndex).getModified()); } }; template @@ -236,8 +243,11 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex, - MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex) + value); + MWMechanics::CreatureStats::AiSetting setting + = MWMechanics::CreatureStats::AiSetting(mIndex); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (setting, + MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (setting).getBase() + value); } }; template @@ -253,8 +263,11 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex, - value); + MWMechanics::CreatureStats::AiSetting setting = (MWMechanics::CreatureStats::AiSetting)mIndex; + + MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(setting); + stat.setModified(value, 0); + ptr.getClass().getCreatureStats(ptr).setAiSetting(setting, stat); } }; @@ -339,31 +352,121 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getTypeId (); + Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getLastRunTypeId(); runtime.push (value); } }; template - class OpGetDetected : public Interpreter::Opcode1 + class OpGetDetected : public Interpreter::Opcode0 { public: - virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + virtual void execute (Interpreter::Runtime& runtime) { - + MWWorld::Ptr observer = R()(runtime); std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - Interpreter::Type_Integer value = false; // TODO replace with implementation + MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true); + + Interpreter::Type_Integer value = + MWBase::Environment::get().getWorld()->getLOS(observer, actor) && + MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); + + runtime.push (value); + } + }; + + template + class OpGetLineOfSight : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr source = R()(runtime); - std::cout << "AiGetDetected: " << actorID << ", " << value << std::endl; + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true); + bool value = false; + if(dest != MWWorld::Ptr() ) + { + value = MWBase::Environment::get().getWorld()->getLOS(source,dest); + } runtime.push (value); } }; + template + class OpGetTarget : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime &runtime) + { + MWWorld::Ptr actor = R()(runtime); + std::string testedTargetId = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); + std::string currentTargetId; + + bool targetsAreEqual = false; + if (creatureStats.getAiSequence().getCombatTarget (currentTargetId)) + { + if (currentTargetId == testedTargetId) + targetsAreEqual = true; + } + runtime.push(int(targetsAreEqual)); + } + }; + + template + class OpStartCombat : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime &runtime) + { + MWWorld::Ptr actor = R()(runtime); + std::string targetID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); + + + creatureStats.setHostile(true); + creatureStats.getAiSequence().stack( + MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) )); + } + }; + + template + class OpStopCombat : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr actor = R()(runtime); + MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); + creatureStats.getAiSequence().stopCombat(); + creatureStats.setHostile(false); + } + }; + + template + class OpToggleAI : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getMechanicsManager()->toggleAI(); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -387,8 +490,19 @@ namespace MWScript new OpGetAiPackageDone); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); - interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); - interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeGetTarget, new OpGetTarget); + interpreter.installSegment5 (Compiler::Ai::opcodeGetTargetExplicit, new OpGetTarget); + interpreter.installSegment5 (Compiler::Ai::opcodeStartCombat, new OpStartCombat); + interpreter.installSegment5 (Compiler::Ai::opcodeStartCombatExplicit, new OpStartCombat); + interpreter.installSegment5 (Compiler::Ai::opcodeStopCombat, new OpStopCombat); + interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAIExplicit, new OpToggleAI); + interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(1)); diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 316f912da..a0acfa4da 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -12,7 +12,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" - #include "../mwworld/player.hpp" #include "interpretercontext.hpp" @@ -43,10 +42,13 @@ namespace MWScript ESM::Position pos; MWBase::World *world = MWBase::Environment::get().getWorld(); - if (world->findExteriorPosition(cell, pos)) { + world->getPlayer().setTeleported(true); + if (world->findExteriorPosition(cell, pos)) + { world->changeToExteriorCell(pos); } - else { + else + { // Change to interior even if findInteriorPosition() // yields false. In this case position will be zero-point. world->findInteriorPosition(cell, pos); @@ -68,13 +70,14 @@ namespace MWScript runtime.pop(); ESM::Position pos; - - MWBase::Environment::get().getWorld()->indexToPosition (x, y, pos.pos[0], pos.pos[1], true); + MWBase::World *world = MWBase::Environment::get().getWorld(); + world->getPlayer().setTeleported(true); + world->indexToPosition (x, y, pos.pos[0], pos.pos[1], true); pos.pos[2] = 0; pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - MWBase::Environment::get().getWorld()->changeToExteriorCell (pos); + world->changeToExteriorCell (pos); } }; @@ -85,7 +88,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { bool interior = - !MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->isExterior(); + !MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell->isExterior(); runtime.push (interior ? 1 : 0); } @@ -100,17 +103,19 @@ namespace MWScript std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell; + const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell; std::string current = cell->mName; - if (!(cell->mData.mFlags & ESM::Cell::Interior) && current.empty()) + if (!(cell->mData.mFlags & ESM::Cell::Interior) && current.empty() + && !cell->mRegion.empty()) { const ESM::Region *region = MWBase::Environment::get().getWorld()->getStore().get().find (cell->mRegion); current = region->mName; } + Misc::StringUtils::toLower(current); bool match = current.length()>=name.length() && current.substr (0, name.length())==name; @@ -125,8 +130,11 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); - runtime.push (cell->mWaterLevel); + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + if (cell->mCell->hasWater()) + runtime.push (cell->mWaterLevel); + else + runtime.push (-std::numeric_limits().max()); } }; @@ -138,7 +146,7 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->mCell->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); @@ -156,7 +164,7 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->mCell->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 2f3ef2d79..fc21c5712 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -17,12 +17,10 @@ #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" @@ -53,26 +51,14 @@ namespace MWScript 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(), ptr); + MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) - if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() ) + if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() ) { // 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()); + std::string itemName = itemPtr.getClass().getName(itemPtr); if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); @@ -84,7 +70,7 @@ namespace MWScript msgBox = boost::str(boost::format(msgBox) % count % itemName); } std::vector noButtons; - MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true); + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only); } } }; @@ -101,15 +87,9 @@ namespace MWScript std::string item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); - - Interpreter::Type_Integer sum = 0; - - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) - sum += iter->getRefData().getCount(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); - runtime.push (sum); + runtime.push (store.count(item)); } }; @@ -136,41 +116,21 @@ namespace MWScript 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) - { + std::string itemName; + for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) - { - itemName = MWWorld::Class::get(*iter).getName(*iter); - - if (iter->getRefData().getCount()<=count) - { - count -= iter->getRefData().getCount(); - iter->getRefData().setCount (0); - } - else - { - iter->getRefData().setCount (iter->getRefData().getCount()-count); - count = 0; - } - } - } - + itemName = iter->getClass().getName(*iter); + + int numRemoved = store.remove(item, count, ptr); + // Spawn a messagebox (only for items removed from player's inventory) - if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer()) + if ((numRemoved > 0) + && (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())) { // 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 == 0) - return; - + if(numRemoved > 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); @@ -182,7 +142,7 @@ namespace MWScript msgBox = boost::str (boost::format(msgBox) % itemName); } std::vector noButtons; - MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true); + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only); } } }; @@ -199,7 +159,7 @@ namespace MWScript std::string item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ContainerStoreIterator it = invStore.begin(); for (; it != invStore.end(); ++it) { @@ -211,6 +171,9 @@ namespace MWScript MWWorld::ActionEquip action (*it); action.execute(ptr); + + if (ptr.getRefData().getHandle() == "player" && !ptr.getClass().getScript(ptr).empty()) + ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1); } }; @@ -322,7 +285,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - const std::string &name = 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); diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index e46302b18..d2e774859 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -11,7 +11,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" @@ -77,34 +76,34 @@ namespace MWScript template class OpClearMovementFlag : public Interpreter::Opcode0 { - MWMechanics::NpcStats::Flag mFlag; + MWMechanics::CreatureStats::Flag mFlag; public: - OpClearMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {} + OpClearMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, false); + ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, false); } }; template class OpSetMovementFlag : public Interpreter::Opcode0 { - MWMechanics::NpcStats::Flag mFlag; + MWMechanics::CreatureStats::Flag mFlag; public: - OpSetMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {} + OpSetMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, true); + ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, true); } }; @@ -117,9 +116,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr); - - runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); } }; @@ -132,9 +130,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr); - - runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); } }; @@ -144,8 +141,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); - runtime.push (MWWorld::Class::get(ptr).getStance (ptr, MWWorld::Class::Run)); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + runtime.push (ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)); } }; @@ -155,8 +152,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); - runtime.push (MWWorld::Class::get(ptr).getStance (ptr, MWWorld::Class::Sneak)); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + runtime.push (ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)); } }; @@ -173,22 +170,22 @@ namespace MWScript interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision); interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun, - new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeForceRun, - new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, - new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, - new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit, - new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit, - new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit, - new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit, - new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning); interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun); diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 5e797ee58..a882ae05e 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -13,7 +13,6 @@ #include "../mwbase/journal.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" #include "interpretercontext.hpp" @@ -183,7 +182,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).isSameFaction (MWWorld::Class::get(player).getNpcStats (player))); } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 7bbb33699..25f3c38aa 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -127,7 +127,6 @@ op 0x200007e-0x2000084: Enable Controls op 0x2000085-0x200008b: Disable Controls op 0x200008c: Unlock op 0x200008d: Unlock, explicit reference -op 0x200008e: COE op 0x200008e-0x20000a8: GetSkill op 0x20000a9-0x20000c3: GetSkill, explicit reference op 0x20000c4-0x20000de: SetSkill @@ -354,5 +353,33 @@ op 0x200021e: ShowVarsExplicit op 0x200021f: ToggleGodMode op 0x2000220: DisableLevitation op 0x2000221: EnableLevitation +op 0x2000222: GetLineOfSight +op 0x2000223: GetLineOfSightExplicit +op 0x2000224: ToggleAI +op 0x2000225: ToggleAIExplicit +op 0x2000226: COE +op 0x2000227: Cast +op 0x2000228: Cast, explicit +op 0x2000229: ExplodeSpell +op 0x200022a: ExplodeSpell, explicit +op 0x200022b: RemoveSpellEffects +op 0x200022c: RemoveSpellEffects, explicit +op 0x200022d: RemoveEffects +op 0x200022e: RemoveEffects, explicit +op 0x200022f: Resurrect +op 0x2000230: Resurrect, explicit +op 0x2000231: GetSpellReadied +op 0x2000232: GetSpellReadied, explicit +op 0x2000233: GetPcJumping +op 0x2000234: ShowRestMenu, explicit +op 0x2000235: GoToJail +op 0x2000236: PayFine +op 0x2000237: PayFineThief +op 0x2000238: GetTarget +op 0x2000239: GetTargetExplicit +op 0x200023a: StartCombat +op 0x200023b: StartCombatExplicit +op 0x200023c: StopCombat +op 0x200023d: StopCombatExplicit -opcodes 0x2000222-0x3ffffff unused +opcodes 0x200023e-0x3ffffff unused diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 608725ae6..8f269a015 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -3,6 +3,9 @@ #include +#include +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -15,25 +18,16 @@ namespace MWScript GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) { - reset(); - } - - void GlobalScripts::reset() - { - mScripts.clear(); - addScript ("Main"); - - MWWorld::Store::iterator iter = - mStore.get().begin(); - - for (; iter != mStore.get().end(); ++iter) { - addScript (iter->mScript); - } + addStartup(); } void GlobalScripts::addScript (const std::string& name) { - if (mScripts.find (name)==mScripts.end()) + std::map >::iterator iter = + mScripts.find (Misc::StringUtils::lowerCase (name)); + + if (iter==mScripts.end()) + { if (const ESM::Script *script = mStore.get().find (name)) { Locals locals; @@ -42,11 +36,15 @@ namespace MWScript mScripts.insert (std::make_pair (name, std::make_pair (true, locals))); } + } + else + iter->second.first = true; } void GlobalScripts::removeScript (const std::string& name) { - std::map >::iterator iter = mScripts.find (name); + std::map >::iterator iter = + mScripts.find (Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) iter->second.first = false; @@ -55,7 +53,7 @@ namespace MWScript bool GlobalScripts::isRunning (const std::string& name) const { std::map >::const_iterator iter = - mScripts.find (name); + mScripts.find (Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) return false; @@ -76,4 +74,78 @@ namespace MWScript } } } + + void GlobalScripts::clear() + { + mScripts.clear(); + } + + void GlobalScripts::addStartup() + { + addScript ("main"); + + for (MWWorld::Store::iterator iter = + mStore.get().begin(); + iter != mStore.get().end(); ++iter) + { + addScript (iter->mScript); + } + } + + int GlobalScripts::countSavedGameRecords() const + { + return mScripts.size(); + } + + void GlobalScripts::write (ESM::ESMWriter& writer) const + { + for (std::map >::const_iterator iter (mScripts.begin()); + iter!=mScripts.end(); ++iter) + { + ESM::GlobalScript script; + + script.mId = iter->first; + + iter->second.second.write (script.mLocals, iter->first); + + script.mRunning = iter->second.first ? 1 : 0; + + writer.startRecord (ESM::REC_GSCR); + script.save (writer); + writer.endRecord (ESM::REC_GSCR); + } + } + + bool GlobalScripts::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type==ESM::REC_GSCR) + { + ESM::GlobalScript script; + script.load (reader); + + std::map >::iterator iter = + mScripts.find (script.mId); + + if (iter==mScripts.end()) + { + if (const ESM::Script *scriptRecord = mStore.get().search (script.mId)) + { + std::pair data (false, Locals()); + + data.second.configure (*scriptRecord); + + iter = mScripts.insert (std::make_pair (script.mId, data)).first; + } + else // script does not exist anymore + return true; + } + + iter->second.first = script.mRunning!=0; + iter->second.second.read (script.mLocals, script.mId); + + return true; + } + + return false; + } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 628919d1d..cf716c8e4 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -4,9 +4,17 @@ #include #include +#include + #include "locals.hpp" -namespace MWWorld +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + +namespace MWWorld { struct ESMStore; } @@ -22,8 +30,6 @@ namespace MWScript GlobalScripts (const MWWorld::ESMStore& store); - void reset(); - void addScript (const std::string& name); void removeScript (const std::string& name); @@ -32,6 +38,20 @@ namespace MWScript void run(); ///< run all active global scripts + + void clear(); + + void addStartup(); + ///< Add startup script + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); + ///< Records for variables that do not exist are dropped silently. + /// + /// \return Known type? }; } diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 6c89a0d1c..ab8901881 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -1,8 +1,6 @@ #include "guiextensions.hpp" -#include - #include #include @@ -15,7 +13,10 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" + #include "interpretercontext.hpp" +#include "ref.hpp" namespace MWScript { @@ -45,6 +46,20 @@ namespace MWScript } }; + template + class OpShowRestMenu : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr bed = R()(runtime, false); + + if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWBase::Environment::get().getWorld()->getPlayerPtr(), + bed)) + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_RestBed); + } + }; + class OpShowDialogue : public Interpreter::Opcode0 { MWGui::GuiMode mDialogue; @@ -172,7 +187,8 @@ namespace MWScript new OpEnableRest ()); interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenu, - new OpShowDialogue (MWGui::GM_RestBed)); + new OpShowRestMenu); + interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenuExplicit, new OpShowRestMenu); interpreter.installSegment5 (Compiler::Gui::opcodeGetButtonPressed, new OpGetButtonPressed); diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index b8fc9ed47..10e98e398 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -14,7 +14,6 @@ #include "../mwbase/inputmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" @@ -24,7 +23,7 @@ namespace MWScript { MWWorld::Ptr InterpreterContext::getReference ( - const std::string& id, bool activeOnly) + const std::string& id, bool activeOnly, bool doThrow) { if (!id.empty()) { @@ -32,7 +31,7 @@ namespace MWScript } else { - if (mReference.isEmpty()) + if (mReference.isEmpty() && doThrow) throw std::runtime_error ("no implicit reference"); return mReference; @@ -40,7 +39,7 @@ namespace MWScript } const MWWorld::Ptr InterpreterContext::getReference ( - const std::string& id, bool activeOnly) const + const std::string& id, bool activeOnly, bool doThrow) const { if (!id.empty()) { @@ -48,7 +47,7 @@ namespace MWScript } else { - if (mReference.isEmpty()) + if (mReference.isEmpty() && doThrow) throw std::runtime_error ("no implicit reference"); return mReference; @@ -127,61 +126,49 @@ namespace MWScript int InterpreterContext::getGlobalShort (const std::string& name) const { - return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort; + return MWBase::Environment::get().getWorld()->getGlobalInt (name); } int InterpreterContext::getGlobalLong (const std::string& name) const { // a global long is internally a float. - return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong; + return MWBase::Environment::get().getWorld()->getGlobalInt (name); } float InterpreterContext::getGlobalFloat (const std::string& name) const { - return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat; + return MWBase::Environment::get().getWorld()->getGlobalFloat (name); } void InterpreterContext::setGlobalShort (const std::string& name, int value) { - if (name=="gamehour") - MWBase::Environment::get().getWorld()->setHour (value); - else if (name=="day") - MWBase::Environment::get().getWorld()->setDay (value); - else if (name=="month") - MWBase::Environment::get().getWorld()->setMonth (value); - else - MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort = value; + MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalLong (const std::string& name, int value) { - if (name=="gamehour") - MWBase::Environment::get().getWorld()->setHour (value); - else if (name=="day") - MWBase::Environment::get().getWorld()->setDay (value); - else if (name=="month") - MWBase::Environment::get().getWorld()->setMonth (value); - else - MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong = value; + MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalFloat (const std::string& name, float value) { - if (name=="gamehour") - MWBase::Environment::get().getWorld()->setHour (value); - else if (name=="day") - MWBase::Environment::get().getWorld()->setDay (value); - else if (name=="month") - MWBase::Environment::get().getWorld()->setMonth (value); - else - MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat = value; + MWBase::Environment::get().getWorld()->setGlobalFloat (name, value); } - std::vector InterpreterContext::getGlobals () const + std::vector InterpreterContext::getGlobals() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - return world->getGlobals(); + std::vector ids; + + const MWWorld::Store& globals = + MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); + ++iter) + { + ids.push_back (iter->mId); + } + + return ids; } char InterpreterContext::getGlobalType (const std::string& name) const @@ -248,28 +235,28 @@ namespace MWScript std::string InterpreterContext::getPCName() const { MWBase::World *world = MWBase::Environment::get().getWorld(); - ESM::NPC player = *world->getPlayer().getPlayer().get()->mBase; + ESM::NPC player = *world->getPlayerPtr().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; + std::string race = world->getPlayerPtr().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; + std::string class_ = world->getPlayerPtr().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(); + MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; @@ -288,7 +275,7 @@ namespace MWScript std::string InterpreterContext::getPCNextRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; @@ -316,14 +303,13 @@ namespace MWScript int InterpreterContext::getPCBounty() const { MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); return MWWorld::Class::get (player).getNpcStats (player).getBounty(); } std::string InterpreterContext::getCurrentCellName() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - return world->getCurrentCellName(); + return MWBase::Environment::get().getWorld()->getCellName(); } bool InterpreterContext::isScriptRunning (const std::string& name) const @@ -387,7 +373,7 @@ namespace MWScript if (!mAction.get()) throw std::runtime_error ("activation failed, because no action to perform"); - mAction->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mAction->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); mActivationHandled = true; } @@ -499,8 +485,8 @@ namespace MWScript ptr.getRefData().getLocals().mFloats[index] = value; } - MWWorld::Ptr InterpreterContext::getReference() + MWWorld::Ptr InterpreterContext::getReference(bool required) { - return getReference ("", true); + return getReference ("", true, required); } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 9c7b443ae..04546faed 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -33,9 +33,9 @@ namespace MWScript bool mActivationHandled; boost::shared_ptr mAction; - MWWorld::Ptr getReference (const std::string& id, bool activeOnly); + MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true); - const MWWorld::Ptr getReference (const std::string& id, bool activeOnly) const; + const MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true) const; public: @@ -150,7 +150,7 @@ namespace MWScript virtual void setMemberFloat (const std::string& id, const std::string& name, float value); - MWWorld::Ptr getReference(); + MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) }; } diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 180a2791b..094fe085a 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -1,6 +1,8 @@ #include "locals.hpp" #include +#include +#include #include @@ -65,4 +67,68 @@ namespace MWScript } return false; } + + void Locals::write (ESM::Locals& locals, const std::string& script) const + { + const Compiler::Locals& declarations = + MWBase::Environment::get().getScriptManager()->getLocals(script); + + for (int i=0; i<3; ++i) + { + char type = 0; + + switch (i) + { + case 0: type = 's'; break; + case 1: type = 'l'; break; + case 2: type = 'f'; break; + } + + const std::vector& names = declarations.get (type); + + for (int i2=0; i2 (names.size()); ++i2) + { + ESM::Variant value; + + switch (i) + { + case 0: value.setType (ESM::VT_Int); value.setInteger (mShorts.at (i2)); break; + case 1: value.setType (ESM::VT_Int); value.setInteger (mLongs.at (i2)); break; + case 2: value.setType (ESM::VT_Float); value.setFloat (mFloats.at (i2)); break; + } + + locals.mVariables.push_back (std::make_pair (names[i2], value)); + } + } + } + + void Locals::read (const ESM::Locals& locals, const std::string& script) + { + const Compiler::Locals& declarations = + MWBase::Environment::get().getScriptManager()->getLocals(script); + + for (std::vector >::const_iterator iter + = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter) + { + char type = declarations.getType (iter->first); + char index = declarations.getIndex (iter->first); + + try + { + switch (type) + { + case 's': mShorts.at (index) = iter->second.getInteger(); break; + case 'l': mLongs.at (index) = iter->second.getInteger(); break; + case 'f': mFloats.at (index) = iter->second.getFloat(); break; + + // silently ignore locals that don't exist anymore + } + } + catch (...) + { + // ignore type changes + /// \todo write to log + } + } + } } diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index deae0d44e..d17d1be2d 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -8,6 +8,7 @@ namespace ESM { struct Script; + struct Locals; } namespace MWScript @@ -23,6 +24,9 @@ namespace MWScript bool setVarByInt(const std::string& script, const std::string& var, int val); int getIntVar (const std::string& script, const std::string& var); ///< if var does not exist, returns 0 + void write (ESM::Locals& locals, const std::string& script) const; + + void read (const ESM::Locals& locals, const std::string& script); }; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 4ae1136e2..d117c8ded 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -18,12 +18,11 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -58,6 +57,18 @@ namespace MWScript } }; + class OpGetPcJumping : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayerPtr(); + runtime.push (!world->isOnGround(player) && !world->isFlying(player)); + } + }; + class OpWakeUpPc : public Interpreter::Opcode0 { public: @@ -348,14 +359,8 @@ namespace MWScript 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(), ptr); - + MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); + item.getCellRef().mSoul = creature; } }; @@ -366,24 +371,18 @@ namespace MWScript 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) + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) + if (::Misc::StringUtils::ciEqual(it->getCellRef().mSoul, soul)) { - if (iter->getRefData().getCount() <= 1) - iter->getRefData().setCount (0); - else - iter->getRefData().setCount (iter->getRefData().getCount() - 1); - break; + store.remove(*it, 1, ptr); + return; } } } @@ -415,24 +414,18 @@ namespace MWScript MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + int toRemove = amount; 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); - } + int removed = store.remove(*iter, toRemove, ptr); + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); - break; + toRemove -= removed; + + if (toRemove <= 0) + break; } } } @@ -458,20 +451,8 @@ namespace MWScript { 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); - } - + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); + store.remove(*iter, 1, ptr); break; } } @@ -500,7 +481,20 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(MWWorld::Class::get(ptr).getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + } + }; + + template + class OpGetSpellReadied : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); } }; @@ -541,12 +535,7 @@ namespace MWScript runtime.pop(); if (parameter == 1) - { - if (ptr.isInCell()) - MWBase::Environment::get().getWorld()->deleteObject (ptr); - else - ptr.getRefData().setCount(0); - } + MWBase::Environment::get().getWorld()->deleteObject(ptr); } }; @@ -693,35 +682,53 @@ namespace MWScript void printGlobalVars(Interpreter::Runtime &runtime) { + InterpreterContext& context = + static_cast (runtime.getContext()); + std::stringstream str; str<< "Global variables:"; MWBase::World *world = MWBase::Environment::get().getWorld(); - std::vector names = world->getGlobals(); + std::vector names = context.getGlobals(); for(size_t i = 0;i < names.size();++i) { - char type = world->getGlobalVariableType(names[i]); - if(type == 's') - str<getGlobalVariableType (names[i]); + str << std::endl << " " << names[i] << " = "; + + switch (type) + { + case 's': + + str << context.getGlobalShort (names[i]) << " (short)"; + break; + + case 'l': + + str << context.getGlobalLong (names[i]) << " (long)"; + break; + + case 'f': + + str << context.getGlobalFloat (names[i]) << " (float)"; + break; + + default: + + str << ""; + } } - runtime.getContext().report(str.str()); + context.report (str.str()); } public: virtual void execute(Interpreter::Runtime& runtime) { - // No way to tell if we have a reference before trying to get it, and it will - // cause an exception is there isn't one :( - try { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::Ptr ptr = R()(runtime, false); + if (!ptr.isEmpty()) printLocalVars(runtime, ptr); - } - catch(std::runtime_error&) { + else + { // No reference, no problem. printGlobalVars(runtime); } @@ -741,6 +748,75 @@ namespace MWScript } }; + template + class OpCast : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string spell = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger)); + runtime.pop(); + + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); + + MWMechanics::CastSpell cast(ptr, target); + cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos); + cast.cast(spell); + } + }; + + template + class OpExplodeSpell : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string spell = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWMechanics::CastSpell cast(ptr, ptr); + cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos); + cast.cast(spell); + } + }; + + class OpGoToJail : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->goToJail(); + } + }; + + class OpPayFine : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + player.getClass().getNpcStats(player).setBounty(0); + MWBase::Environment::get().getWorld()->confiscateStolenItems(player); + } + }; + + class OpPayFineThief : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + player.getClass().getNpcStats(player).setBounty(0); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -762,8 +838,12 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); + interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc); interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink); + interpreter.installSegment5 (Compiler::Misc::opcodePayFine, new OpPayFine); + interpreter.installSegment5 (Compiler::Misc::opcodePayFineThief, new OpPayFineThief); + interpreter.installSegment5 (Compiler::Misc::opcodeGoToJail, new OpGoToJail); interpreter.installSegment5 (Compiler::Misc::opcodeGetLocked, new OpGetLocked); interpreter.installSegment5 (Compiler::Misc::opcodeGetLockedExplicit, new OpGetLocked); interpreter.installSegment5 (Compiler::Misc::opcodeGetEffect, new OpGetEffect); @@ -780,6 +860,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetAttackedExplicit, new OpGetAttacked); interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawn, new OpGetWeaponDrawn); interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn); + interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadied, new OpGetSpellReadied); + interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadiedExplicit, new OpGetSpellReadied); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects); interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime); @@ -802,6 +884,10 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation); + interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast); + interpreter.installSegment5 (Compiler::Misc::opcodeCastExplicit, new OpCast); + interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpell, new OpExplodeSpell); + interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); } } } diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp index 81b1d5ef9..795839139 100644 --- a/apps/openmw/mwscript/ref.hpp +++ b/apps/openmw/mwscript/ref.hpp @@ -16,7 +16,7 @@ namespace MWScript { struct ExplicitRef { - MWWorld::Ptr operator() (Interpreter::Runtime& runtime) const + MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required=true) const { std::string id = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -27,12 +27,12 @@ namespace MWScript struct ImplicitRef { - MWWorld::Ptr operator() (Interpreter::Runtime& runtime) const + MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required=true) const { MWScript::InterpreterContext& context = static_cast (runtime.getContext()); - return context.getReference(); + return context.getReference(required); } }; } diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 14fe5b7fd..a3e061546 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -114,10 +114,8 @@ namespace MWScript } catch (const std::exception& e) { - std::cerr << "execution of script " << name << " failed." << std::endl; - - if (mVerbose) - std::cerr << "(" << e.what() << ")" << std::endl; + std::cerr << "Execution of script " << name << " failed:" << std::endl; + std::cerr << e.what() << std::endl; iter->second.first.clear(); // don't execute again. } @@ -222,9 +220,4 @@ namespace MWScript throw std::runtime_error ("unable to access local variable " + variable + " of " + scriptId); } - - void ScriptManager::resetGlobalScripts() - { - mGlobalScripts.reset(); - } } diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index 7bb98ffbd..1a856e0c5 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -61,8 +61,6 @@ namespace MWScript ///< Compile script with the given namen /// \return Success? - virtual void resetGlobalScripts(); - virtual std::pair compileAll(); ///< Compile all scripts /// \return count, success diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 603515ff4..c4f672dc7 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -3,8 +3,6 @@ #include -#include - #include #include "../mwworld/esmstore.hpp" @@ -19,9 +17,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -124,10 +122,9 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get(ptr) - .getCreatureStats(ptr) - .getAttribute(mIndex) - .setModified (value, 0); + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); + attribute.setBase (value - (attribute.getModified() - attribute.getBase())); + ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; @@ -147,16 +144,12 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - value += - MWWorld::Class::get(ptr) - .getCreatureStats(ptr) - .getAttribute(mIndex) - .getModified(); - - MWWorld::Class::get(ptr) + MWMechanics::AttributeValue attribute = MWWorld::Class::get(ptr) .getCreatureStats(ptr) - .getAttribute(mIndex) - .setModified (value, 0, 100); + .getAttribute(mIndex); + + attribute.setBase (std::min(100, attribute.getBase() + value)); + ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; @@ -209,6 +202,7 @@ namespace MWScript .getDynamic (mIndex)); stat.setModified (value, 0); + stat.setCurrent(value); MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat); } @@ -313,9 +307,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = - MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). - getModified(); + Interpreter::Type_Integer value = ptr.getClass().getSkill(ptr, mIndex); runtime.push (value); } @@ -346,17 +338,13 @@ namespace MWScript const ESM::Class& class_ = *MWBase::Environment::get().getWorld()->getStore().get().find (ref->mBase->mClass); - float level = 0; - float progress = std::modf (stats.getSkill (mIndex).getBase(), &level); - - float modifier = stats.getSkill (mIndex).getModifier(); + float level = stats.getSkill(mIndex).getBase(); + float progress = stats.getSkill(mIndex).getProgress(); - int newLevel = static_cast (value-modifier); + int newLevel = value - (stats.getSkill(mIndex).getModified() - stats.getSkill(mIndex).getBase()); if (newLevel<0) newLevel = 0; - else if (newLevel>100) - newLevel = 100; progress = (progress / stats.getSkillGain (mIndex, class_, -1, level)) * stats.getSkillGain (mIndex, class_, -1, newLevel); @@ -364,8 +352,8 @@ namespace MWScript if (progress>=1) progress = 0.999999999; - stats.getSkill (mIndex).set (newLevel + progress); - stats.getSkill (mIndex).setModifier (modifier); + stats.getSkill (mIndex).setBase (newLevel); + stats.getSkill (mIndex).setProgress(progress); } }; @@ -385,11 +373,10 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - value += MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). - getModified(); + MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); - MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). - setModified (value, 0, 100); + stats.getSkill(mIndex). + setBase (std::min(100, stats.getSkill(mIndex).getBase() + value)); } }; @@ -400,7 +387,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); runtime.push (static_cast (MWWorld::Class::get (player).getNpcStats (player).getBounty())); } }; @@ -412,7 +399,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat); runtime.pop(); @@ -426,7 +413,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat + MWWorld::Class::get (player).getNpcStats (player).getBounty()); runtime.pop(); @@ -465,6 +452,46 @@ namespace MWScript runtime.pop(); MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id); + + MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); + + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() && + id == wm->getSelectedSpell()) + { + wm->unsetSelectedSpell(); + } + } + }; + + template + class OpRemoveSpellEffects : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string spellid = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); + } + }; + + template + class OpRemoveEffects : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer effectId = runtime[0].mInteger; + runtime.pop(); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); } }; @@ -516,7 +543,7 @@ namespace MWScript Misc::StringUtils::toLower(factionID); if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0; @@ -545,7 +572,7 @@ namespace MWScript Misc::StringUtils::toLower(factionID); if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0; @@ -578,7 +605,7 @@ namespace MWScript Misc::StringUtils::toLower(factionID); if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] -1; @@ -614,7 +641,7 @@ namespace MWScript } } Misc::StringUtils::toLower(factionID); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) @@ -717,7 +744,7 @@ namespace MWScript Misc::StringUtils::toLower (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push ( MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId)); } @@ -753,7 +780,7 @@ namespace MWScript Misc::StringUtils::toLower (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value); } }; @@ -788,7 +815,7 @@ namespace MWScript Misc::StringUtils::toLower (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId)+ value); @@ -847,7 +874,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getWerewolfKills ()); } @@ -880,18 +907,10 @@ namespace MWScript } } Misc::StringUtils::toLower(factionID); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { - std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); - if (expelled.find (factionID) != expelled.end()) - { - runtime.push(1); - } - else - { - runtime.push(0); - } + runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); } else { @@ -907,8 +926,6 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); - std::string factionID = ""; if(arg0 >0 ) { @@ -917,6 +934,7 @@ namespace MWScript } else { + MWWorld::Ptr ptr = R()(runtime); if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; @@ -926,12 +944,10 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { - std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); - Misc::StringUtils::toLower(factionID); - expelled.insert(factionID); + player.getClass().getNpcStats(player).expell(factionID); } } }; @@ -943,8 +959,6 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); - std::string factionID = ""; if(arg0 >0 ) { @@ -953,6 +967,7 @@ namespace MWScript } else { + MWWorld::Ptr ptr = R()(runtime); if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; @@ -962,13 +977,9 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") - { - std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); - Misc::StringUtils::toLower(factionID); - expelled.erase (factionID); - } + player.getClass().getNpcStats(player).clearExpelled(factionID); } }; @@ -988,7 +999,7 @@ namespace MWScript { factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // no-op when executed on the player if (ptr == player) @@ -1015,7 +1026,7 @@ namespace MWScript { factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // no-op when executed on the player if (ptr == player) @@ -1081,6 +1092,18 @@ namespace MWScript } }; + template + class OpResurrect : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + ptr.getClass().getCreatureStats(ptr).resurrect(); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { for (int i=0; i); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit, new OpRemoveSpell); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffects, new OpRemoveSpellEffects); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffectsExplicit, + new OpRemoveSpellEffects); + interpreter.installSegment5 (Compiler::Stats::opcodeResurrect, new OpResurrect); + interpreter.installSegment5 (Compiler::Stats::opcodeResurrectExplicit, + new OpResurrect); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffects, new OpRemoveEffects); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffectsExplicit, + new OpRemoveEffects); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 6246daee2..43a0fedce 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -1,9 +1,5 @@ -#include - -#include #include -#include "../mwworld/esmstore.hpp" #include #include @@ -16,8 +12,9 @@ #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/esmstore.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -208,6 +205,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; @@ -272,6 +277,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; @@ -329,6 +342,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; @@ -450,62 +471,16 @@ namespace MWScript } }; - template - class OpPlaceAtPc : public Interpreter::Opcode0 + template + class OpPlaceAt : public Interpreter::Opcode0 { public: virtual void execute (Interpreter::Runtime& runtime) { - std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - Interpreter::Type_Integer count = runtime[0].mInteger; - runtime.pop(); - Interpreter::Type_Float distance = runtime[0].mFloat; - runtime.pop(); - Interpreter::Type_Integer direction = runtime[0].mInteger; - runtime.pop(); - - if (count<0) - throw std::runtime_error ("count must be non-negative"); - - // no-op - if (count == 0) - return; - - ESM::Position ipos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); - Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); - if(direction == 0) pos = pos + distance*rot.yAxis(); - else if(direction == 1) pos = pos - distance*rot.yAxis(); - else if(direction == 2) pos = pos - distance*rot.xAxis(); - else if(direction == 3) pos = pos + distance*rot.xAxis(); - else throw std::runtime_error ("direction must be 0,1,2 or 3"); - - ipos.pos[0] = pos.x; - ipos.pos[1] = pos.y; - ipos.pos[2] = pos.z; - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - - MWWorld::CellStore* store = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); - ref.getPtr().getCellRef().mPos = ipos; - ref.getPtr().getRefData().setCount(count); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); - } - }; - - template - class OpPlaceAtMe : public Interpreter::Opcode0 - { - public: - - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr me = R()(runtime); + MWWorld::Ptr actor = pc + ? MWBase::Environment::get().getWorld()->getPlayerPtr() + : R()(runtime); std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -524,7 +499,7 @@ namespace MWScript if (count == 0) return; - ESM::Position ipos = me.getRefData().getPosition(); + ESM::Position ipos = actor.getRefData().getPosition(); Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); if(direction == 0) pos = pos + distance*rot.yAxis(); @@ -536,16 +511,26 @@ namespace MWScript ipos.pos[0] = pos.x; ipos.pos[1] = pos.y; ipos.pos[2] = pos.z; - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - MWWorld::CellStore* store = me.getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); + if (actor.getClass().isActor()) + { + // TODO: should this depend on the 'direction' parameter? + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + } + else + { + ipos.rot[0] = actor.getRefData().getPosition().rot[0]; + ipos.rot[1] = actor.getRefData().getPosition().rot[1]; + ipos.rot[2] = actor.getRefData().getPosition().rot[2]; + } + // create item + MWWorld::CellStore* store = actor.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); ref.getPtr().getCellRef().mPos = ipos; - ref.getPtr().getRefData().setCount(count); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); } }; @@ -629,6 +614,10 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); + + if (!ptr.isInCell()) + return; + ptr.getRefData().getLocalRotation().rot[0] = 0; ptr.getRefData().getLocalRotation().rot[1] = 0; ptr.getRefData().getLocalRotation().rot[2] = 0; @@ -648,6 +637,9 @@ namespace MWScript { const MWWorld::Ptr& ptr = R()(runtime); + if (!ptr.isInCell()) + return; + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); @@ -683,6 +675,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); @@ -730,9 +725,9 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAtPc); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAtMe); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAtMe); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodeModScale,new OpModScale); interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale); interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index d5c382a41..c83697442 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -158,6 +158,16 @@ void FFmpeg_Decoder::open(const std::string &fname) mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { + if (mFormatCtx->pb != NULL) + { + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + } avformat_free_context(mFormatCtx); mFormatCtx = NULL; fail("Failed to allocate input stream"); @@ -195,6 +205,14 @@ void FFmpeg_Decoder::open(const std::string &fname) } catch(std::exception &e) { + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + avformat_close_input(&mFormatCtx); throw; } @@ -211,9 +229,22 @@ void FFmpeg_Decoder::close() if(mFormatCtx) { - AVIOContext* context = mFormatCtx->pb; + if (mFormatCtx->pb != NULL) + { + // mFormatCtx->pb->buffer must be freed by hand, + // if not, valgrind will show memleak, see: + // + // https://trac.ffmpeg.org/ticket/1357 + // + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + } avformat_close_input(&mFormatCtx); - av_free(context); } mDataStream.setNull(); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 4ee754b35..9dc0b8c5d 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -403,7 +403,7 @@ void OpenAL_SoundStream::update() alSourcef(mSource, AL_GAIN, gain); alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 372be8393..fd3fe4c0a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -6,9 +6,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/player.hpp" #include "sound_output.hpp" #include "sound_decoder.hpp" @@ -157,11 +157,12 @@ namespace MWSound volume *= mFootstepsVolume; break; case Play_TypeMusic: - case Play_TypeMovie: volume *= mMusicVolume; break; case Play_TypeMask: break; + default: + break; } return volume; } @@ -248,14 +249,13 @@ namespace MWSound return; try { - // The range values are not tested 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]); + const Ogre::Vector3 objpos(pos.pos); MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - 20.0f, 12750.0f, Play_Normal|Play_TypeVoice, 0); + 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -354,7 +354,7 @@ namespace MWSound float 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]); + const Ogre::Vector3 objpos(pos.pos); sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset); if((mode&Play_NoTrack)) @@ -399,7 +399,7 @@ namespace MWSound } } - void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell) + void SoundManager::stopSound(const MWWorld::CellStore *cell) { SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -479,7 +479,7 @@ namespace MWSound static std::string regionName = ""; static float sTimePassed = 0.0; MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Ptr player = world->getPlayer().getPlayer(); + const MWWorld::Ptr player = world->getPlayerPtr(); const ESM::Cell *cell = player.getCell()->mCell; sTimePassed += duration; @@ -547,7 +547,7 @@ namespace MWSound startRandomTitle(); MWWorld::Ptr player = - MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWBase::Environment::get().getWorld()->getPlayerPtr(); const ESM::Cell *cell = player.getCell()->mCell; Environment env = Env_Normal; @@ -584,7 +584,7 @@ namespace MWSound if(!ptr.isEmpty()) { const ESM::Position &pos = ptr.getRefData().getPosition(); - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + const Ogre::Vector3 objpos(pos.pos); snditer->first->setPosition(objpos); } //update fade out @@ -595,7 +595,7 @@ namespace MWSound soundDuration=snditer->first->mFadeOutTime; snditer->first->setVolume(snditer->first->mVolume - soundDuration / snditer->first->mFadeOutTime * snditer->first->mVolume); - snditer->first->mFadeOutTime -= soundDuration; + snditer->first->mFadeOutTime -= soundDuration; } snditer->first->update(); ++snditer; @@ -607,8 +607,13 @@ namespace MWSound { if(!mOutput->isInitialized()) return; - updateSounds(duration); - updateRegionSound(duration); + + if (MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + updateSounds(duration); + updateRegionSound(duration); + } } @@ -641,6 +646,15 @@ namespace MWSound mListenerUp = up; } + void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + { + for (SoundMap::iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer) + { + if (snditer->second.first == old) + snditer->second.first = updated; + } + } + // Default readAll implementation, for decoders that can't do anything // better void Sound_Decoder::readAll(std::vector &output) @@ -705,4 +719,13 @@ namespace MWSound { return bytes / framesToBytes(1, config, type); } + + void SoundManager::clear() + { + for (SoundMap::iterator iter (mActiveSounds.begin()); iter!=mActiveSounds.end(); ++iter) + iter->first->stop(); + + mActiveSounds.clear(); + stopMusic(); + } } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f62e62d50..1454240b4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -148,6 +148,10 @@ namespace MWSound virtual void update(float duration); virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up); + + virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); + + virtual void clear(); }; } diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp new file mode 100644 index 000000000..304eaddd3 --- /dev/null +++ b/apps/openmw/mwstate/character.cpp @@ -0,0 +1,153 @@ + +#include "character.hpp" + +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +bool MWState::operator< (const Slot& left, const Slot& right) +{ + return left.mTimeStampESM::Header::CurrentFormat) + return; // format is too new -> ignore + + if (reader.getRecName()!=ESM::REC_SAVE) + return; // invalid save file -> ignore + + reader.getRecHeader(); + + slot.mProfile.load (reader); + + if (Misc::StringUtils::lowerCase (slot.mProfile.mContentFiles.at (0))!= + Misc::StringUtils::lowerCase (game)) + return; // this file is for a different game -> ignore + + mSlots.push_back (slot); +} + +void MWState::Character::addSlot (const ESM::SavedGame& profile) +{ + Slot slot; + + std::ostringstream stream; + stream << mNext++; + + slot.mPath = mPath / stream.str(); + slot.mProfile = profile; + slot.mTimeStamp = std::time (0); + + mSlots.push_back (slot); +} + +MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) +: mPath (saves), mNext (0) +{ + if (!boost::filesystem::is_directory (mPath)) + { + boost::filesystem::create_directories (mPath); + } + else + { + for (boost::filesystem::directory_iterator iter (mPath); + iter!=boost::filesystem::directory_iterator(); ++iter) + { + boost::filesystem::path slotPath = *iter; + + try + { + addSlot (slotPath, game); + } + catch (...) {} // ignoring bad saved game files for now + + std::istringstream stream (slotPath.filename().string()); + + int index = 0; + + if ((stream >> index) && index>=mNext) + mNext = index+1; + } + + std::sort (mSlots.begin(), mSlots.end()); + } +} + +const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile) +{ + addSlot (profile); + + return &mSlots.back(); +} + +const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile) +{ + int index = slot - &mSlots[0]; + + if (index<0 || index>=static_cast (mSlots.size())) + { + // sanity check; not entirely reliable + throw std::logic_error ("slot not found"); + } + + Slot newSlot = *slot; + newSlot.mProfile = profile; + newSlot.mTimeStamp = std::time (0); + + mSlots.erase (mSlots.begin()+index); + + mSlots.push_back (newSlot); + + return &mSlots.back(); +} + +MWState::Character::SlotIterator MWState::Character::begin() const +{ + return mSlots.rbegin(); +} + +MWState::Character::SlotIterator MWState::Character::end() const +{ + return mSlots.rend(); +} + +ESM::SavedGame MWState::Character::getSignature() const +{ + if (mSlots.empty()) + throw std::logic_error ("character signature not available"); + + std::vector::const_iterator iter (mSlots.begin()); + + Slot slot = *iter; + + for (++iter; iter!=mSlots.end(); ++iter) + if (iter->mProfile.mPlayerLevel>slot.mProfile.mPlayerLevel) + slot = *iter; + else if (iter->mProfile.mPlayerLevel==slot.mProfile.mPlayerLevel && + iter->mTimeStamp>slot.mTimeStamp) + slot = *iter; + + return slot.mProfile; +} \ No newline at end of file diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp new file mode 100644 index 000000000..61e4e5b25 --- /dev/null +++ b/apps/openmw/mwstate/character.hpp @@ -0,0 +1,63 @@ +#ifndef GAME_STATE_CHARACTER_H +#define GAME_STATE_CHARACTER_H + +#include + +#include + +namespace MWState +{ + struct Slot + { + boost::filesystem::path mPath; + ESM::SavedGame mProfile; + std::time_t mTimeStamp; + }; + + bool operator< (const Slot& left, const Slot& right); + + class Character + { + public: + + typedef std::vector::const_reverse_iterator SlotIterator; + + private: + + boost::filesystem::path mPath; + std::vector mSlots; + int mNext; + + void addSlot (const boost::filesystem::path& path, const std::string& game); + + void addSlot (const ESM::SavedGame& profile); + + public: + + Character (const boost::filesystem::path& saves, const std::string& game); + + const Slot *createSlot (const ESM::SavedGame& profile); + ///< Create new slot. + /// + /// \attention The ownership of the slot is not transferred. + + const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile); + /// \note Slot must belong to this character. + /// + /// \attention The \a slot pointer will be invalidated by this call. + + SlotIterator begin() const; + ///< First slot is the most recent. Other slots follow in descending order of save date. + /// + /// Any call to createSlot and updateSlot can invalidate the returned iterator. + + SlotIterator end() const; + + ESM::SavedGame getSignature() const; + ///< Return signature information for this character. + /// + /// \attention This function must not be called if there are no slots. + }; +} + +#endif diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp new file mode 100644 index 000000000..2a40fb1cc --- /dev/null +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -0,0 +1,85 @@ + +#include "charactermanager.hpp" + +#include +#include + +#include + +MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, + const std::string& game) +: mPath (saves), mNext (0), mCurrent (0), mGame (game) +{ + if (!boost::filesystem::is_directory (mPath)) + { + boost::filesystem::create_directories (mPath); + } + else + { + for (boost::filesystem::directory_iterator iter (mPath); + iter!=boost::filesystem::directory_iterator(); ++iter) + { + boost::filesystem::path characterDir = *iter; + + if (boost::filesystem::is_directory (characterDir)) + { + Character character (characterDir, mGame); + + if (character.begin()!=character.end()) + mCharacters.push_back (character); + } + + std::istringstream stream (characterDir.filename().string()); + + int index = 0; + + if ((stream >> index) && index>=mNext) + mNext = index+1; + } + } +} + +MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create) +{ + if (!mCurrent && create) + createCharacter(); + + return mCurrent; +} + +void MWState::CharacterManager::createCharacter() +{ + std::ostringstream stream; + stream << mNext++; + + boost::filesystem::path path = mPath / stream.str(); + + mCharacters.push_back (Character (path, mGame)); + + mCurrent = &mCharacters.back(); +} + +void MWState::CharacterManager::setCurrentCharacter (const Character *character) +{ + int index = character - &mCharacters[0]; + + if (index<0 || index>=static_cast (mCharacters.size())) + throw std::logic_error ("invalid character"); + + mCurrent = &mCharacters[index]; +} + +void MWState::CharacterManager::clearCurrentCharacter() +{ + mCurrent = 0; +} + +std::vector::const_iterator MWState::CharacterManager::begin() const +{ + return mCharacters.begin(); +} + +std::vector::const_iterator MWState::CharacterManager::end() const +{ + return mCharacters.end(); +} diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp new file mode 100644 index 000000000..bc2e23f89 --- /dev/null +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -0,0 +1,46 @@ +#ifndef GAME_STATE_CHARACTERMANAGER_H +#define GAME_STATE_CHARACTERMANAGER_H + +#include + +#include "character.hpp" + +namespace MWState +{ + class CharacterManager + { + boost::filesystem::path mPath; + int mNext; + std::vector mCharacters; + Character *mCurrent; + std::string mGame; + + private: + + CharacterManager (const CharacterManager&); + ///< Not implemented + + CharacterManager& operator= (const CharacterManager&); + ///< Not implemented + + public: + + CharacterManager (const boost::filesystem::path& saves, const std::string& game); + + Character *getCurrentCharacter (bool create = true); + ///< \param create Create a new character, if there is no current character. + + void createCharacter(); + ///< Create new character within saved game management + + void setCurrentCharacter (const Character *character); + + void clearCurrentCharacter(); + + std::vector::const_iterator begin() const; + + std::vector::const_iterator end() const; + }; +} + +#endif diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp new file mode 100644 index 000000000..f68a01bf4 --- /dev/null +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -0,0 +1,345 @@ + +#include "statemanagerimp.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "../mwscript/globalscripts.hpp" + +void MWState::StateManager::cleanup (bool force) +{ + if (mState!=State_NoGame || force) + { + MWBase::Environment::get().getSoundManager()->clear(); + MWBase::Environment::get().getDialogueManager()->clear(); + MWBase::Environment::get().getJournal()->clear(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); + MWBase::Environment::get().getWorld()->clear(); + MWBase::Environment::get().getWindowManager()->clear(); + + mState = State_NoGame; + mCharacterManager.clearCurrentCharacter(); + mTimePlayed = 0; + } +} + +std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) + const +{ + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + const std::vector& prev = reader.getGameFiles(); + + std::map map; + + for (int iPrev = 0; iPrev (prev.size()); ++iPrev) + { + std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); + + for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) + if (id==Misc::StringUtils::lowerCase (current[iCurrent])) + { + map.insert (std::make_pair (iPrev, iCurrent)); + break; + } + } + + return map; +} + +MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) +: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) +{ + +} + +void MWState::StateManager::requestQuit() +{ + mQuitRequest = true; +} + +bool MWState::StateManager::hasQuitRequest() const +{ + return mQuitRequest; +} + +void MWState::StateManager::askLoadRecent() +{ + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) + return; + + if( !mAskLoadRecent ) + { + if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + else + { + MWState::Slot lastSave = *getCurrentCharacter()->begin(); + std::vector buttons; + buttons.push_back("#{sYes}"); + buttons.push_back("#{sNo}"); + std::string tag("%s"); + std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); + size_t pos = message.find(tag); + message.replace(pos, tag.length(), lastSave.mProfile.mDescription); + MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); + mAskLoadRecent = true; + } + } +} + +MWState::StateManager::State MWState::StateManager::getState() const +{ + return mState; +} + +void MWState::StateManager::newGame (bool bypass) +{ + cleanup(); + + if (!bypass) + { + MWBase::Environment::get().getWorld()->startNewGame(); + MWBase::Environment::get().getWindowManager()->setNewGame (true); + } + else + MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + + mState = State_Running; +} + +void MWState::StateManager::endGame() +{ + mState = State_Ended; + MWBase::Environment::get().getWorld()->useDeathCamera(); +} + +void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) +{ + ESM::SavedGame profile; + + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + MWWorld::Ptr player = world.getPlayer().getPlayer(); + + profile.mContentFiles = world.getContentFiles(); + + profile.mPlayerName = player.getClass().getName (player); + profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); + profile.mPlayerClass = player.get()->mBase->mClass; + + profile.mPlayerCell = world.getCellName(); + + profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); + profile.mInGameTime.mDay = world.getDay(); + profile.mInGameTime.mMonth = world.getMonth(); + profile.mInGameTime.mYear = world.getYear(); + profile.mTimePlayed = mTimePlayed; + profile.mDescription = description; + + int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing + Ogre::Image screenshot; + world.screenshot(screenshot, screenshotW, screenshotH); + Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); + profile.mScreenshot.resize(encoded->size()); + encoded->read(&profile.mScreenshot[0], encoded->size()); + + if (!slot) + slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + else + slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + + std::ofstream stream (slot->mPath.string().c_str()); + + ESM::ESMWriter writer; + + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); + ++iter) + writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 + + writer.setFormat (ESM::Header::CurrentFormat); + writer.setRecordCount ( + 1 // saved game header + +MWBase::Environment::get().getJournal()->countSavedGameRecords() + +MWBase::Environment::get().getWorld()->countSavedGameRecords() + +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + + 1 // global map + ); + + writer.save (stream); + + writer.startRecord (ESM::REC_SAVE); + slot->mProfile.save (writer); + writer.endRecord (ESM::REC_SAVE); + + MWBase::Environment::get().getJournal()->write (writer); + MWBase::Environment::get().getWorld()->write (writer); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); + MWBase::Environment::get().getWindowManager()->write(writer); + + writer.close(); + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); +} + +void MWState::StateManager::loadGame (const Character *character, const Slot *slot) +{ + try + { + cleanup(); + + mTimePlayed = slot->mProfile.mTimePlayed; + + ESM::ESMReader reader; + reader.open (slot->mPath.string()); + + std::map contentFileMap = buildContentFileIndexMap (reader); + + while (reader.hasMoreRecs()) + { + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); + + switch (n.val) + { + case ESM::REC_SAVE: + + // don't need to read that here + reader.skipRecord(); + break; + + case ESM::REC_JOUR: + case ESM::REC_QUES: + + MWBase::Environment::get().getJournal()->readRecord (reader, n.val); + break; + + case ESM::REC_ALCH: + case ESM::REC_ARMO: + case ESM::REC_BOOK: + case ESM::REC_CLAS: + case ESM::REC_CLOT: + case ESM::REC_ENCH: + case ESM::REC_NPC_: + case ESM::REC_SPEL: + case ESM::REC_WEAP: + case ESM::REC_GLOB: + case ESM::REC_PLAY: + case ESM::REC_CSTA: + + MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); + break; + + case ESM::REC_GSCR: + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); + break; + + case ESM::REC_GMAP: + + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); + break; + + default: + + // ignore invalid records + /// \todo log error + reader.skipRecord(); + } + } + + mCharacterManager.setCurrentCharacter(character); + + mState = State_Running; + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); + + MWBase::Environment::get().getWindowManager()->setNewGame(false); + MWBase::Environment::get().getWorld()->setupPlayer(); + MWBase::Environment::get().getWorld()->renderPlayer(); + MWBase::Environment::get().getWindowManager()->updatePlayer(); + MWBase::Environment::get().getMechanicsManager()->playerLoaded(); + + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); + + MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); + } + catch (const std::exception& e) + { + std::cerr << "failed to load saved game: " << e.what() << std::endl; + cleanup (true); + } +} + +MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) +{ + return mCharacterManager.getCurrentCharacter (create); +} + +MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() +{ + return mCharacterManager.begin(); +} + +MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() +{ + return mCharacterManager.end(); +} + +void MWState::StateManager::update (float duration) +{ + mTimePlayed += duration; + + // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update. + if (mAskLoadRecent) + { + int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + if(iButton==0) + { + mAskLoadRecent = false; + //Load last saved game for current character + MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); + loadGame(curCharacter, &lastSave); + } + else if(iButton==1) + { + mAskLoadRecent = false; + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + } +} diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp new file mode 100644 index 000000000..46ade236b --- /dev/null +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -0,0 +1,70 @@ +#ifndef GAME_STATE_STATEMANAGER_H +#define GAME_STATE_STATEMANAGER_H + +#include + +#include "../mwbase/statemanager.hpp" + +#include + +#include "charactermanager.hpp" + +namespace MWState +{ + class StateManager : public MWBase::StateManager + { + bool mQuitRequest; + bool mAskLoadRecent; + State mState; + CharacterManager mCharacterManager; + double mTimePlayed; + + private: + + void cleanup (bool force = false); + + std::map buildContentFileIndexMap (const ESM::ESMReader& reader) const; + + public: + + StateManager (const boost::filesystem::path& saves, const std::string& game); + + virtual void requestQuit(); + + virtual bool hasQuitRequest() const; + + virtual void askLoadRecent(); + + virtual State getState() const; + + virtual void newGame (bool bypass = false); + ///< Start a new game. + /// + /// \param bypass Skip new game mechanics. + + virtual void endGame(); + + virtual void saveGame (const std::string& description, const Slot *slot = 0); + ///< Write a saved game to \a slot or create a new slot if \a slot == 0. + /// + /// \note Slot must belong to the current character. + + virtual void loadGame (const Character *character, const Slot *slot); + ///< Load a saved game file from \a slot. + /// + /// \note \a slot must belong to \a character. + + virtual Character *getCurrentCharacter (bool create = true); + ///< \param create Create a new character, if there is no current character. + + virtual CharacterIterator characterBegin(); + ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned + /// iterator. + + virtual CharacterIterator characterEnd(); + + virtual void update (float duration); + }; +} + +#endif diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 3353ae0ee..669a19067 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONAPPLY_H #define GAME_MWWORLD_ACTIONAPPLY_H diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 63efff738..f5d7e2636 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -3,45 +3,25 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/containerstore.hpp" -#include "esmstore.hpp" #include "class.hpp" namespace MWWorld { void ActionEat::executeImp (const Ptr& actor) { - // remove used item - getTarget().getRefData().setCount (getTarget().getRefData().getCount()-1); - - // check for success - const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - - if (x>=100*static_cast (std::rand()) / RAND_MAX) - { - // apply to actor - std::string id = Class::get (getTarget()).getId (getTarget()); + // remove used item (assume the item is present in inventory) + getTarget().getContainerStore()->remove(getTarget(), 1, actor); + + // apply to actor + std::string id = Class::get (getTarget()).getId (getTarget()); - Class::get (actor).apply (actor, id, actor); - // we ignore the result here. Skill increases no matter if the ingredient did something or not. - - // increase skill + if (Class::get (actor).apply (actor, id, actor)) Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); - } } ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 0f1a85dda..2a1b7a3aa 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -19,12 +19,12 @@ namespace MWWorld void ActionEquip::executeImp (const Ptr& actor) { MWWorld::Ptr object = getTarget(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); + MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); - std::pair result = MWWorld::Class::get (object).canBeEquipped (object, actor); + std::pair result = object.getClass().canBeEquipped (object, actor); // display error message if the player tried to equip something - if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->messageBox(result.second); switch(result.first) @@ -32,15 +32,15 @@ namespace MWWorld case 0: return; case 2: - invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end()); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor); break; case 3: - invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end()); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); break; } // slots that this item can be equipped in - std::pair, bool> slots = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget()); + std::pair, bool> slots_ = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget()); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = invStore.begin(); @@ -54,34 +54,27 @@ namespace MWWorld assert(it != invStore.end()); - 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) + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) { + // if the item is equipped already, nothing to do + if (invStore.getSlot(*slot) == it) + return; // if all slots are occupied, replace the last slot - if (slot == --slots.first.end()) + if (slot == --slots_.first.end()) { - invStore.equip(*slot, it); - equipped = true; + invStore.equip(*slot, it, actor); break; } if (invStore.getSlot(*slot) == invStore.end()) { // slot is not occupied - invStore.equip(*slot, it); - equipped = true; + invStore.equip(*slot, it, actor); break; } } - - std::string script = MWWorld::Class::get(object).getScript(object); - - /* 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 != "") - object.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); } } diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index 728b6e32b..e9d8b4716 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -5,6 +5,8 @@ #include "../mwgui/container.hpp" +#include "../mwmechanics/disease.hpp" + #include "class.hpp" #include "containerstore.hpp" @@ -21,6 +23,8 @@ namespace MWWorld if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; + MWMechanics::diseaseContact(actor, getTarget()); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container); MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(getTarget(), mLoot); } diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 6d5d9d8fd..67755259e 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -34,7 +34,7 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->getBookWindow()->open(getTarget()); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); // Skill gain from books diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index d3c4aa2f6..548a94981 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "class.hpp" #include "containerstore.hpp" @@ -14,11 +15,9 @@ namespace MWWorld void ActionTake::executeImp (const Ptr& actor) { - // insert into player's inventory - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPtr ("player", true); - - MWWorld::Class::get (player).getContainerStore (player).add (getTarget(), player); - + MWBase::Environment::get().getMechanicsManager()->itemTaken( + actor, getTarget(), getTarget().getRefData().getCount()); + actor.getClass().getContainerStore (actor).add (getTarget(), getTarget().getRefData().getCount(), actor); MWBase::Environment::get().getWorld()->deleteObject (getTarget()); } } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index ae5ffc3b9..b4c572ba9 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -3,6 +3,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "player.hpp" namespace MWWorld { @@ -14,9 +16,36 @@ namespace MWWorld void ActionTeleport::executeImp (const Ptr& actor) { - if (mCellName.empty()) - MWBase::Environment::get().getWorld()->changeToExteriorCell (mPosition); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + //find any NPC that is following the actor and teleport him too + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); + for(std::list::iterator it = followers.begin();it != followers.end();it++) + { + std::cout << "teleporting someone!" << (*it).getCellRef().mRefID; + executeImp(*it); + } + + if(actor == world->getPlayerPtr()) + { + world->getPlayer().setTeleported(true); + if (mCellName.empty()) + world->changeToExteriorCell (mPosition); + else + world->changeToInteriorCell (mCellName, mPosition); + } else - MWBase::Environment::get().getWorld()->changeToInteriorCell (mCellName, mPosition); + { + if (mCellName.empty()) + { + int cellX; + int cellY; + world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); + world->moveObject(actor,*world->getExterior(cellX,cellY), + mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + } + else + world->moveObject(actor,*world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + } } } diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp new file mode 100644 index 000000000..bcefb0181 --- /dev/null +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -0,0 +1,17 @@ +#include "actiontrap.hpp" + +#include "../mwmechanics/spellcasting.hpp" + +namespace MWWorld +{ + + void ActionTrap::executeImp(const Ptr &actor) + { + MWMechanics::CastSpell cast(mTrapSource, actor); + cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos); + cast.cast(mSpellId); + + mTrapSource.getCellRef().mTrap = ""; + } + +} diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp new file mode 100644 index 000000000..4c2f4139f --- /dev/null +++ b/apps/openmw/mwworld/actiontrap.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_MWWORLD_ACTIONTRAP_H +#define GAME_MWWORLD_ACTIONTRAP_H + +#include + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionTrap : public Action + { + std::string mSpellId; + MWWorld::Ptr mTrapSource; + + virtual void executeImp (const Ptr& actor); + + public: + + /// @param spellId + /// @param actor Actor that activated the trap + /// @param trapSource + ActionTrap (const Ptr& actor, const std::string& spellId, const Ptr& trapSource) + : Action(false, actor), mSpellId(spellId), mTrapSource(trapSource) {} + }; +} + + +#endif diff --git a/apps/openmw/mwworld/cellfunctors.hpp b/apps/openmw/mwworld/cellfunctors.hpp index 4b1f70096..5115fa02d 100644 --- a/apps/openmw/mwworld/cellfunctors.hpp +++ b/apps/openmw/mwworld/cellfunctors.hpp @@ -4,7 +4,7 @@ #include #include -#include "refdata.hpp" +#include "ptr.hpp" namespace ESM { @@ -18,13 +18,13 @@ namespace MWWorld { std::vector mHandles; - bool operator() (ESM::CellRef& ref, RefData& data) + bool operator() (MWWorld::Ptr ptr) { - Ogre::SceneNode* handle = data.getBaseNode(); + Ogre::SceneNode* handle = ptr.getRefData().getBaseNode(); if (handle) mHandles.push_back (handle); - data.setBaseNode(0); + ptr.getRefData().setBaseNode(0); return true; } }; diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 37c4b6a3f..965c9fc5d 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,5 +1,10 @@ #include "cells.hpp" +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -7,28 +12,29 @@ #include "esmstore.hpp" #include "containerstore.hpp" -MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) +MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) { if (cell->mData.mFlags & ESM::Cell::Interior) { - std::map::iterator result = mInteriors.find (Misc::StringUtils::lowerCase(cell->mName)); + std::string lowerName(Misc::StringUtils::lowerCase(cell->mName)); + std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) { - result = mInteriors.insert (std::make_pair (Misc::StringUtils::lowerCase(cell->mName), Ptr::CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; } return &result->second; } else { - std::map, Ptr::CellStore>::iterator result = + std::map, CellStore>::iterator result = mExteriors.find (std::make_pair (cell->getGridX(), cell->getGridY())); if (result==mExteriors.end()) { result = mExteriors.insert (std::make_pair ( - std::make_pair (cell->getGridX(), cell->getGridY()), Ptr::CellStore (cell))).first; + std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell))).first; } @@ -40,11 +46,11 @@ void MWWorld::Cells::clear() { mInteriors.clear(); mExteriors.clear(); - std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::Ptr::CellStore*)0)); + std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::CellStore*)0)); mIdCacheIndex = 0; } -MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellStore& cellStore) +MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& cellStore) { Ptr ptr = getPtr (name, cellStore); @@ -59,15 +65,39 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellS return ptr; } +void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, const CellStore& cell) const +{ + ESM::CellState cellState; + + cell.saveState (cellState); + + writer.startRecord (ESM::REC_CSTA); + cellState.mId.save (writer); + cellState.save (writer); + cell.writeReferences (writer); + writer.endRecord (ESM::REC_CSTA); +} + +bool MWWorld::Cells::hasState (const CellStore& cellStore) const +{ + if (cellStore.mState==CellStore::State_Loaded) + return true; + + if (cellStore.mCell->mData.mFlags & ESM::Cell::Interior) + return cellStore.mCell->mData.mFlags & ESM::Cell::HasWater; + else + return false; +} + MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) : mStore (store), mReader (reader), - mIdCache (40, std::pair ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable + mIdCache (40, std::pair ("", (CellStore*)0)), /// \todo make cache size configurable mIdCacheIndex (0) {} -MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) +MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) { - std::map, Ptr::CellStore>::iterator result = + std::map, CellStore>::iterator result = mExteriors.find (std::make_pair (x, y)); if (result==mExteriors.end()) @@ -79,7 +109,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) // Cell isn't predefined. Make one on the fly. ESM::Cell record; - record.mData.mFlags = 0; + record.mData.mFlags = ESM::Cell::HasWater; record.mData.mX = x; record.mData.mY = y; record.mWater = 0; @@ -92,7 +122,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) std::make_pair (x, y), CellStore (cell))).first; } - if (result->second.mState!=Ptr::CellStore::State_Loaded) + if (result->second.mState!=CellStore::State_Loaded) { // Multiple plugin support for landscape data is much easier than for references. The last plugin wins. result->second.load (mStore, mReader); @@ -101,19 +131,19 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) return &result->second; } -MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) +MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) { std::string lowerName = Misc::StringUtils::lowerCase(name); - std::map::iterator result = mInteriors.find (lowerName); + std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) { const ESM::Cell *cell = mStore.get().find(lowerName); - result = mInteriors.insert (std::make_pair (lowerName, Ptr::CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; } - if (result->second.mState!=Ptr::CellStore::State_Loaded) + if (result->second.mState!=CellStore::State_Loaded) { result->second.load (mStore, mReader); } @@ -121,17 +151,23 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) return &result->second; } -MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& cell, +MWWorld::CellStore *MWWorld::Cells::getCell (const ESM::CellId& id) +{ + if (id.mPaged) + return getExterior (id.mIndex.mX, id.mIndex.mY); + + return getInterior (id.mWorldspace); +} + +MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, bool searchInContainers) { - if (cell.mState==Ptr::CellStore::State_Unloaded) + if (cell.mState==CellStore::State_Unloaded) cell.preload (mStore, mReader); - if (cell.mState==Ptr::CellStore::State_Preloaded) + if (cell.mState==CellStore::State_Preloaded) { - std::string lowerCase = Misc::StringUtils::lowerCase(name); - - if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), lowerCase)) + if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), name)) { cell.load (mStore, mReader); } @@ -208,7 +244,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& ce MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) { // First check the cache - for (std::vector >::iterator iter (mIdCache.begin()); + for (std::vector >::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter) if (iter->first==name && iter->second) { @@ -218,7 +254,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) } // Then check cells that are already listed - for (std::map, Ptr::CellStore>::iterator iter = mExteriors.begin(); + for (std::map, CellStore>::iterator iter = mExteriors.begin(); iter!=mExteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); @@ -226,7 +262,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) return ptr; } - for (std::map::iterator iter = mInteriors.begin(); + for (std::map::iterator iter = mInteriors.begin(); iter!=mInteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); @@ -240,7 +276,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) for (iter = cells.extBegin(); iter != cells.extEnd(); ++iter) { - Ptr::CellStore *cellStore = getCellStore (&(*iter)); + CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); @@ -250,7 +286,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) for (iter = cells.intBegin(); iter != cells.intEnd(); ++iter) { - Ptr::CellStore *cellStore = getCellStore (&(*iter)); + CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); @@ -261,3 +297,91 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) // giving up return Ptr(); } + +void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector &out) +{ + for (std::map, CellStore>::iterator iter = mExteriors.begin(); + iter!=mExteriors.end(); ++iter) + { + Ptr ptr = getPtrAndCache (name, iter->second); + if (!ptr.isEmpty()) + out.push_back(ptr); + } + +} + +void MWWorld::Cells::getInteriorPtrs(const std::string &name, std::vector &out) +{ + for (std::map::iterator iter = mInteriors.begin(); + iter!=mInteriors.end(); ++iter) + { + Ptr ptr = getPtrAndCache (name, iter->second); + if (!ptr.isEmpty()) + out.push_back(ptr); + } + +} + +int MWWorld::Cells::countSavedGameRecords() const +{ + int count = 0; + + for (std::map::const_iterator iter (mInteriors.begin()); + iter!=mInteriors.end(); ++iter) + if (hasState (iter->second)) + ++count; + + for (std::map, CellStore>::const_iterator iter (mExteriors.begin()); + iter!=mExteriors.end(); ++iter) + if (hasState (iter->second)) + ++count; + + return count; +} + +void MWWorld::Cells::write (ESM::ESMWriter& writer) const +{ + for (std::map, CellStore>::const_iterator iter (mExteriors.begin()); + iter!=mExteriors.end(); ++iter) + if (hasState (iter->second)) + writeCell (writer, iter->second); + + for (std::map::const_iterator iter (mInteriors.begin()); + iter!=mInteriors.end(); ++iter) + if (hasState (iter->second)) + writeCell (writer, iter->second); +} + +bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap) +{ + if (type==ESM::REC_CSTA) + { + ESM::CellState state; + state.mId.load (reader); + + CellStore *cellStore = 0; + + try + { + cellStore = getCell (state.mId); + } + catch (...) + { + // silently drop cells that don't exist anymore + /// \todo log + } + + state.load (reader); + cellStore->loadState (state); + + if (cellStore->mState!=CellStore::State_Loaded) + cellStore->load (mStore, mReader); + + cellStore->readReferences (reader, contentFileMap); + + return true; + } + + return false; +} diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 0c51cf452..7ee8a3f6c 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -10,6 +10,8 @@ namespace ESM { class ESMReader; + class ESMWriter; + struct CellId; } namespace MWWorld @@ -33,22 +35,46 @@ namespace MWWorld Ptr getPtrAndCache (const std::string& name, CellStore& cellStore); + void writeCell (ESM::ESMWriter& writer, const CellStore& cell) const; + + bool hasState (const CellStore& cellStore) const; + ///< Check if cell has state that needs to be included in a saved game file. + public: void clear(); 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 CellStore *getExterior (int x, int y); CellStore *getInterior (const std::string& name); + CellStore *getCell (const ESM::CellId& id); + Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false); ///< \param searchInContainers Only affect loaded cells. + /// @note name must be lower case + /// @note name must be lower case Ptr getPtr (const std::string& name); + + /// Get all Ptrs referencing \a name in exterior cells + /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. + /// @note name must be lower case + void getExteriorPtrs (const std::string& name, std::vector& out); + + /// Get all Ptrs referencing \a name in interior cells + /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. + /// @note name must be lower case + void getInteriorPtrs (const std::string& name, std::vector& out); + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 0c145ab60..42c954afb 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -2,6 +2,15 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -29,38 +38,99 @@ namespace return MWWorld::Ptr(); } + + template + void writeReferenceCollection (ESM::ESMWriter& writer, + const MWWorld::CellRefList& collection) + { + if (!collection.mList.empty()) + { + // references + for (typename MWWorld::CellRefList::List::const_iterator + iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + { + if (iter->mData.getCount()==0 && iter->mRef.mRefNum.mContentFile==-1) + continue; // deleted file that did not came from a content file -> ignore + + RecordType state; + iter->save (state); + + writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); + state.save (writer); + } + } + } + + template + void readReferenceCollection (ESM::ESMReader& reader, + MWWorld::CellRefList& collection, const std::map& contentFileMap) + { + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + + RecordType state; + state.load (reader); + + std::map::const_iterator iter = + contentFileMap.find (state.mRef.mRefNum.mContentFile); + + if (iter==contentFileMap.end()) + return; // content file has been removed -> skip + + state.mRef.mRefNum.mContentFile = iter->second; + + if (!MWWorld::LiveCellRef::checkState (state)) + return; // not valid anymore with current content files -> skip + + const T *record = esmStore.get().search (state.mRef.mRefID); + + if (!record) + return; + + for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + if (iter->mRef.mRefNum==state.mRef.mRefNum) + { + // overwrite existing reference + iter->load (state); + return; + } + + // new reference + MWWorld::LiveCellRef ref (record); + ref.load (state); + collection.mList.push_back (ref); + } } namespace MWWorld { template - void CellRefList::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) + void CellRefList::load(ESM::CellRef &ref, bool deleted, 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); + const MWWorld::Store &store = esmStore.get(); + + if (const X *ptr = store.search (ref.mRefID)) + { + typename std::list::iterator iter = + std::find(mList.begin(), mList.end(), ref.mRefNum); + + LiveRef liveCellRef (ref, ptr); + + if (deleted) + liveCellRef.mData.setCount (0); - // Skip this when reference was deleted. - // TODO: Support respawning references, in this case, we need to track it somehow. - if (ref.mDeleted) { if (iter != mList.end()) - mList.erase(iter); - return; + *iter = liveCellRef; + else + mList.push_back (liveCellRef); } - - // 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)); + else + { + std::cerr + << "Error: could not resolve cell reference " << ref.mRefID + << " (dropping reference)" << std::endl; } } @@ -117,16 +187,13 @@ namespace MWWorld ESM::CellRef ref; // Get each reference in turn - while (mCell->getNextRef (esm[index], ref)) + bool deleted = false; + while (mCell->getNextRef (esm[index], ref, deleted)) { - 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... + if (deleted) continue; - } - mIds.push_back (lowerCase); + mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); } } @@ -135,7 +202,7 @@ namespace MWWorld void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) { - assert (mCell); + assert (mCell); if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. @@ -148,104 +215,30 @@ namespace MWWorld mCell->restore (esm[index], i); ESM::CellRef ref; + ref.mRefNum.mContentFile = -1; // Get each reference in turn - while(mCell->getNextRef(esm[index], ref)) + bool deleted = false; + while(mCell->getNextRef(esm[index], ref, deleted)) { // 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); + 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; - 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"; - } + + loadRef (ref, deleted, store); } } // 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"; - } + loadRef (ref, false, store); } } @@ -274,4 +267,198 @@ namespace MWWorld return Ptr(); } + + void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) + { + Misc::StringUtils::toLower (ref.mRefID); + + switch (store.find (ref.mRefID)) + { + case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; + case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; + case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; + case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; + case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; + case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; + case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; + case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; + case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; + case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; + case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; + case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; + case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; + case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; + case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; + case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; + case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; + case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; + case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; + + case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; + + default: + std::cerr + << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; + } + } + + void CellStore::loadState (const ESM::CellState& state) + { + if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) + mWaterLevel = state.mWaterLevel; + + mWaterLevel = state.mWaterLevel; + } + + void CellStore::saveState (ESM::CellState& state) const + { + state.mId = mCell->getCellId(); + + if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) + state.mWaterLevel = mWaterLevel; + + state.mWaterLevel = mWaterLevel; + } + + void CellStore::writeReferences (ESM::ESMWriter& writer) const + { + writeReferenceCollection (writer, mActivators); + writeReferenceCollection (writer, mPotions); + writeReferenceCollection (writer, mAppas); + writeReferenceCollection (writer, mArmors); + writeReferenceCollection (writer, mBooks); + writeReferenceCollection (writer, mClothes); + writeReferenceCollection (writer, mContainers); + writeReferenceCollection (writer, mCreatures); + writeReferenceCollection (writer, mDoors); + writeReferenceCollection (writer, mIngreds); + writeReferenceCollection (writer, mCreatureLists); + writeReferenceCollection (writer, mItemLists); + writeReferenceCollection (writer, mLights); + writeReferenceCollection (writer, mLockpicks); + writeReferenceCollection (writer, mMiscItems); + writeReferenceCollection (writer, mNpcs); + writeReferenceCollection (writer, mProbes); + writeReferenceCollection (writer, mRepairs); + writeReferenceCollection (writer, mStatics); + writeReferenceCollection (writer, mWeapons); + } + + void CellStore::readReferences (ESM::ESMReader& reader, + const std::map& contentFileMap) + { + while (reader.isNextSub ("OBJE")) + { + unsigned int id = 0; + reader.getHT (id); + + switch (id) + { + case ESM::REC_ACTI: + + readReferenceCollection (reader, mActivators, contentFileMap); + break; + + case ESM::REC_ALCH: + + readReferenceCollection (reader, mPotions, contentFileMap); + break; + + case ESM::REC_APPA: + + readReferenceCollection (reader, mAppas, contentFileMap); + break; + + case ESM::REC_ARMO: + + readReferenceCollection (reader, mArmors, contentFileMap); + break; + + case ESM::REC_BOOK: + + readReferenceCollection (reader, mBooks, contentFileMap); + break; + + case ESM::REC_CLOT: + + readReferenceCollection (reader, mClothes, contentFileMap); + break; + + case ESM::REC_CONT: + + readReferenceCollection (reader, mContainers, contentFileMap); + break; + + case ESM::REC_CREA: + + readReferenceCollection (reader, mCreatures, contentFileMap); + break; + + case ESM::REC_DOOR: + + readReferenceCollection (reader, mDoors, contentFileMap); + break; + + case ESM::REC_INGR: + + readReferenceCollection (reader, mIngreds, contentFileMap); + break; + + case ESM::REC_LEVC: + + readReferenceCollection (reader, mCreatureLists, contentFileMap); + break; + + case ESM::REC_LEVI: + + readReferenceCollection (reader, mItemLists, contentFileMap); + break; + + case ESM::REC_LIGH: + + readReferenceCollection (reader, mLights, contentFileMap); + break; + + case ESM::REC_LOCK: + + readReferenceCollection (reader, mLockpicks, contentFileMap); + break; + + case ESM::REC_MISC: + + readReferenceCollection (reader, mMiscItems, contentFileMap); + break; + + case ESM::REC_NPC_: + + readReferenceCollection (reader, mNpcs, contentFileMap); + break; + + case ESM::REC_PROB: + + readReferenceCollection (reader, mProbes, contentFileMap); + break; + + case ESM::REC_REPA: + + readReferenceCollection (reader, mRepairs, contentFileMap); + break; + + case ESM::REC_STAT: + + readReferenceCollection (reader, mStatics, contentFileMap); + break; + + case ESM::REC_WEAP: + + readReferenceCollection (reader, mWeapons, contentFileMap); + break; + + default: + + throw std::runtime_error ("unknown type in cell reference section"); + } + } + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index bcbc5e415..a4f219013 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -7,6 +7,11 @@ #include "livecellref.hpp" #include "esmstore.hpp" +namespace ESM +{ + struct CellState; +} + namespace MWWorld { @@ -25,7 +30,7 @@ namespace MWWorld // 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); + void load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); LiveRef *find (const std::string& name) { @@ -133,6 +138,14 @@ namespace MWWorld Ptr searchInContainer (const std::string& id); + void loadState (const ESM::CellState& state); + + void saveState (ESM::CellState& state) const; + + void writeReferences (ESM::ESMWriter& writer) const; + + void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap); + private: template @@ -140,9 +153,12 @@ namespace MWWorld { for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) - if (!functor (iter->mRef, iter->mData)) + { + if (!iter->mData.getCount()) + continue; + if (!functor (MWWorld::Ptr(&*iter, this))) return false; - + } return true; } @@ -151,6 +167,10 @@ namespace MWWorld void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); + void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); + ///< Make case-adjustments to \a ref and insert it into the respective container. + /// + /// Invalid \a ref objects are silently dropped. }; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index de5093547..2110086d3 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -92,6 +92,11 @@ namespace MWWorld throw std::runtime_error("class cannot hit"); } + void Class::block(const Ptr &ptr) const + { + throw std::runtime_error("class cannot block"); + } + void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const { throw std::runtime_error("class cannot be hit"); @@ -122,6 +127,11 @@ namespace MWWorld throw std::runtime_error ("class does not have an inventory store"); } + bool Class::hasInventoryStore(const Ptr &ptr) const + { + return false; + } + void Class::lock (const Ptr& ptr, int lockLevel) const { throw std::runtime_error ("class does not support locking"); @@ -132,24 +142,19 @@ namespace MWWorld throw std::runtime_error ("class does not support unlocking"); } - std::string Class::getScript (const Ptr& ptr) const - { - return ""; - } - - void Class::setForceStance (const Ptr& ptr, Stance stance, bool force) const + void Class::setRemainingUsageTime (const Ptr& ptr, float duration) const { - throw std::runtime_error ("stance not supported by class"); + throw std::runtime_error ("class does not support time-based uses"); } - void Class::setStance (const Ptr& ptr, Stance stance, bool set) const + float Class::getRemainingUsageTime (const Ptr& ptr) const { - throw std::runtime_error ("stance not supported by class"); + return -1; } - bool Class::getStance (const Ptr& ptr, Stance stance, bool ignoreForce) const + std::string Class::getScript (const Ptr& ptr) const { - return false; + return ""; } float Class::getSpeed (const Ptr& ptr) const @@ -162,7 +167,7 @@ namespace MWWorld return 0; } - float Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { throw std::runtime_error ("class does not support enchanting"); } @@ -222,11 +227,6 @@ namespace MWWorld return false; } - bool Class::hasDetected (const MWWorld::Ptr& ptr, const MWWorld::Ptr& ptr2) const - { - return true; - } - float Class::getArmorRating (const MWWorld::Ptr& ptr) const { throw std::runtime_error("Class does not support armor rating"); @@ -362,4 +362,23 @@ namespace MWWorld return newPtr; } + + bool Class::isFlying(const Ptr &ptr) const + { + return false; + } + + int Class::getSkill(const MWWorld::Ptr& ptr, int skill) const + { + throw std::runtime_error("class does not support skills"); + } + + int Class::getBloodTexture (const MWWorld::Ptr& ptr) const + { + throw std::runtime_error("class does not support gore"); + } + + void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} + + void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 1db09647e..ad2cc3af4 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -9,6 +9,11 @@ #include "ptr.hpp" +namespace ESM +{ + struct ObjectState; +} + namespace Ogre { class Vector3; @@ -70,7 +75,7 @@ namespace MWWorld /// NPC-stances. enum Stance { - Run, Sneak, Combat + Run, Sneak }; virtual ~Class(); @@ -128,6 +133,10 @@ namespace MWWorld /// actor responsible for the attack, and \a successful specifies if the hit is /// successful or not. + virtual void block (const Ptr& ptr) const; + ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield + /// (default implementation: throw an exception) + virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const; ///< Sets a new current health value for the actor, optionally specifying the object causing /// the change. Use this instead of using CreatureStats directly as this will make sure the @@ -150,25 +159,27 @@ namespace MWWorld ///< Return inventory store or throw an exception, if class does not have a /// inventory store (default implementation: throw an exceoption) + virtual bool hasInventoryStore (const Ptr& ptr) const; + ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) + virtual void lock (const Ptr& ptr, int lockLevel) const; ///< Lock object (default implementation: throw an exception) virtual void unlock (const Ptr& ptr) const; ///< Unlock object (default implementation: throw an exception) + virtual void setRemainingUsageTime (const Ptr& ptr, float duration) const; + ///< Sets the remaining duration of the object, such as an equippable light + /// source. (default implementation: throw an exception) + + virtual float getRemainingUsageTime (const Ptr& ptr) const; + ///< Returns the remaining duration of the object, such as an equippable light + /// source. (default implementation: -1, i.e. infinite) + virtual std::string getScript (const Ptr& ptr) const; ///< Return name of the script attached to ptr (default implementation: return an empty /// string). - virtual void setForceStance (const Ptr& ptr, Stance stance, bool force) const; - ///< Force or unforce a stance. - - virtual void setStance (const Ptr& ptr, Stance stance, bool set) const; - ///< Set or unset a stance. - - virtual bool getStance (const Ptr& ptr, Stance stance, bool ignoreForce = false) const; - ///< Check if a stance is active or not. - virtual float getSpeed (const Ptr& ptr) const; ///< Return movement speed. @@ -232,11 +243,6 @@ namespace MWWorld /// /// (default implementation: return false) - virtual bool hasDetected (const MWWorld::Ptr& ptr, const MWWorld::Ptr& ptr2) const; - ///< Has \æ ptr detected \a ptr2? - /// - /// (default implementation: return false) - virtual std::string getUpSoundId (const Ptr& ptr) const; ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) @@ -258,7 +264,7 @@ namespace MWWorld ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; ///< @return the number of enchantment points available for possible enchanting virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; @@ -282,6 +288,11 @@ namespace MWWorld virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; } + + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) + virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual Ptr copyToCell(const Ptr &ptr, CellStore &cell) const; @@ -296,6 +307,18 @@ namespace MWWorld return false; } + virtual bool isFlying(const MWWorld::Ptr& ptr) const; + + virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index c6768f5fd..bd0704724 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -5,17 +5,13 @@ #include #include -#include - -#include -#include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/scriptmanager.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/levelledlist.hpp" #include "manualref.hpp" #include "refdata.hpp" @@ -51,7 +47,7 @@ namespace for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { - if (Misc::StringUtils::lowerCase (iter->mBase->mId)==id2) + if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2)) { MWWorld::Ptr ptr (&*iter, 0); ptr.setContainerStore (store); @@ -63,7 +59,57 @@ namespace } } -MWWorld::ContainerStore::ContainerStore() : mStateId (0), mCachedWeight (0), mWeightUpToDate (false) {} +template +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList& collection, + const ESM::ObjectState& state) +{ + if (!LiveCellRef::checkState (state)) + return ContainerStoreIterator (this); // not valid anymore with current content files -> skip + + const T *record = MWBase::Environment::get().getWorld()->getStore(). + get().search (state.mRef.mRefID); + + if (!record) + return ContainerStoreIterator (this); + + LiveCellRef ref (record); + ref.load (state); + ref.mRef.mRefNum.mContentFile = -1; + collection.mList.push_back (ref); + + return ContainerStoreIterator (this, --collection.mList.end()); +} + +template +void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::ObjectState& state) const +{ + ref.save (state); +} + +template +void MWWorld::ContainerStore::storeStates (const CellRefList& collection, + std::vector > >& states, bool equipable) const +{ + for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + { + ESM::ObjectState state; + storeState (*iter, state); + int slot = equipable ? getSlot (*iter) : -1; + states.push_back (std::make_pair (state, std::make_pair (T::sRecordId, slot))); + } +} + +int MWWorld::ContainerStore::getSlot (const MWWorld::LiveCellRefBase& ref) const +{ + return -1; +} + +void MWWorld::ContainerStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) {} + +const std::string MWWorld::ContainerStore::sGoldId = "gold_001"; + +MWWorld::ContainerStore::ContainerStore() : mCachedWeight (0), mWeightUpToDate (false) {} MWWorld::ContainerStore::~ContainerStore() {} @@ -77,35 +123,91 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() return ContainerStoreIterator (this); } +int MWWorld::ContainerStore::count(const std::string &id) +{ + int total=0; + for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, id)) + total += iter->getRefData().getCount(); + return total; +} + +void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) +{ + if (ptr.getRefData().getCount() <= 1) + return; + addNewStack(ptr, ptr.getRefData().getCount()-1); + remove(ptr, ptr.getRefData().getCount()-1, container); +} + bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { - /// \todo add current enchantment charge here when it is implemented - if ( Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID) - && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks - && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) + const MWWorld::Class& cls1 = MWWorld::Class::get(ptr1); + const MWWorld::Class& cls2 = MWWorld::Class::get(ptr2); + + if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID)) + return false; + + // If it has an enchantment, don't stack when some of the charge is already used + if (!ptr1.getClass().getEnchantment(ptr1).empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + ptr1.getClass().getEnchantment(ptr1)); + float maxCharge = enchantment->mData.mCharge; + float enchantCharge1 = ptr1.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr1.getCellRef().mEnchantmentCharge; + float enchantCharge2 = ptr2.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr2.getCellRef().mEnchantmentCharge; + if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge) + return false; + } + + return ptr1 != ptr2 // an item never stacks onto itself && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul - // item that is already partly used up never stacks - && (!MWWorld::Class::get(ptr1).hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1 - || MWWorld::Class::get(ptr1).getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge) - && (!MWWorld::Class::get(ptr2).hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1 - || MWWorld::Class::get(ptr2).getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge)) - return true; - return false; + && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) + + && cls1.getScript(ptr1) == cls2.getScript(ptr2) + + // item that is already partly used up never stacks + && (!cls1.hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1 + || cls1.getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge) + && (!cls2.hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1 + || cls2.getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr) { - MWWorld::ContainerStoreIterator it = addImp(itemPtr); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); + // a bit pointless to set owner for the player + if (actorPtr.getRefData().getHandle() != "player") + return add(ref.getPtr(), count, actorPtr, true); + else + return add(ref.getPtr(), count, actorPtr, false); +} + +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner) +{ + MWWorld::ContainerStoreIterator it = addImp(itemPtr, count); MWWorld::Ptr item = *it; + // we may have copied an item from the world, so reset a few things first + item.getRefData().setBaseNode(NULL); + item.getCellRef().mPos.rot[0] = 0; + item.getCellRef().mPos.rot[1] = 0; + item.getCellRef().mPos.rot[2] = 0; + item.getCellRef().mPos.pos[0] = 0; + item.getCellRef().mPos.pos[1] = 0; + item.getCellRef().mPos.pos[2] = 0; + + if (setOwner && actorPtr.getClass().isActor()) + item.getCellRef().mOwner = actorPtr.getCellRef().mRefID; + std::string script = MWWorld::Class::get(item).getScript(item); if(script != "") { CellStore *cell; - Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); + Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); if(&(MWWorld::Class::get (player).getContainerStore (player)) == this) { @@ -125,7 +227,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr return it; } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count) { int type = getType(ptr); @@ -140,22 +242,20 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025") || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100")) { - MWWorld::ManualRef ref(esmStore, "Gold_001"); + int realCount = count * ptr.getClass().getValue(ptr); - int count = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount(); - - ref.getPtr().getRefData().setCount(count); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { - if (Misc::StringUtils::ciEqual((*iter).get()->mRef.mRefID, "gold_001")) + if (Misc::StringUtils::ciEqual((*iter).getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) { - (*iter).getRefData().setCount( (*iter).getRefData().getCount() + count); + iter->getRefData().setCount(iter->getRefData().getCount() + realCount); flagAsModified(); return iter; } } - return addImpl(ref.getPtr()); + MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount); + return addNewStack(ref.getPtr(), realCount); } // determine whether to stack or not @@ -164,17 +264,17 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) if (stacks(*iter, ptr)) { // stack - iter->getRefData().setCount( iter->getRefData().getCount() + ptr.getRefData().getCount() ); + iter->getRefData().setCount( iter->getRefData().getCount() + count ); flagAsModified(); return iter; } } // if we got here, this means no stacking - return addImpl(ptr); + return addNewStack(ptr, count); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr, int count) { ContainerStoreIterator it = begin(); @@ -194,88 +294,92 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr case Type_Weapon: weapons.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --weapons.mList.end()); break; } + it->getRefData().setCount(count); + flagAsModified(); return it; } -void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store) +int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor) +{ + int toRemove = count; + + for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, itemId)) + toRemove -= remove(*iter, toRemove, actor); + + flagAsModified(); + + // number of removed items + return count - toRemove; +} + +int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) +{ + assert(this == item.getContainerStore()); + + int toRemove = count; + RefData& itemRef = item.getRefData(); + + if (itemRef.getCount() <= toRemove) + { + toRemove -= itemRef.getCount(); + itemRef.setCount(0); + } + else + { + itemRef.setCount(itemRef.getCount() - toRemove); + toRemove = 0; + } + + flagAsModified(); + + // number of removed items + return count - toRemove; +} + +void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, const MWWorld::ESMStore& store) { for (std::vector::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); ++iter) { std::string id = iter->mItem.toString(); - addInitialItem(id, owner, iter->mCount); + addInitialItem(id, owner, faction, iter->mCount); } flagAsModified(); } -void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, unsigned char failChance, bool topLevel) +void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, + int count, bool topLevel) { count = std::abs(count); /// \todo implement item restocking (indicated by negative count) - try + ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); + + if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) { - ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id); + const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; - if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) + if (topLevel && count > 1 && levItem->mFlags & ESM::ItemLevList::Each) { - const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; - const std::vector& items = levItem->mList; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - int playerLevel = MWWorld::Class::get(player).getCreatureStats(player).getLevel(); - - failChance += levItem->mChanceNone; - - if (topLevel && count > 1 && levItem->mFlags & ESM::ItemLevList::Each) - { - for (int i=0; i (std::rand()) / RAND_MAX; - if (random >= failChance/100.f) - { - std::vector candidates; - int highestLevel = 0; - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) - { - if (it->mLevel > highestLevel) - highestLevel = it->mLevel; - } - - std::pair highest = std::make_pair(-1, ""); - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) - { - if (playerLevel >= it->mLevel - && (levItem->mFlags & ESM::ItemLevList::AllLevels || it->mLevel == highestLevel)) - { - candidates.push_back(it->mId); - if (it->mLevel >= highest.first) - highest = std::make_pair(it->mLevel, it->mId); - } - - } - if (candidates.empty()) - return; - std::string item = candidates[std::rand()%candidates.size()]; - addInitialItem(item, owner, count, failChance, false); - } + for (int i=0; i()->mBase, false); + if (id.empty()) + return; + addInitialItem(id, owner, faction, count, false); } } - catch (std::logic_error& e) + else { - // Vanilla doesn't fail on nonexistent items in levelled lists - std::cerr << "Warning: ignoring nonexistent item '" << id << "'" << std::endl; - return; + ref.getPtr().getCellRef().mOwner = owner; + ref.getPtr().getCellRef().mFaction = faction; + addImp (ref.getPtr(), count); } } @@ -289,15 +393,9 @@ void MWWorld::ContainerStore::clear() void MWWorld::ContainerStore::flagAsModified() { - ++mStateId; mWeightUpToDate = false; } -int MWWorld::ContainerStore::getStateId() const -{ - return mStateId; -} - float MWWorld::ContainerStore::getWeight() const { if (!mWeightUpToDate) @@ -445,6 +543,69 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) return Ptr(); } +void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const +{ + state.mItems.clear(); + + storeStates (potions, state.mItems); + storeStates (appas, state.mItems); + storeStates (armors, state.mItems, true); + storeStates (books, state.mItems); + storeStates (clothes, state.mItems, true); + storeStates (ingreds, state.mItems); + storeStates (lockpicks, state.mItems, true); + storeStates (miscItems, state.mItems); + storeStates (probes, state.mItems, true); + storeStates (repairs, state.mItems); + storeStates (weapons, state.mItems, true); + + state.mLights.clear(); + + for (MWWorld::CellRefList::List::const_iterator iter (lights.mList.begin()); + iter!=lights.mList.end(); ++iter) + { + ESM::LightState objectState; + storeState (*iter, objectState); + state.mLights.push_back (std::make_pair (objectState, getSlot (*iter))); + } +} + +void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) +{ + clear(); + + for (std::vector > >::const_iterator + iter (state.mItems.begin()); iter!=state.mItems.end(); ++iter) + { + int slot = iter->second.second; + + switch (iter->second.first) + { + case ESM::REC_ALCH: getState (potions, iter->first); break; + case ESM::REC_APPA: getState (appas, iter->first); break; + case ESM::REC_ARMO: setSlot (getState (armors, iter->first), slot); break; + case ESM::REC_BOOK: getState (books, iter->first); break; + case ESM::REC_CLOT: setSlot (getState (clothes, iter->first), slot); break; + case ESM::REC_INGR: getState (ingreds, iter->first); break; + case ESM::REC_LOCK: setSlot (getState (lockpicks, iter->first), slot); break; + case ESM::REC_MISC: getState (miscItems, iter->first); break; + case ESM::REC_PROB: setSlot (getState (probes, iter->first), slot); break; + case ESM::REC_REPA: getState (repairs, iter->first); break; + case ESM::REC_WEAP: setSlot (getState (weapons, iter->first), slot); break; + + default: + + std::cerr << "invalid item type in inventory state" << std::endl; + } + } + + for (std::vector >::const_iterator iter (state.mLights.begin()); + iter!=state.mLights.end(); ++iter) + { + getState (lights, iter->first); + } +} + MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container) : mType (-1), mMask (0), mContainer (container) @@ -486,6 +647,11 @@ MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *contain MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList::List::iterator iterator) : mType(MWWorld::ContainerStore::Type_Weapon), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mWeapon(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator( const ContainerStoreIterator& src ) +{ + copy(src); +} + void MWWorld::ContainerStoreIterator::incType() { if (mType==0) @@ -738,6 +904,41 @@ const MWWorld::ContainerStore *MWWorld::ContainerStoreIterator::getContainerStor return mContainer; } +void MWWorld::ContainerStoreIterator::copy(const ContainerStoreIterator& src) +{ + mType = src.mType; + mMask = src.mMask; + mContainer = src.mContainer; + mPtr = src.mPtr; + + switch (mType) + { + case MWWorld::ContainerStore::Type_Potion: mPotion = src.mPotion; break; + case MWWorld::ContainerStore::Type_Apparatus: mApparatus = src.mApparatus; break; + case MWWorld::ContainerStore::Type_Armor: mArmor = src.mArmor; break; + case MWWorld::ContainerStore::Type_Book: mBook = src.mBook; break; + case MWWorld::ContainerStore::Type_Clothing: mClothing = src.mClothing; break; + case MWWorld::ContainerStore::Type_Ingredient: mIngredient = src.mIngredient; break; + case MWWorld::ContainerStore::Type_Light: mLight = src.mLight; break; + case MWWorld::ContainerStore::Type_Lockpick: mLockpick = src.mLockpick; break; + case MWWorld::ContainerStore::Type_Miscellaneous: mMiscellaneous = src.mMiscellaneous; break; + case MWWorld::ContainerStore::Type_Probe: mProbe = src.mProbe; break; + case MWWorld::ContainerStore::Type_Repair: mRepair = src.mRepair; break; + case MWWorld::ContainerStore::Type_Weapon: mWeapon = src.mWeapon; break; + case -1: break; + default: assert(0); + } +} + +MWWorld::ContainerStoreIterator& MWWorld::ContainerStoreIterator::operator=( const ContainerStoreIterator& rhs ) +{ + if (this!=&rhs) + { + copy(rhs); + } + return *this; +} + bool MWWorld::operator== (const ContainerStoreIterator& left, const ContainerStoreIterator& right) { return left.isEqual (right); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 9a11f1603..acf429891 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -8,6 +8,7 @@ namespace ESM { struct InventoryList; + struct InventoryState; } namespace MWWorld @@ -35,6 +36,8 @@ namespace MWWorld static const int Type_All = 0xffff; + static const std::string sGoldId; + private: MWWorld::CellRefList potions; @@ -49,11 +52,28 @@ namespace MWWorld MWWorld::CellRefList probes; MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; - int mStateId; mutable float mCachedWeight; mutable bool mWeightUpToDate; - ContainerStoreIterator addImp (const Ptr& ptr); - void addInitialItem (const std::string& id, const std::string& owner, int count, unsigned char failChance=0, bool topLevel=true); + ContainerStoreIterator addImp (const Ptr& ptr, int count); + void addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, int count, bool topLevel=true); + + template + ContainerStoreIterator getState (CellRefList& collection, + const ESM::ObjectState& state); + + template + void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; + + template + void storeStates (const CellRefList& collection, + std::vector > >& states, + bool equipable = false) const; + + virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const; + ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot). + + virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot); + ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1. public: @@ -61,11 +81,13 @@ namespace MWWorld virtual ~ContainerStore(); + virtual ContainerStore* clone() { return new ContainerStore(*this); } + ContainerStoreIterator begin (int mask = Type_All); ContainerStoreIterator end(); - virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr); + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// /// \note The item pointed to is not required to exist beyond this function call. @@ -73,32 +95,46 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// + /// @param setOwner Set the owner of the added item to \a actorPtr? + /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); + ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) + + int remove(const std::string& itemId, int count, const Ptr& actor); + ///< Remove \a count item(s) designated by \a itemId from this container. + /// + /// @return the number of items actually removed + + virtual int remove(const Ptr& item, int count, const Ptr& actor); + ///< Remove \a count item(s) designated by \a item from this inventory. + /// + /// @return the number of items actually removed + + void unstack (const Ptr& ptr, const Ptr& container); + ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). + + /// @return How many items with refID \a id are in this container? + int count (const std::string& id); + protected: - ContainerStoreIterator addImpl (const Ptr& ptr); - ///< Add the item to this container (no stacking) + ContainerStoreIterator addNewStack (const Ptr& ptr, int count); + ///< Add the item to this container (do not try to stack it onto existing items) + + virtual void flagAsModified(); public: virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); ///< @return true if the two specified objects can stack with each other - void fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store); + void fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, const MWWorld::ESMStore& store); ///< Insert items into *this. - void clear(); + virtual void clear(); ///< Empty container. - virtual void flagAsModified(); - ///< \attention This function is internal to the world model and should not be called from - /// outside. - - int getStateId() const; - ///< This ID is changed every time the container is modified or items in the container - /// are accessed in a way that may be used to modify the item. - /// \note This method of change-tracking will ocasionally yield false positives. - float getWeight() const; ///< Return total weight of the items contained in *this. @@ -108,6 +144,10 @@ namespace MWWorld Ptr search (const std::string& id); + void writeState (ESM::InventoryState& state) const; + + void readState (const ESM::InventoryState& state); + friend class ContainerStoreIterator; }; @@ -157,6 +197,8 @@ namespace MWWorld ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList::List::iterator); ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList::List::iterator); + void copy (const ContainerStoreIterator& src); + void incType(); void nextType(); @@ -173,6 +215,8 @@ namespace MWWorld public: + ContainerStoreIterator(const ContainerStoreIterator& src); + Ptr *operator->() const; Ptr operator*() const; @@ -181,6 +225,8 @@ namespace MWWorld ContainerStoreIterator operator++ (int); + ContainerStoreIterator& operator= (const ContainerStoreIterator& rhs); + bool isEqual (const ContainerStoreIterator& iter) const; int getType() const; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp new file mode 100644 index 000000000..46bd7d3f9 --- /dev/null +++ b/apps/openmw/mwworld/contentloader.hpp @@ -0,0 +1,36 @@ +#ifndef CONTENTLOADER_HPP +#define CONTENTLOADER_HPP + +#include +#include +#include + +#include "components/loadinglistener/loadinglistener.hpp" + +namespace MWWorld +{ + +struct ContentLoader +{ + ContentLoader(Loading::Listener& listener) + : mListener(listener) + { + } + + virtual ~ContentLoader() + { + } + + virtual void load(const boost::filesystem::path& filepath, int& index) + { + std::cout << "Loading content file " << filepath.string() << std::endl; + mListener.setLabel(filepath.string()); + } + + protected: + Loading::Listener& mListener; +}; + +} /* namespace MWWorld */ + +#endif /* CONTENTLOADER_HPP */ diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp new file mode 100644 index 000000000..1b8880d37 --- /dev/null +++ b/apps/openmw/mwworld/esmloader.cpp @@ -0,0 +1,31 @@ +#include "esmloader.hpp" +#include "esmstore.hpp" + +#include "components/to_utf8/to_utf8.hpp" + +namespace MWWorld +{ + +EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& readers, + ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener) + : ContentLoader(listener) + , mStore(store) + , mEsm(readers) + , mEncoder(encoder) +{ +} + +void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +{ + ContentLoader::load(filepath.filename(), index); + + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string()); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); +} + +} /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp new file mode 100644 index 000000000..d799c3f15 --- /dev/null +++ b/apps/openmw/mwworld/esmloader.hpp @@ -0,0 +1,34 @@ +#ifndef ESMLOADER_HPP +#define ESMLOADER_HPP + +#include + +#include "contentloader.hpp" +#include "components/esm/esmreader.hpp" + +namespace ToUTF8 +{ + class Utf8Encoder; +} + +namespace MWWorld +{ + +class ESMStore; + +struct EsmLoader : public ContentLoader +{ + EsmLoader(MWWorld::ESMStore& store, std::vector& readers, + ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); + + void load(const boost::filesystem::path& filepath, int& index); + + private: + std::vector& mEsm; + MWWorld::ESMStore& mStore; + ToUTF8::Utf8Encoder* mEncoder; +}; + +} /* namespace MWWorld */ + +#endif // ESMLOADER_HPP diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7703f2d23..c5c826d47 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -36,7 +36,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) // all files/readers used by the engine. This will greaty accelerate // refnumber mangling, as required for handling moved references. int index = ~0; - const std::vector &masters = esm.getMasters(); + const std::vector &masters = esm.getGameFiles(); std::vector *allPlugins = esm.getGlobalReaderList(); for (size_t j = 0; j < masters.size(); j++) { ESM::Header::MasterData &mast = const_cast(masters[j]); @@ -52,7 +52,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) 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 + std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name + ", but it has not been loaded yet. Please check your load order."; esm.fail(fstring); } @@ -70,8 +70,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) if (it == mStores.end()) { if (n.val == ESM::REC_INFO) { + std::string id = esm.getHNOString("INAM"); if (dialogue) { dialogue->mInfo.push_back(ESM::DialInfo()); + dialogue->mInfo.back().mId = id; dialogue->mInfo.back().load(esm); } else { std::cerr << "error: info record without dialog" << std::endl; @@ -100,17 +102,13 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) 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 = const_cast(mDialogs.find(id)); - assert (dialogue->mId == id); } else { dialogue = 0; } // Insert the reference into the global lookup if (!id.empty() && isCacheableRecord(n.val)) { - mIds[id] = n.val; + mIds[Misc::StringUtils::lowerCase (id)] = n.val; } } listener->setProgress(esm.getFileOffset() / (float)esm.getFileSize() * 1000); @@ -141,4 +139,68 @@ void ESMStore::setUp() mAttributes.setUp(); } + int ESMStore::countSavedGameRecords() const + { + return + mPotions.getDynamicSize() + +mArmors.getDynamicSize() + +mBooks.getDynamicSize() + +mClasses.getDynamicSize() + +mClothes.getDynamicSize() + +mEnchants.getDynamicSize() + +mNpcs.getDynamicSize() + +mSpells.getDynamicSize() + +mWeapons.getDynamicSize(); + } + + void ESMStore::write (ESM::ESMWriter& writer) const + { + mPotions.write (writer); + mArmors.write (writer); + mBooks.write (writer); + mClasses.write (writer); + mClothes.write (writer); + mEnchants.write (writer); + mSpells.write (writer); + mWeapons.write (writer); + mNpcs.write (writer); + } + + bool ESMStore::readRecord (ESM::ESMReader& reader, int32_t type) + { + switch (type) + { + case ESM::REC_ALCH: + case ESM::REC_ARMO: + case ESM::REC_BOOK: + case ESM::REC_CLAS: + case ESM::REC_CLOT: + case ESM::REC_ENCH: + case ESM::REC_SPEL: + case ESM::REC_WEAP: + case ESM::REC_NPC_: + + mStores[type]->read (reader); + + if (type==ESM::REC_NPC_) + { + // NPC record will always be last and we know that there can be only one + // dynamic NPC record (player) -> We are done here with dynamic record laoding + setUp(); + + const ESM::NPC *player = mNpcs.find ("player"); + + if (!mRaces.find (player->mRace) || + !mClasses.find (player->mClass)) + throw std::runtime_error ("Invalid player record (race or class unavilable"); + } + + return true; + + default: + + return false; + } + } + } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index ebb086cee..e6730c307 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -24,10 +24,8 @@ namespace MWWorld Store mBirthSigns; Store mClasses; Store mClothes; - Store mContChange; Store mContainers; Store mCreatures; - Store mCreaChange; Store mDialogs; Store mDoors; Store mEnchants; @@ -40,7 +38,6 @@ namespace MWWorld Store mLockpicks; Store mMiscItems; Store mNpcs; - Store mNpcChange; Store mProbes; Store mRaces; Store mRegions; @@ -103,7 +100,7 @@ namespace MWWorld { // 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; @@ -114,10 +111,8 @@ namespace MWWorld mStores[ESM::REC_CELL] = &mCells; mStores[ESM::REC_CLAS] = &mClasses; mStores[ESM::REC_CLOT] = &mClothes; - mStores[ESM::REC_CNTC] = &mContChange; mStores[ESM::REC_CONT] = &mContainers; mStores[ESM::REC_CREA] = &mCreatures; - mStores[ESM::REC_CREC] = &mCreaChange; mStores[ESM::REC_DIAL] = &mDialogs; mStores[ESM::REC_DOOR] = &mDoors; mStores[ESM::REC_ENCH] = &mEnchants; @@ -133,7 +128,6 @@ namespace MWWorld mStores[ESM::REC_LTEX] = &mLandTextures; mStores[ESM::REC_MISC] = &mMiscItems; mStores[ESM::REC_NPC_] = &mNpcs; - mStores[ESM::REC_NPCC] = &mNpcChange; mStores[ESM::REC_PGRD] = &mPathgrids; mStores[ESM::REC_PROB] = &mProbes; mStores[ESM::REC_RACE] = &mRaces; @@ -215,6 +209,13 @@ namespace MWWorld // 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(); + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); + ///< \return Known type? }; template <> @@ -287,11 +288,6 @@ namespace MWWorld return mClothes; } - template <> - inline const Store &ESMStore::get() const { - return mContChange; - } - template <> inline const Store &ESMStore::get() const { return mContainers; @@ -302,11 +298,6 @@ namespace MWWorld return mCreatures; } - template <> - inline const Store &ESMStore::get() const { - return mCreaChange; - } - template <> inline const Store &ESMStore::get() const { return mDialogs; @@ -367,11 +358,6 @@ namespace MWWorld return mNpcs; } - template <> - inline const Store &ESMStore::get() const { - return mNpcChange; - } - template <> inline const Store &ESMStore::get() const { return mProbes; diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index a905f8aae..879ffa8e3 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -3,21 +3,15 @@ #include +#include + +#include +#include + #include "esmstore.hpp" 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); @@ -38,122 +32,78 @@ namespace MWWorld return iter; } - Globals::Globals (const MWWorld::ESMStore& store) + void Globals::fill (const MWWorld::ESMStore& store) { - const MWWorld::Store &globals = store.get(); - MWWorld::Store::iterator iter = globals.begin(); - for (; iter != globals.end(); ++iter) - { - char type = ' '; - Data value; - - switch (iter->mValue.getType()) - { - case ESM::VT_Short: - - type = 's'; - value.mShort = iter->mValue.getInteger(); - break; + mVariables.clear(); - case ESM::VT_Long: + const MWWorld::Store& globals = store.get(); - type = 'l'; - value.mLong = iter->mValue.getInteger(); - break; - - case ESM::VT_Float: - - type = 'f'; - 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))); + for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); + ++iter) + { + mVariables.insert (std::make_pair (iter->mId, iter->mValue)); } } - const Globals::Data& Globals::operator[] (const std::string& name) const + const ESM::Variant& Globals::operator[] (const std::string& name) const { - Collection::const_iterator iter = find (name); - - return iter->second.second; + return find (name)->second; } - Globals::Data& Globals::operator[] (const std::string& name) + ESM::Variant& Globals::operator[] (const std::string& name) { - Collection::iterator iter = find (name); - - return iter->second.second; + return find (name)->second; } - void Globals::setInt (const std::string& name, int value) + char Globals::getType (const std::string& name) const { - Collection::iterator iter = find (name); + Collection::const_iterator iter = mVariables.find (name); - switch (iter->second.first) + if (iter==mVariables.end()) + return ' '; + + switch (iter->second.getType()) { - case 's': iter->second.second.mShort = value; break; - case 'l': iter->second.second.mLong = value; break; - case 'f': iter->second.second.mFloat = value; break; + case ESM::VT_Short: return 's'; + case ESM::VT_Long: return 'l'; + case ESM::VT_Float: return 'f'; - default: throw std::runtime_error ("unsupported global variable type"); + default: return ' '; } } - void Globals::setFloat (const std::string& name, float value) + int Globals::countSavedGameRecords() const { - 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"); - } + return mVariables.size(); } - int Globals::getInt (const std::string& name) const + void Globals::write (ESM::ESMWriter& writer) const { - Collection::const_iterator iter = find (name); - - switch (iter->second.first) + for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { - case 's': return iter->second.second.mShort; - case 'l': return iter->second.second.mLong; - case 'f': return iter->second.second.mFloat; - - default: throw std::runtime_error ("unsupported global variable type"); + writer.startRecord (ESM::REC_GLOB); + writer.writeHNString ("NAME", iter->first); + iter->second.write (writer, ESM::Variant::Format_Global); + writer.endRecord (ESM::REC_GLOB); } } - float Globals::getFloat (const std::string& name) const + bool Globals::readRecord (ESM::ESMReader& reader, int32_t type) { - Collection::const_iterator iter = find (name); - - switch (iter->second.first) + if (type==ESM::REC_GLOB) { - case 's': return iter->second.second.mShort; - case 'l': return iter->second.second.mLong; - case 'f': return iter->second.second.mFloat; + std::string id = reader.getHNString ("NAME"); - default: throw std::runtime_error ("unsupported global variable type"); - } - } + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id)); - char Globals::getType (const std::string& name) const - { - Collection::const_iterator iter = mVariables.find (name); + if (iter!=mVariables.end()) + iter->second.read (reader, ESM::Variant::Format_Global); + else + reader.skipHRecord(); - if (iter==mVariables.end()) - return ' '; + return true; + } - return iter->second.first; + return false; } } - diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 681bd560e..8f521c8a6 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -5,7 +5,16 @@ #include #include +#include + #include +#include + +namespace ESM +{ + class ESMWriter; + class ESMReader; +} namespace MWWorld { @@ -13,49 +22,37 @@ namespace MWWorld class Globals { - public: - - union Data - { - Interpreter::Type_Float mFloat; - Interpreter::Type_Float mLong; // Why Morrowind, why? :( - Interpreter::Type_Float mShort; - }; - - typedef std::map > Collection; - private: - + + typedef std::map Collection; + Collection mVariables; // type, value - + Collection::const_iterator find (const std::string& name) const; Collection::iterator find (const std::string& name); - + public: - - Globals (const MWWorld::ESMStore& store); - - const Data& operator[] (const std::string& name) const; - - Data& operator[] (const std::string& name); - - void setInt (const std::string& name, int value); - ///< Set value independently from real type. - - void setFloat (const std::string& name, float value); - ///< Set value independently from real type. - - int getInt (const std::string& name) const; - ///< Get value independently from real type. - - float getFloat (const std::string& name) const; - ///< Get value independently from real type. - + + const ESM::Variant& operator[] (const std::string& name) const; + + ESM::Variant& operator[] (const std::string& name); + char getType (const std::string& name) const; ///< If there is no global variable with this name, ' ' is returned. - std::vector getGlobals () const; + void fill (const MWWorld::ESMStore& store); + ///< Replace variables with variables from \a store with default values. + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); + ///< Records for variables that do not exist are dropped silently. + /// + /// \return Known type? + }; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 98cb6d347..e00276293 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -8,8 +8,12 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" + #include "esmstore.hpp" #include "class.hpp" @@ -30,16 +34,41 @@ void MWWorld::InventoryStore::copySlots (const InventoryStore& store) mSlots.push_back (slot); } + + // some const-trickery, required because of a flaw in the handling of MW-references and the + // resulting workarounds + std::size_t distance = std::distance (const_cast (store).begin(), const_cast (store).mSelectedEnchantItem); + ContainerStoreIterator slot = begin(); + std::advance (slot, distance); + mSelectedEnchantItem = slot; } -void MWWorld::InventoryStore::initSlots (TSlots& slots) +void MWWorld::InventoryStore::initSlots (TSlots& slots_) { for (int i=0; i (mSlots.size()); ++i) + if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref) + return i; + + return -1; +} + +void MWWorld::InventoryStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) +{ + if (iter!=end() && slot>=0 && slot=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); if (iterator.getContainerStore()!=this) throw std::runtime_error ("attempt to equip an item that is not in the inventory"); - std::pair, bool> slots; - if (iterator!=end()) - { - slots = Class::get (*iterator).getEquipmentSlots (*iterator); + std::pair, bool> slots_; - if (std::find (slots.first.begin(), slots.first.end(), slot)==slots.first.end()) - throw std::runtime_error ("invalid slot"); - } + slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); + + if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) + throw std::runtime_error ("invalid slot"); - // restack item previously in this slot (if required) if (mSlots[slot] != end()) - { - for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - { - if (stacks(*iter, *mSlots[slot])) - { - iter->getRefData().setCount( iter->getRefData().getCount() + mSlots[slot]->getRefData().getCount() ); - mSlots[slot]->getRefData().setCount(0); - break; - } - } - } + unequipSlot(slot, actor); // unstack item pointed to by iterator if required - if (iterator!=end() && !slots.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped + if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { - // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 - int count = iterator->getRefData().getCount(); - iterator->getRefData().setCount(count-1); - addImpl(*iterator); - iterator->getRefData().setCount(1); + unstack(*iterator, actor); } mSlots[slot] = iterator; flagAsModified(); + + fireEquipmentChangedEvent(); + updateMagicEffects(actor); + + // Update HUD icon for player weapon + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*getSlot(slot)); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) { + // Only *one* change event should be fired + mUpdatesEnabled = false; for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = getSlot(slot); - if (it != end()) - { - equip(slot, 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((actor.getRefData().getHandle() == "player") && (script != "")) - (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); - } - } + unequipSlot(slot, actor); + mUpdatesEnabled = true; + fireEquipmentChangedEvent(); + updateMagicEffects(actor); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) @@ -153,55 +177,70 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) if (mSlots[slot]->getRefData().getCount()<1) { - // object has been deleted - mSlots[slot] = end(); - return end(); + // Object has been deleted + // This should no longer happen, since the new remove function will unequip first + throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object"); } return mSlots[slot]; } -void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) +void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { - const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + TSlots slots_; + initSlots (slots_); - TSlots slots; - initSlots (slots); + // Disable model update during auto-equip + mUpdatesEnabled = false; for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) { Ptr test = *iter; - int testSkill = MWWorld::Class::get (test).getEquipmentSkill (test); + + // Don't autoEquip lights + if (test.getTypeName() == typeid(ESM::Light).name()) + { + continue; + } + + // Only autoEquip if we are the original owner of the item. + // This stops merchants from auto equipping anything you sell to them. + // ...unless this is a companion, he should always equip items given to him. + if (!Misc::StringUtils::ciEqual(test.getCellRef().mOwner, actor.getCellRef().mRefID) && + (actor.getClass().getScript(actor).empty() || + !actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))) + continue; + + int testSkill = test.getClass().getEquipmentSkill (test); std::pair, bool> itemsSlots = - MWWorld::Class::get (*iter).getEquipmentSlots (*iter); + iter->getClass().getEquipmentSlots (*iter); for (std::vector::const_iterator iter2 (itemsSlots.first.begin()); iter2!=itemsSlots.first.end(); ++iter2) { bool use = false; - if (slots.at (*iter2)==end()) + if (slots_.at (*iter2)==end()) use = true; // slot was empty before -> skip all further checks else { - Ptr old = *slots.at (*iter2); + Ptr old = *slots_.at (*iter2); if (!use) { // check skill int oldSkill = - MWWorld::Class::get (old).getEquipmentSkill (old); + old.getClass().getEquipmentSkill (old); if (testSkill!=-1 && oldSkill==-1) use = true; else if (testSkill!=-1 && oldSkill!=-1 && testSkill!=oldSkill) { - if (stats.getSkill (oldSkill).getModified()>stats.getSkill (testSkill).getModified()) + if (actor.getClass().getSkill(actor, oldSkill) > actor.getClass().getSkill (actor, testSkill)) continue; // rejected, because old item better matched the NPC's skills. - if (stats.getSkill (oldSkill).getModified()= - MWWorld::Class::get (test).getValue (test)) + if (old.getClass().getValue (old)>= + test.getClass().getValue (test)) { continue; } @@ -219,15 +258,15 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) } } - switch(MWWorld::Class::get (test).canBeEquipped (test, npc).first) + switch(MWWorld::Class::get (test).canBeEquipped (test, actor).first) { case 0: continue; case 2: - invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end()); + slots_[MWWorld::InventoryStore::Slot_CarriedLeft] = end(); break; case 3: - invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end()); + // Prefer keeping twohanded weapon break; } @@ -236,65 +275,158 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) // unstack item pointed to by iterator if required if (iter->getRefData().getCount() > 1) { - // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 - int count = iter->getRefData().getCount(); - iter->getRefData().setCount(count-1); - addImpl(*iter); - iter->getRefData().setCount(1); + unstack(*iter, actor); } } - slots[*iter2] = iter; + slots_[*iter2] = iter; break; } } bool changed = false; - for (std::size_t i=0; igetStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + std::vector params; - if (!enchantmentId.empty()) + bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); + if (!existed) + { + // Roll some dice, one for each effect + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; + + // Try resisting each effect + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor); + ++i; + } + + // Note that using the RefID as a key here is not entirely correct. + // Consider equipping the same item twice (e.g. a ring) + // However, permanent enchantments with a random magnitude are kind of an exploit anyway, + // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; + } + else + params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID]; - if (enchantment.mData.mType==ESM::Enchantment::ConstantEffect) - mMagicEffects.add (enchantment.mEffects); + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + // Fully resisted? + if (params[i].mMultiplier == 0) + continue; + + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + + if (!existed) + { + // During first auto equip, we don't play any sounds. + // Basically we don't want sounds when the actor is first loaded, + // the items should appear as if they'd always been equipped. + mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, + !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); + + // Apply instant effects + MWMechanics::CastSpell cast(actor, actor); + if (magnitude) + cast.applyInstantEffect(actor, actor, effectIt->mEffectID, magnitude); } + + if (magnitude) + mMagicEffects.add (*effectIt, magnitude); } + } + } - mMagicEffectsUpToDate = true; + // Now drop expired effects + for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin(); + it != mPermanentMagicEffectMagnitudes.end();) + { + bool found = false; + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter == end()) + continue; + if ((**iter).getCellRef().mRefID == it->first) + { + found = true; + } + } + if (!found) + mPermanentMagicEffectMagnitudes.erase(it++); + else + ++it; } - return mMagicEffects; + // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); + + mFirstAutoEquip = false; } void MWWorld::InventoryStore::flagAsModified() { ContainerStore::flagAsModified(); - mMagicEffectsUpToDate = false; } bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) @@ -303,14 +435,16 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) if (!canStack) return false; - // don't stack if the item being checked against is currently equipped. + // don't stack if either item is currently equipped for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { - if (*iter != end() && ptr1 == **iter) - return false; - if (*iter != end() && ptr2 == **iter) - return false; + if (*iter != end() && (ptr1 == **iter || ptr2 == **iter)) + { + bool stackWhenEquipped = MWWorld::Class::get(**iter).getEquipmentSlots(**iter).second; + if (!stackWhenEquipped) + return false; + } } return true; @@ -325,3 +459,203 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem( { return mSelectedEnchantItem; } + +int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor) +{ + int retCount = ContainerStore::remove(item, count, actor); + + if (!item.getRefData().getCount()) + { + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + if (mSlots[slot] == end()) + continue; + + if (*mSlots[slot] == item) + { + unequipSlot(slot, actor); + break; + } + } + } + + // If an armor/clothing item is removed, try to find a replacement, + // but not for the player nor werewolves. + if ((actor.getRefData().getHandle() != "player") + && !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())) + { + std::string type = item.getTypeName(); + if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) + && !actor.getClass().getCreatureStats(actor).isDead()) + autoEquip(actor); + } + + if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() + && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player") + { + mSelectedEnchantItem = end(); + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + } + + updateRechargingItems(); + + return retCount; +} + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) +{ + ContainerStoreIterator it = mSlots[slot]; + + if (it != end()) + { + ContainerStoreIterator retval = it; + + // empty this slot + mSlots[slot] = end(); + + // restack the previously equipped item with other (non-equipped) items + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, *it)) + { + iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); + it->getRefData().setCount(0); + retval = iter; + break; + } + } + + if (actor.getRefData().getHandle() == "player") + { + // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared + const std::string& script = Class::get(*it).getScript(*it); + if (script != "") + (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); + + // Update HUD icon when removing player weapon or selected enchanted item. + // We have to check for both as the weapon could also be the enchanted item. + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + { + // weapon + MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); + } + if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) + { + // enchanted item + mSelectedEnchantItem = end(); + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + } + } + + fireEquipmentChangedEvent(); + updateMagicEffects(actor); + + return retval; + } + + return it; +} + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item, const MWWorld::Ptr& actor) +{ + for (int slot=0; slotequipmentChanged(); +} + +void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) +{ + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter==end()) + continue; + + std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + if (enchantmentId.empty()) + continue; + + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) + continue; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; + magnitude *= params.mMultiplier; + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), "", magnitude); + + ++i; + } + } +} + +void MWWorld::InventoryStore::updateRechargingItems() +{ + mRechargingItems.clear(); + for (ContainerStoreIterator it = begin(); it != end(); ++it) + { + if (it->getClass().getEnchantment(*it) != "") + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + it->getClass().getEnchantment(*it)); + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed + || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + mRechargingItems.push_back(std::make_pair(it, enchantment->mData.mCharge)); + } + } +} + +void MWWorld::InventoryStore::rechargeItems(float duration) +{ + for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) + { + if (it->first->getCellRef().mEnchantmentCharge == -1 + || it->first->getCellRef().mEnchantmentCharge == it->second) + continue; + + static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicItemRechargePerSecond")->getFloat(); + + it->first->getCellRef().mEnchantmentCharge = std::min (it->first->getCellRef().mEnchantmentCharge + fMagicItemRechargePerSecond * duration, + it->second); + } +} + +void MWWorld::InventoryStore::purgeEffect(short effectId) +{ + mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude); +} + +void MWWorld::InventoryStore::clear() +{ + mSlots.clear(); + initSlots (mSlots); + ContainerStore::clear(); +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index f0cba0f9f..714ba47da 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -12,6 +12,25 @@ namespace MWMechanics namespace MWWorld { + class InventoryStoreListener + { + public: + /** + * Fired when items are equipped or unequipped + */ + virtual void equipmentChanged () {} + + /** + * @param effect + * @param isNew Is this effect new (e.g. the item for it was just now manually equipped) + * or was it loaded from a savegame / initial game state? \n + * If it isn't new, non-looping VFX should not be played. + * @param playSound Play effect sound? + */ + virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) {} + + }; + ///< \brief Variant of the ContainerStore for NPCs class InventoryStore : public ContainerStore { @@ -43,19 +62,54 @@ namespace MWWorld private: - mutable MWMechanics::MagicEffects mMagicEffects; - mutable bool mMagicEffectsUpToDate; + MWMechanics::MagicEffects mMagicEffects; + + InventoryStoreListener* mListener; + + // Enables updates of magic effects and actor model whenever items are equipped or unequipped. + // This is disabled during autoequip to avoid excessive updates + bool mUpdatesEnabled; + + bool mFirstAutoEquip; + + // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. + // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. + struct EffectParams + { + // Modifier to scale between min and max magnitude + float mRandom; + // Multiplier for when an effect was fully or partially resisted + float mMultiplier; + }; + + typedef std::map > TEffectMagnitudes; + TEffectMagnitudes mPermanentMagicEffectMagnitudes; typedef std::vector TSlots; - mutable TSlots mSlots; + TSlots mSlots; // selected magic item (for using enchantments of type "Cast once" or "Cast when used") ContainerStoreIterator mSelectedEnchantItem; + // (item, max charge) + typedef std::vector > TRechargingItems; + TRechargingItems mRechargingItems; + void copySlots (const InventoryStore& store); - void initSlots (TSlots& slots); + void initSlots (TSlots& slots_); + + void updateMagicEffects(const Ptr& actor); + void updateRechargingItems(); + + void fireEquipmentChangedEvent(); + + virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const; + ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot). + + virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot); + ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1. public: @@ -65,7 +119,9 @@ namespace MWWorld InventoryStore& operator= (const InventoryStore& store); - virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr); + virtual InventoryStore* clone() { return new InventoryStore(*this); } + + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// Auto-equip items if specific conditions are fulfilled (see the implementation). /// @@ -74,9 +130,11 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// + /// @param setOwner Set the owner of the added item to \a actorPtr? + /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. - void equip (int slot, const ContainerStoreIterator& iterator); + void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \note \a iterator can be an end-iterator void setSelectedEnchantItem(const ContainerStoreIterator& iterator); @@ -92,13 +150,11 @@ namespace MWWorld void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. - void autoEquip (const MWWorld::Ptr& npc); + void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. - const MWMechanics::MagicEffects& getMagicEffects(); + const MWMechanics::MagicEffects& getMagicEffects() const; ///< Return magic effects from worn items. - /// - /// \todo make this const again, after the constness of Ptrs and iterators has been addressed. virtual void flagAsModified(); ///< \attention This function is internal to the world model and should not be called from @@ -106,8 +162,38 @@ namespace MWWorld virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); ///< @return true if the two specified objects can stack with each other - /// @note ptr1 is the item that is already in this container + virtual int remove(const Ptr& item, int count, const Ptr& actor); + ///< Remove \a count item(s) designated by \a item from this inventory. + /// + /// @return the number of items actually removed + + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor); + ///< Unequip \a slot. + /// + /// @return an iterator to the item that was previously in the slot + + ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); + ///< Unequip an item identified by its Ptr. An exception is thrown + /// if the item is not currently equipped. + /// + /// @return an iterator to the item that was previously in the slot + /// (it can be re-stacked so its count may be different than when it + /// was equipped). + + void setListener (InventoryStoreListener* listener, const Ptr& actor); + ///< Set a listener for various events, see \a InventoryStoreListener + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); + + void rechargeItems (float duration); + ///< Restore charge on enchanted items. Note this should only be done for the player. + + void purgeEffect (short effectId); + ///< Remove a magic effect + + virtual void clear(); + ///< Empty container. }; } diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp new file mode 100644 index 000000000..d71704fd7 --- /dev/null +++ b/apps/openmw/mwworld/livecellref.cpp @@ -0,0 +1,29 @@ + +#include "livecellref.hpp" + +#include + +#include "ptr.hpp" +#include "class.hpp" + +void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) +{ + mRef = state.mRef; + mData = RefData (state); + Ptr ptr (this); + mClass->readAdditionalState (ptr, state); +} + +void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const +{ + state.mRef = mRef; + mData.write (state); + /// \todo get rid of this cast once const-correct Ptr are available + Ptr ptr (const_cast (this)); + mClass->writeAdditionalState (ptr, state); +} + +bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) +{ + return true; +} \ No newline at end of file diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 415351e78..b2e4d6d56 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -7,6 +7,11 @@ #include "refdata.hpp" +namespace ESM +{ + struct ObjectState; +} + namespace MWWorld { class Ptr; @@ -29,8 +34,39 @@ namespace MWWorld LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } + + virtual void load (const ESM::ObjectState& state) = 0; + ///< Load state into a LiveCellRef, that has already been initialised with base and class. + /// + /// \attention Must not be called with an invalid \a state. + + virtual void save (ESM::ObjectState& state) const = 0; + ///< Save LiveCellRef state into \a state. + + protected: + + void loadImp (const ESM::ObjectState& state); + ///< Load state into a LiveCellRef, that has already been initialised with base and + /// class. + /// + /// \attention Must not be called with an invalid \a state. + + void saveImp (ESM::ObjectState& state) const; + ///< Save LiveCellRef state into \a state. + + static bool checkStateImp (const ESM::ObjectState& state); + ///< Check if state is valid and report errors. + /// + /// \return Valid? + /// + /// \note Does not check if the RefId exists. }; + inline bool operator== (const LiveCellRefBase& cellRef, const ESM::CellRef::RefNum refNum) + { + return cellRef.mRef.mRefNum==refNum; + } + /// A reference to one object (of any type) in a cell. /// /// Constructing this with a CellRef instance in the constructor means that @@ -50,9 +86,41 @@ namespace MWWorld // The object that this instance is based on. const X* mBase; + + virtual void load (const ESM::ObjectState& state); + ///< Load state into a LiveCellRef, that has already been initialised with base and class. + /// + /// \attention Must not be called with an invalid \a state. + + virtual void save (ESM::ObjectState& state) const; + ///< Save LiveCellRef state into \a state. + + static bool checkState (const ESM::ObjectState& state); + ///< Check if state is valid and report errors. + /// + /// \return Valid? + /// + /// \note Does not check if the RefId exists. }; -// template bool operator==(const LiveCellRef& ref, int pRefnum); + template + void LiveCellRef::load (const ESM::ObjectState& state) + { + loadImp (state); + } + + template + void LiveCellRef::save (ESM::ObjectState& state) const + { + saveImp (state); + } + + template + bool LiveCellRef::checkState (const ESM::ObjectState& state) + { + return checkStateImp (state); + } + } #endif diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 5ec5ca9b5..997e9e32c 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -11,7 +11,7 @@ namespace { template void listCellScripts (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::Ptr::CellStore *cell) + MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) { for (typename MWWorld::CellRefList::List::iterator iter ( cellRefList.mList.begin()); @@ -27,15 +27,15 @@ namespace // Adds scripts for items in containers (containers/npcs/creatures) template void listCellScriptsCont (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::Ptr::CellStore *cell) + MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) { for (typename MWWorld::CellRefList::List::iterator iter ( cellRefList.mList.begin()); iter!=cellRefList.mList.end(); ++iter) { - - MWWorld::Ptr containerPtr (&*iter, cell); - + + MWWorld::Ptr containerPtr (&*iter, cell); + MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr); for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3) { @@ -99,7 +99,7 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) } } -void MWWorld::LocalScripts::addCell (Ptr::CellStore *cell) +void MWWorld::LocalScripts::addCell (CellStore *cell) { listCellScripts (*this, cell->mActivators, cell); listCellScripts (*this, cell->mPotions, cell); @@ -128,7 +128,7 @@ void MWWorld::LocalScripts::clear() mScripts.clear(); } -void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell) +void MWWorld::LocalScripts::clearCell (CellStore *cell) { std::list >::iterator iter = mScripts.begin(); diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 6616165fa..0e21c55ac 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -25,6 +25,8 @@ namespace MWWorld { LiveCellRef ref; ref.mBase = instance; + ref.mRef.mRefNum.mIndex = 0; + ref.mRef.mRefNum.mContentFile = -1; mRef = ref; mPtr = Ptr (&boost::any_cast&> (mRef), 0); @@ -37,7 +39,7 @@ namespace MWWorld public: - ManualRef (const MWWorld::ESMStore& store, const std::string& name) + ManualRef (const MWWorld::ESMStore& store, const std::string& name, const int count=1) { // create if (!create (store.get(), name) && @@ -64,8 +66,9 @@ namespace MWWorld // initialise ESM::CellRef& cellRef = mPtr.getCellRef(); - cellRef.mRefID = name; - cellRef.mRefnum = -1; + cellRef.mRefID = Misc::StringUtils::lowerCase (name); + cellRef.mRefNum.mIndex = 0; + cellRef.mRefNum.mContentFile = -1; cellRef.mScale = 1; cellRef.mFactIndex = 0; cellRef.mCharge = -1; @@ -74,6 +77,7 @@ namespace MWWorld cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; + mPtr.getRefData().setCount(count); } const Ptr& getPtr() const diff --git a/apps/openmw/mwworld/omwloader.cpp b/apps/openmw/mwworld/omwloader.cpp new file mode 100644 index 000000000..8562a4fe0 --- /dev/null +++ b/apps/openmw/mwworld/omwloader.cpp @@ -0,0 +1,17 @@ +#include "omwloader.hpp" + +namespace MWWorld +{ + +OmwLoader::OmwLoader(Loading::Listener& listener) + : ContentLoader(listener) +{ +} + +void OmwLoader::load(const boost::filesystem::path& filepath, int& index) +{ + ContentLoader::load(filepath.filename(), index); +} + +} /* namespace MWWorld */ + diff --git a/apps/openmw/mwworld/omwloader.hpp b/apps/openmw/mwworld/omwloader.hpp new file mode 100644 index 000000000..cb9faa430 --- /dev/null +++ b/apps/openmw/mwworld/omwloader.hpp @@ -0,0 +1,21 @@ +#ifndef OMWLOADER_HPP +#define OMWLOADER_HPP + +#include "contentloader.hpp" + +namespace MWWorld +{ + +/** + * @brief Placeholder for real OpenMW content loader + */ +struct OmwLoader : public ContentLoader +{ + OmwLoader(Loading::Listener& listener); + + void load(const boost::filesystem::path& filepath, int& index); +}; + +} /* namespace MWWorld */ + +#endif /* OMWLOADER_HPP */ diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 433fe0892..a7103b991 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -18,6 +18,8 @@ #include "../mwbase/world.hpp" // FIXME #include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include #include "../mwworld/esmstore.hpp" @@ -106,7 +108,7 @@ namespace MWWorld } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, - bool isFlying, float waterlevel, OEngine::Physic::PhysicEngine *engine) + bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -227,7 +229,10 @@ namespace MWWorld physicActor->setInertialForce(Ogre::Vector3(0.0f)); else { - inertia.z += time*-627.2f; + float diff = time*-627.2f; + if (inertia.z < 0) + diff *= slowFall; + inertia.z += diff; physicActor->setInertialForce(inertia); } physicActor->setOnGround(isOnGround); @@ -573,9 +578,39 @@ namespace MWWorld if(cell->hasWater()) waterlevel = cell->mWater; + float oldHeight = iter->first.getRefData().getPosition().pos[2]; + + const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); + + bool waterCollision = false; + if (effects.get(ESM::MagicEffect::WaterWalking).mMagnitude + && cell->hasWater() + && !world->isUnderwater(iter->first.getCell(), + Ogre::Vector3(iter->first.getRefData().getPosition().pos))) + waterCollision = true; + + btStaticPlaneShape planeShape(btVector3(0,0,1), waterlevel); + btCollisionObject object; + object.setCollisionShape(&planeShape); + + if (waterCollision) + mEngine->dynamicsWorld->addCollisionObject(&object); + + // 100 points of slowfall reduce gravity by 90% (this is just a guess) + float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f); + Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, world->isFlying(iter->first), - waterlevel, mEngine); + waterlevel, slowFall, mEngine); + + if (waterCollision) + mEngine->dynamicsWorld->removeCollisionObject(&object); + + float heightDiff = newpos.z - oldHeight; + + if (heightDiff < 0) + iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + mMovementResults.push_back(std::make_pair(iter->first, newpos)); } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index e26c2e2a5..c1cce84fc 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -1,6 +1,13 @@ #include "player.hpp" +#include + +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -18,8 +25,11 @@ namespace MWWorld { Player::Player (const ESM::NPC *player, const MWBase::World& world) : mCellStore(0), + mLastKnownExteriorPosition(0,0,0), mAutoMove(false), - mForwardBackward (0) + mForwardBackward (0), + mTeleported(false), + mMarkedCell(NULL) { mPlayer.mBase = player; mPlayer.mRef.mRefID = "player"; @@ -31,9 +41,6 @@ namespace MWWorld void Player::set(const ESM::NPC *player) { mPlayer.mBase = player; - - float* playerPos = mPlayer.mData.getPosition().pos; - playerPos[0] = playerPos[1] = playerPos[2] = 0; } void Player::setCell (MWWorld::CellStore *cellStore) @@ -111,14 +118,14 @@ namespace MWWorld void Player::setRunState(bool run) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get(ptr).setStance(ptr, MWWorld::Class::Run, run); + ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, run); } void Player::setSneak(bool sneak) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Sneak, sneak); + ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, sneak); // TODO show sneak indicator only when the player is not detected by any actor MWBase::Environment::get().getWindowManager()->setSneakVisibility(sneak); @@ -145,4 +152,125 @@ namespace MWWorld MWWorld::Ptr ptr = getPlayer(); return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState(); } + + bool Player::wasTeleported() const + { + return mTeleported; + } + + void Player::setTeleported(bool teleported) + { + mTeleported = teleported; + } + + void Player::markPosition(CellStore *markedCell, ESM::Position markedPosition) + { + mMarkedCell = markedCell; + mMarkedPosition = markedPosition; + } + + void Player::getMarkedPosition(CellStore*& markedCell, ESM::Position &markedPosition) const + { + markedCell = mMarkedCell; + if (mMarkedCell) + markedPosition = mMarkedPosition; + } + + void Player::clear() + { + mCellStore = 0; + mSign.clear(); + mMarkedCell = 0; + mAutoMove = false; + mForwardBackward = 0; + mTeleported = false; + } + + void Player::write (ESM::ESMWriter& writer) const + { + ESM::Player player; + + mPlayer.save (player.mObject); + player.mCellId = mCellStore->mCell->getCellId(); + + player.mBirthsign = mSign; + + player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x; + player.mLastKnownExteriorPosition[1] = mLastKnownExteriorPosition.y; + player.mLastKnownExteriorPosition[2] = mLastKnownExteriorPosition.z; + + if (mMarkedCell) + { + player.mHasMark = true; + player.mMarkedPosition = mMarkedPosition; + player.mMarkedCell = mMarkedCell->mCell->getCellId(); + } + else + player.mHasMark = false; + + player.mAutoMove = mAutoMove ? 1 : 0; + + writer.startRecord (ESM::REC_PLAY); + player.save (writer); + writer.endRecord (ESM::REC_PLAY); + } + + bool Player::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type==ESM::REC_PLAY) + { + ESM::Player player; + player.load (reader); + + if (!mPlayer.checkState (player.mObject)) + { + // this is the one object we can not silently drop. + throw std::runtime_error ("invalid player state record (object state)"); + } + + mPlayer.load (player.mObject); + + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + mCellStore = world.getCell (player.mCellId); + + if (!player.mBirthsign.empty() && + !world.getStore().get().search (player.mBirthsign)) + throw std::runtime_error ("invalid player state record (birthsign)"); + + mSign = player.mBirthsign; + + mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0]; + mLastKnownExteriorPosition.y = player.mLastKnownExteriorPosition[1]; + mLastKnownExteriorPosition.z = player.mLastKnownExteriorPosition[2]; + + if (player.mHasMark && !player.mMarkedCell.mPaged) + { + // interior cell -> need to check if it exists (exterior cell will be + // generated on the fly) + + if (!world.getStore().get().search (player.mMarkedCell.mWorldspace)) + player.mHasMark = false; // drop mark silently + } + + if (player.mHasMark) + { + mMarkedPosition = player.mMarkedPosition; + mMarkedCell = world.getCell (player.mMarkedCell); + } + else + { + mMarkedCell = 0; + } + + mAutoMove = player.mAutoMove!=0; + + mForwardBackward = 0; + mTeleported = false; + + return true; + } + + return false; + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index d78b1901c..7eb023a2b 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -6,9 +6,13 @@ #include "../mwmechanics/drawstate.hpp" +#include + namespace ESM { struct NPC; + class ESMWriter; + class ESMReader; } namespace MWBase @@ -28,13 +32,30 @@ namespace MWWorld MWWorld::CellStore *mCellStore; std::string mSign; + Ogre::Vector3 mLastKnownExteriorPosition; + + ESM::Position mMarkedPosition; + // If no position was marked, this is NULL + CellStore* mMarkedCell; + bool mAutoMove; int mForwardBackward; - + bool mTeleported; public: Player(const ESM::NPC *player, const MWBase::World& world); + // For mark/recall magic effects + void markPosition (CellStore* markedCell, ESM::Position markedPosition); + void getMarkedPosition (CellStore*& markedCell, ESM::Position& markedPosition) const; + + /// Interiors can not always be mapped to a world position. However + /// world position is still required for divine / almsivi magic effects + /// and the player arrow on the global map. + /// TODO: This should be stored in the savegame, too. + void setLastKnownExteriorPosition (const Ogre::Vector3& position) { mLastKnownExteriorPosition = position; } + Ogre::Vector3 getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; } + void set (const ESM::NPC *player); void setCell (MWWorld::CellStore *cellStore); @@ -64,6 +85,15 @@ namespace MWWorld void yaw(float yaw); void pitch(float pitch); void roll(float roll); + + bool wasTeleported() const; + void setTeleported(bool teleported); + + void clear(); + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); }; } #endif diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 127ab1364..67bfe4900 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -21,13 +21,18 @@ const std::string& MWWorld::Ptr::getTypeName() const throw std::runtime_error("Can't get type name from an empty object."); } +MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const +{ + if (!mRef) + throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); + + return mRef; +} + ESM::CellRef& MWWorld::Ptr::getCellRef() const { assert(mRef); - if (mContainerStore) - mContainerStore->flagAsModified(); - return mRef->mRef; } @@ -35,9 +40,6 @@ MWWorld::RefData& MWWorld::Ptr::getRefData() const { assert(mRef); - if (mContainerStore) - mContainerStore->flagAsModified(); - return mRef->mData; } diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index e5352da28..1212619d0 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -14,9 +14,6 @@ namespace MWWorld { public: - typedef MWWorld::CellStore CellStore; - ///< \deprecated - MWWorld::LiveCellRefBase *mRef; CellStore *mCell; ContainerStore *mContainerStore; @@ -55,11 +52,13 @@ namespace MWWorld throw std::runtime_error(str.str()); } + MWWorld::LiveCellRefBase *getBase() const; + ESM::CellRef& getCellRef() const; RefData& getRefData() const; - Ptr::CellStore *getCell() const + CellStore *getCell() const { assert(mCell); return mCell; diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index c1a3ae785..8d48078b1 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -3,6 +3,8 @@ #include +#include + #include "customdata.hpp" #include "cellstore.hpp" @@ -32,6 +34,17 @@ namespace MWWorld mCustomData = 0; } + RefData::RefData() + : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0) + { + for (int i=0; i<3; ++i) + { + mLocalRotation.rot[i] = 0; + mPosition.pos[i] = 0; + mPosition.rot[i] = 0; + } + } + RefData::RefData (const ESM::CellRef& cellRef) : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0) @@ -41,6 +54,14 @@ namespace MWWorld mLocalRotation.rot[2]=0; } + RefData::RefData (const ESM::ObjectState& objectState) + : mBaseNode (0), mHasLocals (false), mEnabled (objectState.mEnabled), + mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0) + { + for (int i=0; i<3; ++i) + mLocalRotation.rot[i] = objectState.mLocalRotation[i]; + } + RefData::RefData (const RefData& refData) : mBaseNode(0), mCustomData (0) { @@ -55,6 +76,17 @@ namespace MWWorld } } + void RefData::write (ESM::ObjectState& objectState) const + { + objectState.mHasLocals = false; + objectState.mEnabled = mEnabled; + objectState.mCount = mCount; + objectState.mPosition = mPosition; + + for (int i=0; i<3; ++i) + objectState.mLocalRotation[i] = mLocalRotation.rot[i]; + } + RefData& RefData::operator= (const RefData& refData) { try @@ -88,7 +120,7 @@ namespace MWWorld static const std::string empty; return empty; } - + return mBaseNode->getName(); } @@ -120,7 +152,7 @@ namespace MWWorld { if(count == 0) MWBase::Environment::get().getWorld()->removeRefScript(this); - + mCount = count; } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 642f5412c..19e3d4882 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -14,6 +14,7 @@ namespace ESM { class Script; class CellRef; + struct ObjectState; } namespace MWWorld @@ -48,15 +49,25 @@ namespace MWWorld public: + RefData(); + /// @param cellRef Used to copy constant data such as position into this class where it can /// be altered without effecting the original data. This makes it possible /// to reset the position as the orignal data is still held in the CellRef RefData (const ESM::CellRef& cellRef); + RefData (const ESM::ObjectState& objectState); + ///< Ignores local variables and custom data (not enough context available here to + /// perform these operations). + RefData (const RefData& refData); ~RefData(); + void write (ESM::ObjectState& objectState) const; + ///< Ignores local variables and custom data (not enough context available here to + /// perform these operations). + RefData& operator= (const RefData& refData); /// Return OGRE handle (may be empty). @@ -73,6 +84,11 @@ namespace MWWorld void setLocals (const ESM::Script& script); void setCount (int count); + /// Set object count (an object pile is a simple object with a count >1). + /// + /// \warning Do not call setCount() to add or remove objects from a + /// container or an actor's inventory. Call ContainerStore::add() or + /// ContainerStore::remove() instead. MWScript::Locals& getLocals(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0c98ca504..167adf301 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -111,15 +111,15 @@ namespace MWWorld mRendering.removeCell(*iter); MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); } - void Scene::loadCell (Ptr::CellStore *cell, Loading::Listener* loadingListener) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) { - // register local scripts - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); std::pair result = mActiveCells.insert(cell); if(result.second) @@ -157,14 +157,19 @@ namespace MWWorld mRendering.requestMap(cell); mRendering.configureAmbient(*cell); } + + // register local scripts + // ??? Should this go into the above if block ??? + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); } - void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) + void Scene::playerCellChange(CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) { MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr old = world->getPlayerPtr(); world->getPlayer().setCell(cell); - MWWorld::Ptr player = world->getPlayer().getPlayer(); + MWWorld::Ptr player = world->getPlayerPtr(); mRendering.updatePlayerPtr(player); if (adjustPlayerPos) { @@ -181,7 +186,7 @@ namespace MWWorld MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); - mechMgr->add(player); + mechMgr->updateCell(old, player); mechMgr->watchActor(player); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); @@ -198,14 +203,12 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { + mRendering.enableTerrain(true); Nif::NIFFile::CacheLock cachelock; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - // remove active - MWBase::Environment::get().getMechanicsManager()->remove(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); @@ -347,6 +350,7 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + Nif::NIFFile::CacheLock lock; MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); mRendering.enableTerrain(false); @@ -365,14 +369,14 @@ namespace MWWorld if(!loadcell) { MWBase::World *world = MWBase::Environment::get().getWorld(); - world->moveObject(world->getPlayer().getPlayer(), position.pos[0], position.pos[1], position.pos[2]); + world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]); float x = Ogre::Radian(position.rot[0]).valueDegrees(); float y = Ogre::Radian(position.rot[1]).valueDegrees(); float z = Ogre::Radian(position.rot[2]).valueDegrees(); - world->rotateObject(world->getPlayer().getPlayer(), x, y, z); + world->rotateObject(world->getPlayerPtr(), x, y, z); - MWWorld::Class::get(world->getPlayer().getPlayer()).adjustPosition(world->getPlayer().getPlayer()); + MWWorld::Class::get(world->getPlayerPtr()).adjustPosition(world->getPlayerPtr()); world->getFader()->fadeIn(0.5f); return; } @@ -434,12 +438,10 @@ namespace MWWorld MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - mRendering.enableTerrain(true); - changeCell (x, y, position, true); } - Ptr::CellStore* Scene::getCurrentCell () + CellStore* Scene::getCurrentCell () { return mCurrentCell; } @@ -449,7 +451,7 @@ namespace MWWorld mCellChanged = false; } - int Scene::countRefs (const Ptr::CellStore& cell) + int Scene::countRefs (const CellStore& cell) { return cell.mActivators.mList.size() + cell.mPotions.mList.size() @@ -473,7 +475,7 @@ namespace MWWorld + cell.mNpcs.mList.size(); } - void Scene::insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener) + void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener) { // Loop through all references in the cell insertCellRefList(mRendering, cell.mActivators, cell, *mPhysics, rescale, loadingListener); @@ -485,7 +487,6 @@ namespace MWWorld insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener); insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener); insertCellRefList(mRendering, cell.mIngreds, cell, *mPhysics, rescale, loadingListener); - insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener); insertCellRefList(mRendering, cell.mItemLists, cell, *mPhysics, rescale, loadingListener); insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener); insertCellRefList(mRendering, cell.mLockpicks, cell, *mPhysics, rescale, loadingListener); @@ -497,6 +498,8 @@ namespace MWWorld // Load NPCs and creatures _after_ everything else (important for adjustPosition to work correctly) insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener); insertCellRefList(mRendering, cell.mNpcs, cell, *mPhysics, rescale, loadingListener); + // Since this adds additional creatures, load afterwards, or they would be loaded twice + insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener); } void Scene::addObjectToScene (const Ptr& ptr) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index e3edad352..665274831 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -56,9 +56,9 @@ namespace MWWorld void playerCellChange (CellStore *cell, const ESM::Position& position, bool adjustPlayerPos = true); - void insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener); + void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); - int countRefs (const Ptr::CellStore& cell); + int countRefs (const CellStore& cell); public: @@ -71,8 +71,6 @@ namespace MWWorld void loadCell (CellStore *cell, Loading::Listener* loadingListener); void changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos); - ///< Move from exterior to interior or from interior cell to a different - /// interior cell. CellStore* getCurrentCell (); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 512883f1a..1156cbc15 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -10,7 +10,7 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // 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; @@ -30,17 +30,14 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. - cell->getNextRef(esm, ref); - std::string lowerCase; - - std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + bool deleted = false; + cell->getNextRef(esm, ref, deleted); // 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. cell->mMovedRefs.push_back(cMRef); // But there may be duplicates here! - ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefnum); + 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 @@ -60,7 +57,7 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // copy list into new cell cell->mContextList = oldcell->mContextList; // have new cell replace old cell - *oldcell = *cell; + ESM::Cell::merge(oldcell, cell); } else mInt[idLower] = *cell; } @@ -76,18 +73,18 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // 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); + 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); + 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; + ESM::Cell::merge(oldcell, cell); } else mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 90452f661..7bd00d6bf 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include "recordcmp.hpp" namespace MWWorld @@ -18,10 +20,16 @@ namespace MWWorld virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; + virtual int getDynamicSize() const { return 0; } virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} + + virtual void write (ESM::ESMWriter& writer) const {} + + virtual void read (ESM::ESMReader& reader) {} + ///< Read into dynamic storage }; template @@ -193,6 +201,7 @@ namespace MWWorld void setUp() { //std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); + mShared.clear(); mShared.reserve(mStatic.size()); typename std::map::iterator it = mStatic.begin(); for (; it != mStatic.end(); ++it) { @@ -212,6 +221,11 @@ namespace MWWorld return mShared.size(); } + int getDynamicSize() const + { + return mDynamic.size(); + } + void listIdentifier(std::vector &list) const { list.reserve(list.size() + getSize()); typename std::vector::const_iterator it = mShared.begin(); @@ -258,7 +272,7 @@ namespace MWWorld typename std::vector::iterator sharedIter = mShared.begin(); typename std::vector::iterator end = sharedIter + mStatic.size(); - while (sharedIter != mShared.end() && sharedIter != end) { + while (sharedIter != mShared.end() && sharedIter != end) { if((*sharedIter)->mId == item.mId) { mShared.erase(sharedIter); break; @@ -290,8 +304,42 @@ namespace MWWorld bool erase(const T &item) { return erase(item.mId); } + + void write (ESM::ESMWriter& writer) const + { + for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); + ++iter) + { + writer.startRecord (T::sRecordId); + writer.writeHNString ("NAME", iter->second.mId); + iter->second.save (writer); + writer.endRecord (T::sRecordId); + } + } + + void read (ESM::ESMReader& reader) + { + T record; + record.mId = reader.getHNString ("NAME"); + record.load (reader); + insert (record); + } }; + template <> + inline void Store::clearDynamic() + { + std::map::iterator iter = mDynamic.begin(); + + while (iter!=mDynamic.end()) + if (iter->first=="player") + ++iter; + else + mDynamic.erase (iter++); + + mShared.clear(); + } + template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { std::string idLower = Misc::StringUtils::lowerCase(id); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6f5dbe23f..b00ad15ca 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1,6 +1,5 @@ #include "weather.hpp" -#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" @@ -195,7 +194,7 @@ void WeatherManager::setWeather(const String& weather, bool instant) } mNextWeather = weather; - mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600; + mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f; } mFirstUpdate = false; } @@ -324,7 +323,8 @@ void WeatherManager::update(float duration) mWeatherUpdateTime -= timePassed; - const bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior()); if (!exterior) { mRendering->sunDisable(false); @@ -334,32 +334,7 @@ void WeatherManager::update(float duration) return; } - // Exterior - std::string regionstr = Misc::StringUtils::lowerCase(MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion); - - if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) - { - mCurrentRegion = regionstr; - mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; - - std::string weatherType = "clear"; - - if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) - weatherType = mRegionOverrides[regionstr]; - else - { - // get weather probabilities for the current region - const ESM::Region *region = - MWBase::Environment::get().getWorld()->getStore().get().search (regionstr); - - if (region != 0) - { - weatherType = nextWeather(region); - } - } - - setWeather(weatherType, false); - } + switchToNextWeather(false); if (mNextWeather != "") { @@ -473,7 +448,7 @@ void WeatherManager::update(float duration) { // pick a random sound int sound = rand() % 4; - std::string* soundName; + std::string* soundName = NULL; if (sound == 0) soundName = &mThunderSoundID0; else if (sound == 1) soundName = &mThunderSoundID1; else if (sound == 2) soundName = &mThunderSoundID2; @@ -689,7 +664,7 @@ void WeatherManager::changeWeather(const std::string& region, const unsigned int mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather; - std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion; + std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell->mRegion; if (Misc::StringUtils::ciEqual(region, playerRegion)) setWeather(weather); } @@ -707,3 +682,44 @@ float WeatherManager::getWindSpeed() const { return mWindSpeed; } + +bool WeatherManager::isDark() const +{ + bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() + || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); + return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1); +} + +void WeatherManager::switchToNextWeather(bool instantly) +{ + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isCellExterior() || world->isCellQuasiExterior()) + { + std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->mCell->mRegion); + + if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) + { + mCurrentRegion = regionstr; + mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; + + std::string weatherType = "clear"; + + if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) + { + weatherType = mRegionOverrides[regionstr]; + } + else + { + // get weather probabilities for the current region + const ESM::Region *region = world->getStore().get().search (regionstr); + + if (region != 0) + { + weatherType = nextWeather(region); + } + } + + setWeather(weatherType, instantly); + } + } +} diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 80cbe0418..fa2d9bd8e 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -128,6 +128,7 @@ namespace MWWorld * @param ID of the weather setting to shift to */ void changeWeather(const std::string& region, const unsigned int id); + void switchToNextWeather(bool instantly = true); /** * Per-frame update @@ -152,6 +153,9 @@ namespace MWWorld void modRegion(const std::string ®ionid, const std::vector &chances); + /// @see World::isDark + bool isDark() const; + private: float mHour; int mDay, mMonth; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9635feaf3..4e240195a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,4 +1,11 @@ #include "worldimp.hpp" +#ifdef _WIN32 +#include +#elif defined HAVE_UNORDERED_MAP +#include +#else +#include +#endif #include @@ -7,6 +14,7 @@ #include #include #include +#include #include @@ -14,11 +22,13 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/scriptmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/levelledlist.hpp" + #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" @@ -30,35 +40,16 @@ #include "cellfunctors.hpp" #include "containerstore.hpp" #include "inventorystore.hpp" +#include "actionteleport.hpp" + +#include "contentloader.hpp" +#include "esmloader.hpp" +#include "omwloader.hpp" using namespace Ogre; namespace { -/* // NOTE this code is never instantiated (proper copy in localscripts.cpp), - // so this commented out to not produce syntactic errors - - template - void listCellScripts (const MWWorld::ESMStore& store, - MWWorld::CellRefList& cellRefList, MWWorld::LocalScripts& localScripts, - MWWorld::Ptr::CellStore *cell) - { - for (typename MWWorld::CellRefList::List::iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); ++iter) - { - if (!iter->mBase->mScript.empty() && iter->mData.getCount()) - { - if (const ESM::Script *script = store.get().find (iter->mBase->mScript)) - { - iter->mData.setLocals (*script); - - localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell)); - } - } - } - } -*/ template MWWorld::LiveCellRef *searchViaHandle (const std::string& handle, MWWorld::CellRefList& refList) @@ -80,7 +71,39 @@ namespace namespace MWWorld { - Ptr World::getPtrViaHandle (const std::string& handle, Ptr::CellStore& cell) + struct GameContentLoader : public ContentLoader + { + GameContentLoader(Loading::Listener& listener) + : ContentLoader(listener) + { + } + + bool addLoader(const std::string& extension, ContentLoader* loader) + { + return mLoaders.insert(std::make_pair(extension, loader)).second; + } + + void load(const boost::filesystem::path& filepath, int& index) + { + LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); + if (it != mLoaders.end()) + { + it->second->load(filepath, index); + } + else + { + std::string msg("Cannot load file: "); + msg += filepath.string(); + throw std::runtime_error(msg.c_str()); + } + } + + private: + typedef std::tr1::unordered_map LoadersContainer; + LoadersContainer mLoaders; + }; + + Ptr World::getPtrViaHandle (const std::string& handle, CellStore& cell) { if (MWWorld::LiveCellRef *ref = searchViaHandle (handle, cell.mActivators)) @@ -151,9 +174,9 @@ namespace MWWorld { if (mSky && (isCellExterior() || isCellQuasiExterior())) { - mRendering->skySetHour (mGlobalVariables->getFloat ("gamehour")); - mRendering->skySetDate (mGlobalVariables->getInt ("day"), - mGlobalVariables->getInt ("month")); + mRendering->skySetHour (mGlobalVariables["gamehour"].getFloat()); + mRendering->skySetDate (mGlobalVariables["day"].getInteger(), + mGlobalVariables["month"].getInteger()); mRendering->skyEnable(); } @@ -163,14 +186,15 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::vector& master, const std::vector& plugins, + const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) - : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), + : mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (mActivationDistanceOverride), - mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), - mFacedDistance(FLT_MAX), mGodMode(false) + mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true), + mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), + mGoToJail(false) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -181,44 +205,22 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - int idx = 0; // NOTE: We might need to reserve one more for the running game / save. - mEsm.resize(master.size() + plugins.size()); + mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); - 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"; - listener->setLabel(masterPath.filename().string()); + GameContentLoader gameContentLoader(*listener); + EsmLoader esmLoader(mStore, mEsm, encoder, *listener); + OmwLoader omwLoader(*listener); - // 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], listener); - } - - for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) - { - boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); + gameContentLoader.addLoader(".esm", &esmLoader); + gameContentLoader.addLoader(".esp", &esmLoader); + gameContentLoader.addLoader(".omwgame", &omwLoader); + gameContentLoader.addLoader(".omwaddon", &omwLoader); - std::cout << "Loading ESP " << pluginPath.string() << "\n"; - listener->setLabel(pluginPath.filename().string()); + loadContentFiles(fileCollections, contentFiles, gameContentLoader); - // 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], listener); - } listener->loadingOff(); // insert records that may not be present in all versions of MW @@ -228,35 +230,25 @@ namespace MWWorld mStore.setUp(); mStore.movePlayerRecord(); - mGlobalVariables = new Globals (mStore); + mGlobalVariables.fill (mStore); mWorldScene = new Scene(*mRendering, mPhysics); } void World::startNewGame() { - mWorldScene->changeToVoid(); - - mStore.clearDynamic(); - mStore.setUp(); - - mCells.clear(); + mGoToJail = false; + mLevitationEnabled = true; + mTeleportEnabled = true; // Rebuild player setupPlayer(); - MWWorld::Ptr player = mPlayer->getPlayer(); - - // removes NpcStats, ContainerStore etc - player.getRefData().setCustomData(NULL); renderPlayer(); - mRendering->resetCamera(); - - // make sure to do this so that local scripts from items that were in the players inventory are removed - mLocalScripts.clear(); MWBase::Environment::get().getWindowManager()->updatePlayer(); + // FIXME: this will add cell 0,0 as visible on the global map ESM::Position pos; const int cellSize = 8192; pos.pos[0] = cellSize/2; @@ -267,29 +259,82 @@ namespace MWWorld pos.rot[2] = 0; mWorldScene->changeToExteriorCell(pos); - - // enable collision - if(!mPhysics->toggleCollisionMode()) - mPhysics->toggleCollisionMode(); - // FIXME: should be set to 1, but the sound manager won't pause newly started sounds mPlayIntro = 2; - // global variables - delete mGlobalVariables; - mGlobalVariables = new Globals (mStore); - // set new game mark - mGlobalVariables->setInt ("chargenstate", 1); - mGlobalVariables->setInt ("pcrace", 3); + mGlobalVariables["chargenstate"].setInteger (1); + mGlobalVariables["pcrace"].setInteger (3); // we don't want old weather to persist on a new game delete mWeatherManager; + mWeatherManager = 0; mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); + } + + void World::clear() + { + mLocalScripts.clear(); + mPlayer->clear(); + + // enable collision + if (!mPhysics->toggleCollisionMode()) + mPhysics->toggleCollisionMode(); + + mWorldScene->changeToVoid(); + + mStore.clearDynamic(); + mStore.setUp(); + + if (mPlayer) + { + mPlayer->setCell (0); + mPlayer->getPlayer().getRefData() = RefData(); + mPlayer->set (mStore.get().find ("player")); + } + + mCells.clear(); + + mProjectiles.clear(); + mDoorStates.clear(); - MWBase::Environment::get().getScriptManager()->resetGlobalScripts(); + mGodMode = false; + mSky = true; + mTeleportEnabled = true; + mPlayIntro = 0; + mFacedDistance = FLT_MAX; + + mGlobalVariables.fill (mStore); + } + + int World::countSavedGameRecords() const + { + return + mStore.countSavedGameRecords() + +mGlobalVariables.countSavedGameRecords() + +1 // player record + +mCells.countSavedGameRecords(); } + void World::write (ESM::ESMWriter& writer) const + { + mStore.write (writer); + mGlobalVariables.write (writer); + mCells.write (writer); + mPlayer->write (writer); + } + + void World::readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap) + { + if (!mStore.readRecord (reader, type) && + !mGlobalVariables.readRecord (reader, type) && + !mPlayer->readRecord (reader, type) && + !mCells.readRecord (reader, type, contentFileMap)) + { + throw std::runtime_error ("unknown record in saved game"); + } + } void World::ensureNeededRecords() { @@ -330,7 +375,6 @@ namespace MWWorld { delete mWeatherManager; delete mWorldScene; - delete mGlobalVariables; delete mRendering; delete mPhysics; @@ -364,16 +408,35 @@ namespace MWWorld return &mFallback; } - Ptr::CellStore *World::getExterior (int x, int y) + CellStore *World::getExterior (int x, int y) { return mCells.getExterior (x, y); } - Ptr::CellStore *World::getInterior (const std::string& name) + CellStore *World::getInterior (const std::string& name) { return mCells.getInterior (name); } + CellStore *World::getCell (const ESM::CellId& id) + { + if (id.mPaged) + return getExterior (id.mIndex.mX, id.mIndex.mY); + else + return getInterior (id.mWorldspace); + } + + void World::useDeathCamera() + { + if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() ) + { + mRendering->getCamera()->togglePreviewMode(false); + mRendering->getCamera()->toggleVanityMode(false); + } + if(mRendering->getCamera()->isFirstPerson()) + togglePOV(); + } + MWWorld::Player& World::getPlayer() { return *mPlayer; @@ -399,60 +462,57 @@ namespace MWWorld return mWorldScene->hasCellChanged(); } - Globals::Data& World::getGlobalVariable (const std::string& name) + void World::setGlobalInt (const std::string& name, int value) { - return (*mGlobalVariables)[name]; + if (name=="gamehour") + setHour (value); + else if (name=="day") + setDay (value); + else if (name=="month") + setMonth (value); + else + mGlobalVariables[name].setInteger (value); } - Globals::Data World::getGlobalVariable (const std::string& name) const + void World::setGlobalFloat (const std::string& name, float value) { - return (*mGlobalVariables)[name]; + if (name=="gamehour") + setHour (value); + else if (name=="day") + setDay (value); + else if (name=="month") + setMonth (value); + else + mGlobalVariables[name].setFloat (value); } - char World::getGlobalVariableType (const std::string& name) const + int World::getGlobalInt (const std::string& name) const { - return mGlobalVariables->getType (name); + return mGlobalVariables[name].getInteger(); } - std::vector World::getGlobals () const + float World::getGlobalFloat (const std::string& name) const { - return mGlobalVariables->getGlobals(); + return mGlobalVariables[name].getFloat(); } - std::string World::getCurrentCellName () const + char World::getGlobalVariableType (const std::string& name) const { - std::string name; + return mGlobalVariables.getType (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"); + std::string World::getCellName (const MWWorld::CellStore *cell) const + { + if (!cell) + cell = mWorldScene->getCurrentCell(); - if (setting && setting->mValue.getType()==ESM::VT_String) - name = setting->mValue.getString(); - } + if (!cell->mCell->isExterior() || !cell->mCell->mName.empty()) + return cell->mCell->mName; - } - } - else - { - name = cell->mCell->mName; - } + if (const ESM::Region* region = getStore().get().search (cell->mCell->mRegion)) + return region->mName; - return name; + return getStore().get().find ("sDefaultCellname")->mValue.getString(); } void World::removeRefScript (MWWorld::RefData *ref) @@ -460,8 +520,9 @@ namespace MWWorld mLocalScripts.remove (ref); } - Ptr World::getPtr (const std::string& name, bool activeOnly) + Ptr World::searchPtr (const std::string& name, bool activeOnly) { + Ptr ret; // the player is always in an active cell. if (name=="player") { @@ -474,12 +535,14 @@ namespace MWWorld if (!ptr.isEmpty()) return ptr; + std::string lowerCaseName = Misc::StringUtils::lowerCase(name); + // active cells for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); iter!=mWorldScene->getActiveCells().end(); ++iter) { - Ptr::CellStore* cellstore = *iter; - Ptr ptr = mCells.getPtr (name, *cellstore, true); + CellStore* cellstore = *iter; + Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, true); if (!ptr.isEmpty()) return ptr; @@ -487,12 +550,16 @@ namespace MWWorld if (!activeOnly) { - Ptr ptr = mCells.getPtr (name); - - if (!ptr.isEmpty()) - return ptr; + ret = mCells.getPtr (lowerCaseName); } + return ret; + } + Ptr World::getPtr (const std::string& name, bool activeOnly) + { + Ptr ret = searchPtr(name, activeOnly); + if (!ret.isEmpty()) + return ret; throw std::runtime_error ("unknown ID: " + name); } @@ -511,7 +578,7 @@ namespace MWWorld for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); iter!=mWorldScene->getActiveCells().end(); ++iter) { - Ptr::CellStore* cellstore = *iter; + CellStore* cellstore = *iter; Ptr ptr = getPtrViaHandle (handle, *cellstore); if (!ptr.isEmpty()) @@ -521,7 +588,7 @@ namespace MWWorld return MWWorld::Ptr(); } - void World::addContainerScripts(const Ptr& reference, Ptr::CellStore * cell) + void World::addContainerScripts(const Ptr& reference, CellStore * cell) { if( reference.getTypeName()==typeid (ESM::Container).name() || reference.getTypeName()==typeid (ESM::NPC).name() || @@ -584,16 +651,19 @@ namespace MWWorld void World::advanceTime (double hours) { + MWBase::Environment::get().getMechanicsManager()->advanceTime(hours*3600); + mWeatherManager->advanceTime (hours); - hours += mGlobalVariables->getFloat ("gamehour"); + hours += mGlobalVariables["gamehour"].getFloat(); setHour (hours); int days = hours / 24; if (days>0) - mGlobalVariables->setInt ("dayspassed", days + mGlobalVariables->getInt ("dayspassed")); + mGlobalVariables["dayspassed"].setInteger ( + days + mGlobalVariables["dayspassed"].getInteger()); } void World::setHour (double hour) @@ -605,14 +675,14 @@ namespace MWWorld hour = std::fmod (hour, 24); - mGlobalVariables->setFloat ("gamehour", hour); + mGlobalVariables["gamehour"].setFloat (hour); mRendering->skySetHour (hour); mWeatherManager->setHour (hour); if (days>0) - setDay (days + mGlobalVariables->getInt ("day")); + setDay (days + mGlobalVariables["day"].getInteger()); } void World::setDay (int day) @@ -620,7 +690,7 @@ namespace MWWorld if (day<1) day = 1; - int month = mGlobalVariables->getInt ("month"); + int month = mGlobalVariables["month"].getInteger(); while (true) { @@ -635,14 +705,14 @@ namespace MWWorld else { month = 0; - mGlobalVariables->setInt ("year", mGlobalVariables->getInt ("year")+1); + mGlobalVariables["year"].setInteger (mGlobalVariables["year"].getInteger()+1); } day -= days; } - mGlobalVariables->setInt ("day", day); - mGlobalVariables->setInt ("month", month); + mGlobalVariables["day"].setInteger (day); + mGlobalVariables["month"].setInteger (month); mRendering->skySetDate (day, month); @@ -659,31 +729,56 @@ namespace MWWorld int days = getDaysPerMonth (month); - if (mGlobalVariables->getInt ("day")>days) - mGlobalVariables->setInt ("day", days); + if (mGlobalVariables["day"].getInteger()>days) + mGlobalVariables["day"].setInteger (days); - mGlobalVariables->setInt ("month", month); + mGlobalVariables["month"].setInteger (month); if (years>0) - mGlobalVariables->setInt ("year", years+mGlobalVariables->getInt ("year")); + mGlobalVariables["year"].setInteger (years+mGlobalVariables["year"].getInteger()); - mRendering->skySetDate (mGlobalVariables->getInt ("day"), month); + mRendering->skySetDate (mGlobalVariables["day"].getInteger(), month); } - int World::getDay() + int World::getDay() const { - return mGlobalVariables->getInt("day"); + return mGlobalVariables["day"].getInteger(); } - int World::getMonth() + int World::getMonth() const { - return mGlobalVariables->getInt("month"); + return mGlobalVariables["month"].getInteger(); + } + + int World::getYear() const + { + return mGlobalVariables["year"].getInteger(); + } + + std::string World::getMonthName (int month) const + { + if (month==-1) + month = getMonth(); + + const int months = 12; + + if (month<0 || month>=months) + return ""; + + static const char *monthNames[months] = + { + "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand", + "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed", + "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" + }; + + return getStore().get().find (monthNames[month])->mValue.getString(); } TimeStamp World::getTimeStamp() const { - return TimeStamp (mGlobalVariables->getFloat ("gamehour"), - mGlobalVariables->getInt ("dayspassed")); + return TimeStamp (mGlobalVariables["gamehour"].getFloat(), + mGlobalVariables["dayspassed"].getInteger()); } bool World::toggleSky() @@ -719,17 +814,29 @@ namespace MWWorld float World::getTimeScaleFactor() const { - return mGlobalVariables->getFloat ("timescale"); + return mGlobalVariables["timescale"].getFloat(); } void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { - return mWorldScene->changeToInteriorCell(cellName, position); + removeContainerScripts(getPlayerPtr()); + mWorldScene->changeToInteriorCell(cellName, position); + addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } void World::changeToExteriorCell (const ESM::Position& position) { - return mWorldScene->changeToExteriorCell(position); + removeContainerScripts(getPlayerPtr()); + mWorldScene->changeToExteriorCell(position); + addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); + } + + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position) + { + if (cellId.mPaged) + changeToExteriorCell (position); + else + changeToInteriorCell (cellId.mWorldspace, position); } void World::markCellAsUnchanged() @@ -763,30 +870,9 @@ namespace MWWorld MWWorld::Ptr World::getFacedObject() { - std::pair result; - - if (!mRendering->occlusionQuerySupported()) - result = mPhysics->getFacedHandle (getMaxActivationDistance ()); - else - result = std::make_pair (mFacedDistance, mFacedHandle); - - if (result.second.empty()) - return MWWorld::Ptr (); - - MWWorld::Ptr object = searchPtrViaHandle (result.second); - float ActivationDistance; - - if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) - ActivationDistance = getObjectActivationDistance ()*50; - else if (object.getTypeName ().find("NPC") != std::string::npos) - ActivationDistance = getNpcActivationDistance (); - else - ActivationDistance = getObjectActivationDistance (); - - if (result.first > ActivationDistance) - return MWWorld::Ptr (); - - return object; + if (mFacedHandle.empty()) + return MWWorld::Ptr(); + return searchPtrViaHandle(mFacedHandle); } std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) @@ -800,6 +886,8 @@ namespace MWWorld if(anim != NULL) { Ogre::Node *node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); if(node != NULL) pos += node->_getDerivedPosition(); } @@ -814,12 +902,13 @@ namespace MWWorld void World::deleteObject (const Ptr& ptr) { - if (ptr.getRefData().getCount()>0) + if (ptr.getRefData().getCount() > 0) { - ptr.getRefData().setCount (0); + ptr.getRefData().setCount(0); - if (mWorldScene->getActiveCells().find (ptr.getCell())!=mWorldScene->getActiveCells().end() && - ptr.getRefData().isEnabled()) + if (ptr.isInCell() + && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() + && ptr.getRefData().isEnabled()) { mWorldScene->removeObjectFromScene (ptr); mLocalScripts.remove (ptr); @@ -856,6 +945,7 @@ namespace MWWorld int cellY = newCell.mCell->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } + addContainerScripts (getPlayerPtr(), &newCell); } else { @@ -863,15 +953,16 @@ namespace MWWorld copyObjectToCell(ptr, newCell, pos); else if (!mWorldScene->isCellActive(newCell)) { - MWWorld::Class::get(ptr) - .copyToCell(ptr, newCell) - .getRefData() - .setBaseNode(0); - mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); removeContainerScripts (ptr); haveToMove = false; + + MWWorld::Ptr newPtr = MWWorld::Class::get(ptr) + .copyToCell(ptr, newCell); + newPtr.getRefData().setBaseNode(0); + + objectLeftActiveCell(ptr, newPtr); } else { @@ -879,6 +970,7 @@ namespace MWWorld MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos); mRendering->updateObjectCell(ptr, copy); + MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy); MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); mechMgr->updateCell(ptr, copy); @@ -896,7 +988,7 @@ namespace MWWorld ptr.getRefData().setCount(0); } } - if (haveToMove) + if (haveToMove && ptr.getRefData().getBaseNode()) { mRendering->moveObject(ptr, vec); mPhysics->moveObject (ptr); @@ -1025,7 +1117,7 @@ namespace MWWorld void World::adjustPosition(const Ptr &ptr) { - Ogre::Vector3 pos (ptr.getRefData().getPosition().pos[0], ptr.getRefData().getPosition().pos[1], ptr.getRefData().getPosition().pos[2]); + Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); if(!ptr.getRefData().getBaseNode()) { @@ -1058,9 +1150,9 @@ namespace MWWorld adjust); } - void World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) + MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) { - copyObjectToCell(ptr,Cell,pos); + return copyObjectToCell(ptr,Cell,pos,false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1092,12 +1184,10 @@ namespace MWWorld void World::doPhysics(float duration) { - /* No duration? Shouldn't be any movement, then. */ - if(duration <= 0.0f) - return; - processDoors(duration); + moveProjectiles(duration); + const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); PtrVelocityList::const_iterator player(results.end()); for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++) @@ -1128,7 +1218,7 @@ namespace MWWorld std::map::iterator it = mDoorStates.begin(); while (it != mDoorStates.end()) { - if (!mWorldScene->isCellActive(*it->first.getCell())) + if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode()) mDoorStates.erase(it++); else { @@ -1203,7 +1293,7 @@ namespace MWWorld if (Misc::StringUtils::ciEqual (ids[i], record.mRace)) break; - mGlobalVariables->setInt ("pcrace", (i == ids.size()) ? 0 : i+1); + mGlobalVariables["pcrace"].setInteger (i == ids.size() ? 0 : i+1); const ESM::NPC *player = mPlayer->getPlayer().get()->mBase; @@ -1254,15 +1344,25 @@ namespace MWWorld mRendering->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); } - mWeatherManager->update (duration); + if (mGoToJail && !paused) + goToJail(); + + updateWeather(duration); mWorldScene->update (duration, paused); - doPhysics (duration); + if (!paused) + doPhysics (duration); performUpdateSceneQueries (); updateWindowManager (); + + if (!paused && mPlayer->getPlayer().getCell()->isExterior()) + { + ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition(); + mPlayer->setLastKnownExteriorPosition(Ogre::Vector3(pos.pos)); + } } void World::updateWindowManager () @@ -1303,6 +1403,14 @@ namespace MWWorld void World::updateFacedHandle () { + float telekinesisRangeBonus = + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() + .get(ESM::MagicEffect::Telekinesis).mMagnitude; + telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + + float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; + activationDistance += mRendering->getCameraDistance(); + // send new query // figure out which object we want to test against std::vector < std::pair < float, std::string > > results; @@ -1310,13 +1418,13 @@ namespace MWWorld { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()); + results = mPhysics->getFacedHandles(x, y, activationDistance); if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50); } else { - results = mPhysics->getFacedHandles(getMaxActivationDistance ()); + results = mPhysics->getFacedHandles(activationDistance); } // ignore the player and other things we're not interested in @@ -1346,7 +1454,7 @@ namespace MWWorld bool World::isCellExterior() const { - Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); + CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { return currentCell->mCell->isExterior(); @@ -1356,7 +1464,7 @@ namespace MWWorld bool World::isCellQuasiExterior() const { - Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); + CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { if (!(currentCell->mCell->mData.mFlags & ESM::Cell::QuasiEx)) @@ -1399,10 +1507,8 @@ namespace MWWorld return d; } - std::vector World::getDoorMarkers (CellStore* cell) + void World::getDoorMarkers (CellStore* cell, std::vector& out) { - std::vector result; - MWWorld::CellRefList& doors = cell->mDoors; CellRefList::List& refList = doors.mList; for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) @@ -1418,11 +1524,9 @@ namespace MWWorld newMarker.x = pos.pos[0]; newMarker.y = pos.pos[1]; - result.push_back(newMarker); + out.push_back(newMarker); } } - - return result; } void World::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) @@ -1454,7 +1558,7 @@ namespace MWWorld item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } - bool World::placeObject (const Ptr& object, float cursorX, float cursorY) + bool World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) { std::pair result = mPhysics->castRay(cursorX, cursorY); @@ -1469,9 +1573,9 @@ namespace MWWorld cell = mCells.getExterior(cellX, cellY); } else - cell = getPlayer().getPlayer().getCell(); + cell = getPlayerPtr().getCell(); - ESM::Position pos = getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position pos = getPlayerPtr().getRefData().getPosition(); pos.pos[0] = result.second[0]; pos.pos[1] = result.second[1]; pos.pos[2] = result.second[2]; @@ -1479,9 +1583,14 @@ namespace MWWorld pos.rot[0] = 0; pos.rot[1] = 0; - Ptr dropped = copyObjectToCell(object, *cell, pos); + // copy the object and set its count + int origCount = object.getRefData().getCount(); + object.getRefData().setCount(amount); + Ptr dropped = copyObjectToCell(object, *cell, pos, true); + object.getRefData().setCount(origCount); + + // only the player place items in the world, so no need to check actor PCDropped(dropped); - object.getRefData().setCount(0); return true; } @@ -1498,20 +1607,28 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos) + Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, ESM::Position pos, bool adjustPos) { - /// \todo add searching correct cell for position specified - MWWorld::Ptr dropped = - MWWorld::Class::get(object).copyToCell(object, cell, pos); + if (object.getClass().isActor() || adjustPos) + { + Ogre::Vector3 min, max; + if (mPhysics->getObjectAABB(object, min, max)) { + pos.pos[0] -= (min.x + max.x) / 2; + pos.pos[1] -= (min.y + max.y) / 2; + pos.pos[2] -= min.z; + } + } - Ogre::Vector3 min, max; - if (mPhysics->getObjectAABB(object, min, max)) { - float *pos = dropped.getRefData().getPosition().pos; - pos[0] -= (min.x + max.x) / 2; - pos[1] -= (min.y + max.y) / 2; - pos[2] -= min.z; + if (cell.isExterior()) + { + int cellX, cellY; + positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY); + cell = *mCells.getExterior(cellX, cellY); } + MWWorld::Ptr dropped = + MWWorld::Class::get(object).copyToCell(object, cell, pos); + if (mWorldScene->isCellActive(cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); @@ -1526,9 +1643,9 @@ namespace MWWorld return dropped; } - void World::dropObjectOnGround (const Ptr& actor, const Ptr& object) + void World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) { - MWWorld::Ptr::CellStore* cell = actor.getCell(); + MWWorld::CellStore* cell = actor.getCell(); ESM::Position pos = actor.getRefData().getPosition(); @@ -1537,7 +1654,7 @@ namespace MWWorld pos.rot[1] = 0; Ogre::Vector3 orig = - Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + Ogre::Vector3(pos.pos); Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; @@ -1547,10 +1664,14 @@ namespace MWWorld mPhysics->castRay(orig, dir, len); pos.pos[2] = hit.second.z; + // copy the object and set its count + int origCount = object.getRefData().getCount(); + object.getRefData().setCount(amount); Ptr dropped = copyObjectToCell(object, *cell, pos); + object.getRefData().setCount(origCount); + if(actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); - object.getRefData().setCount(0); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) @@ -1569,11 +1690,16 @@ namespace MWWorld if(!ptr.getClass().isActor()) return false; - const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0) + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return false; + + if (ptr.getClass().isFlying(ptr)) return true; - // TODO: Check if flying creature + const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0 + && isLevitationEnabled()) + return true; const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(ptr.getRefData().getHandle()); if(!actor || !actor->getCollisionMode()) @@ -1589,7 +1715,7 @@ namespace MWWorld return false; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::SlowFall)).mMagnitude > 0) + if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).mMagnitude > 0) return true; return false; @@ -1621,7 +1747,7 @@ namespace MWWorld } bool - World::isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const + World::isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const { if (!(cell->mCell->mData.mFlags & ESM::Cell::HasWater)) { return false; @@ -1641,9 +1767,9 @@ namespace MWWorld return mRendering->vanityRotateCamera(rot); } - void World::setCameraDistance(float dist, bool adjust, bool override) + void World::setCameraDistance(float dist, bool adjust, bool override_) { - return mRendering->setCameraDistance(dist, adjust, override);; + return mRendering->setCameraDistance(dist, adjust, override_); } void World::setupPlayer() @@ -1661,17 +1787,19 @@ namespace MWWorld void World::renderPlayer() { mRendering->renderPlayer(mPlayer->getPlayer()); - mPhysics->addActor(mPlayer->getPlayer()); - } - void World::setupExternalRendering (MWRender::ExternalRendering& rendering) - { - mRendering->setupExternalRendering (rendering); + // At this point the Animation object is live, and the CharacterController associated with it must be created. + // It has to be done at this point: resetCamera below does animation->setViewMode -> CharacterController::forceStateUpdate + // so we should make sure not to use a "stale" controller for that. + MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer()); + + mPhysics->addActor(mPlayer->getPlayer()); + mRendering->resetCamera(); } int World::canRest () { - Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); + CellStore *currentCell = mWorldScene->getCurrentCell(); Ptr player = mPlayer->getPlayer(); RefData &refdata = player.getRefData(); @@ -1707,6 +1835,11 @@ namespace MWWorld mRendering->frameStarted(dt, paused); } + void World::screenshot(Ogre::Image &image, int w, int h) + { + mRendering->screenshot(image, w, h); + } + void World::activateDoor(const MWWorld::Ptr& door) { if (mDoorStates.find(door) != mDoorStates.end()) @@ -1774,9 +1907,9 @@ namespace MWWorld { std::vector mHandles; - bool operator() (ESM::CellRef& ref, RefData& data) + bool operator() (Ptr ptr) { - Ogre::SceneNode* handle = data.getBaseNode(); + Ogre::SceneNode* handle = ptr.getRefData().getBaseNode(); if (handle) mHandles.push_back(handle->getName()); return true; @@ -1797,6 +1930,21 @@ namespace MWWorld } } + bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) + { + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); + float* pos1 = npc.getRefData().getPosition().pos; + Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); + float* pos2 = targetNpc.getRefData().getPosition().pos; + + btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z); + btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z); + + std::pair result = mPhysEngine->rayTest(from, to,false); + if(result.first == "") return true; + return false; + } + void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); @@ -1863,17 +2011,8 @@ namespace MWWorld int y = ext->getGridY(); indexToPosition(x, y, pos.pos[0], pos.pos[1], true); - ESM::Land* land = getStore().get().search(x, y); - if (land) { - if (!land->isDataLoaded(ESM::Land::DATA_VHGT)) { - land->loadData(ESM::Land::DATA_VHGT); - } - pos.pos[2] = land->mLandData->mHeights[ESM::Land::LAND_NUM_VERTS / 2 + 1]; - } - else { - std::cerr << "Land data for cell at (" << x << ", " << y << ") not found\n"; - pos.pos[2] = 0; - } + // Note: Z pos will be adjusted by adjustPosition later + pos.pos[2] = 0; return true; } @@ -1910,38 +2049,28 @@ namespace MWWorld npcStats.setWerewolf(werewolf); + // This is a bit dangerous. Equipped items other than WerewolfRobe may reference + // bones that do not even exist with the werewolf object root. + // Therefore, make sure to unequip everything at once, and only fire the change event + // (which will rebuild the animation parts) afterwards. unequipAll will do this for us. MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); invStore.unequipAll(actor); if(werewolf) { - ManualRef ref(getStore(), "WerewolfRobe"); - ref.getPtr().getRefData().setCount(1); + InventoryStore &inv = actor.getClass().getInventoryStore(actor); - // Configure item's script variables - std::string script = Class::get(ref.getPtr()).getScript(ref.getPtr()); - if(script != "") - { - const ESM::Script *esmscript = getStore().get().find(script); - ref.getPtr().getRefData().setLocals(*esmscript); - } - - // Not sure this is right - InventoryStore &inv = Class::get(actor).getInventoryStore(actor); - inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), actor)); + inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor); } else { - ContainerStore &store = Class::get(actor).getContainerStore(actor); - - const std::string item = "WerewolfRobe"; - for(ContainerStoreIterator iter(store.begin());iter != store.end();++iter) - { - if(Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) - iter->getRefData().setCount(0); - } + actor.getClass().getContainerStore(actor).remove("werewolfrobe", 1, actor); } + // NpcAnimation::updateParts will already rebuild the animation when it detects change of Npc type. + // the following is just for reattaching the camera properly. + mRendering->rebuildPtr(actor); + if(actor.getRefData().getHandle() == "player") { // Update the GUI only when called on the player @@ -1959,8 +2088,6 @@ namespace MWWorld windowManager->unsetForceHide(MWGui::GW_Magic); } } - - mRendering->rebuildPtr(actor); } void World::applyWerewolfAcrobatics(const Ptr &actor) @@ -1968,7 +2095,7 @@ namespace MWWorld const Store &gmst = getStore().get(); MWMechanics::NpcStats &stats = Class::get(actor).getNpcStats(actor); - stats.getSkill(ESM::Skill::Acrobatics).setModified(gmst.find("fWerewolfAcrobatics")->getFloat(), 0); + stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getFloat()); } bool World::getGodModeState() @@ -1983,4 +2110,694 @@ namespace MWWorld return mGodMode; } + void World::loadContentFiles(const Files::Collections& fileCollections, + const std::vector& content, ContentLoader& contentLoader) + { + std::vector::const_iterator it(content.begin()); + std::vector::const_iterator end(content.end()); + for (int idx = 0; it != end; ++it, ++idx) + { + boost::filesystem::path filename(*it); + const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); + if (col.doesExist(*it)) + { + contentLoader.load(col.getPath(*it), idx); + } + } + } + + bool World::startSpellCast(const Ptr &actor) + { + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + std::string message; + bool fail = false; + bool isPlayer = (actor == getPlayerPtr()); + + std::string selectedSpell = stats.getSpells().getSelectedSpell(); + + if (!selectedSpell.empty()) + { + const ESM::Spell* spell = getStore().get().search(selectedSpell); + + // Check mana + MWMechanics::DynamicStat magicka = stats.getMagicka(); + if (magicka.getCurrent() < spell->mData.mCost) + { + message = "#{sMagicInsufficientSP}"; + fail = true; + } + + // If this is a power, check if it was already used in the last 24h + if (!fail && spell->mData.mType == ESM::Spell::ST_Power) + { + if (stats.canUsePower(spell->mId)) + stats.usePower(spell->mId); + else + { + message = "#{sPowerAlreadyUsed}"; + fail = true; + } + } + + // Reduce mana + if (!fail) + { + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + } + } + + if (isPlayer && fail) + MWBase::Environment::get().getWindowManager()->messageBox(message); + + return !fail; + } + + void World::castSpell(const Ptr &actor) + { + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + MWWorld::Ptr target = getFacedObject(); + + std::string selectedSpell = stats.getSpells().getSelectedSpell(); + + MWMechanics::CastSpell cast(actor, target); + if (!target.isEmpty()) + cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos); + + if (!selectedSpell.empty()) + { + const ESM::Spell* spell = getStore().get().search(selectedSpell); + + cast.cast(spell); + } + else if (actor.getClass().hasInventoryStore(actor)) + { + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + if (inv.getSelectedEnchantItem() != inv.end()) + cast.cast(*inv.getSelectedEnchantItem()); + } + } + + void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName) + { + std::string projectileModel; + std::string sound; + float speed = 0; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != ESM::RT_Target) + continue; + + const ESM::MagicEffect *magicEffect = getStore().get().find ( + iter->mEffectID); + + projectileModel = magicEffect->mBolt; + if (projectileModel.empty()) + projectileModel = "VFX_DefaultBolt"; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + if (!magicEffect->mBoltSound.empty()) + sound = magicEffect->mBoltSound; + else + sound = schools[magicEffect->mData.mSchool] + " bolt"; + + speed = magicEffect->mData.mSpeed; + break; + } + if (projectileModel.empty()) + return; + + // Spawn at 0.75 * ActorHeight + float height = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; + + MWWorld::ManualRef ref(getStore(), projectileModel); + ESM::Position pos; + pos.pos[0] = actor.getRefData().getPosition().pos[0]; + pos.pos[1] = actor.getRefData().getPosition().pos[1]; + pos.pos[2] = actor.getRefData().getPosition().pos[2] + height; + pos.rot[0] = actor.getRefData().getPosition().rot[0]; + pos.rot[1] = actor.getRefData().getPosition().rot[1]; + pos.rot[2] = actor.getRefData().getPosition().rot[2]; + ref.getPtr().getCellRef().mPos = pos; + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos); + + ProjectileState state; + state.mSourceName = sourceName; + state.mId = id; + state.mActorHandle = actor.getRefData().getHandle(); + state.mSpeed = speed; + state.mStack = stack; + + // Only interested in "on target" effects + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange == ESM::RT_Target) + state.mEffects.mList.push_back(*iter); + } + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + + mProjectiles[ptr] = state; + } + + void World::moveProjectiles(float duration) + { + std::map moved; + for (std::map::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + { + if (!mWorldScene->isCellActive(*it->first.getCell())) + { + deleteObject(it->first); + mProjectiles.erase(it++); + continue; + } + + MWWorld::Ptr ptr = it->first; + + Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + + // TODO: Why -rot.z, but not -rot.x? (note: same issue in MovementSolver::move) + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); + orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X); + + + static float fTargetSpellMaxSpeed = getStore().get().find("fTargetSpellMaxSpeed")->getFloat(); + float speed = fTargetSpellMaxSpeed * it->second.mSpeed; + + Ogre::Vector3 direction = orient.yAxis(); + direction.normalise(); + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + Ogre::Vector3 newPos = pos + direction * duration * speed; + + // Check for impact + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine->rayTest2(from, to); + bool explode = false; + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !explode; ++cIt) + { + MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); + if (obstacle == ptr) + continue; + + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + if (caster.isEmpty()) + caster = obstacle; + + if (obstacle.isEmpty()) + { + // Terrain + } + else + { + MWMechanics::CastSpell cast(caster, obstacle); + cast.mHitPosition = pos; + cast.mId = it->second.mId; + cast.mSourceName = it->second.mSourceName; + cast.mStack = it->second.mStack; + cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true); + } + + explode = true; + } + + if (explode) + { + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName); + + deleteObject(ptr); + mProjectiles.erase(it++); + continue; + } + + std::string handle = ptr.getRefData().getHandle(); + + moveObject(ptr, newPos.x, newPos.y, newPos.z); + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + if (!ptr.getRefData().getCount()) + { + moved[handle] = it->second; + mProjectiles.erase(it++); + } + else + ++it; + } + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) + { + MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); + if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted + continue; + mProjectiles[getPtrViaHandle(it->first)] = it->second; + } + } + + void World::objectLeftActiveCell(Ptr object, Ptr movedPtr) + { + // For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information + if (mProjectiles.find(object) != mProjectiles.end()) + { + deleteObject(movedPtr); + } + } + + const std::vector& World::getContentFiles() const + { + return mContentFiles; + } + + void World::breakInvisibility(const Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); + if (actor.getClass().hasInventoryStore(actor)) + actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + } + + bool World::isDark() const + { + return mWeatherManager->isDark(); + } + + bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) + { + if (cell->isExterior()) + return false; + MWWorld::CellRefList& doors = cell->mDoors; + CellRefList::List& refList = doors.mList; + + // Check if any door in the cell leads to an exterior directly + for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) + { + MWWorld::LiveCellRef& ref = *it; + if (ref.mRef.mTeleport && ref.mRef.mDestCell.empty()) + { + ESM::Position pos = ref.mRef.mDoorDest; + result = Ogre::Vector3(pos.pos); + return true; + } + } + + // No luck :( + return false; + } + + void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id) + { + Ogre::Vector3 worldPos; + if (!findInteriorPositionInWorldSpace(ptr.getCell(), worldPos)) + worldPos = mPlayer->getLastKnownExteriorPosition(); + + MWWorld::Ptr closestMarker; + float closestDistance = FLT_MAX; + + std::vector markers; + mCells.getExteriorPtrs(id, markers); + + for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) + { + ESM::Position pos = it->getRefData().getPosition(); + Ogre::Vector3 markerPos = Ogre::Vector3(pos.pos); + float distance = worldPos.squaredDistance(markerPos); + if (distance < closestDistance) + { + closestDistance = distance; + closestMarker = *it; + } + + } + + MWWorld::ActionTeleport action("", closestMarker.getRefData().getPosition()); + action.execute(ptr); + } + + void World::updateWeather(float duration) + { + if (mPlayer->wasTeleported()) + { + mPlayer->setTeleported(false); + mWeatherManager->switchToNextWeather(true); + } + + mWeatherManager->update(duration); + } + + struct AddDetectedReference + { + AddDetectedReference(std::vector& out, Ptr detector, World::DetectionType type, float squaredDist) + : mOut(out), mDetector(detector), mType(type), mSquaredDist(squaredDist) + { + } + + std::vector& mOut; + Ptr mDetector; + float mSquaredDist; + World::DetectionType mType; + bool operator() (MWWorld::Ptr ptr) + { + if (Ogre::Vector3(ptr.getRefData().getPosition().pos).squaredDistance( + Ogre::Vector3(mDetector.getRefData().getPosition().pos)) >= mSquaredDist) + return true; + + if (!ptr.getRefData().isEnabled()) + return true; + + // Consider references inside containers as well + if (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name()) + { + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + { + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (needToAdd(*it)) + { + mOut.push_back(ptr); + return true; + } + } + } + } + + if (needToAdd(ptr)) + mOut.push_back(ptr); + + return true; + } + + bool needToAdd (MWWorld::Ptr ptr) + { + if (mType == World::Detect_Creature && ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) + return false; + if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr)) + return false; + if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty()) + return false; + return true; + } + }; + + void World::listDetectedReferences(const Ptr &ptr, std::vector &out, DetectionType type) + { + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + float dist=0; + if (type == World::Detect_Creature) + dist = effects.get(ESM::MagicEffect::DetectAnimal).mMagnitude; + else if (type == World::Detect_Key) + dist = effects.get(ESM::MagicEffect::DetectKey).mMagnitude; + else if (type == World::Detect_Enchantment) + dist = effects.get(ESM::MagicEffect::DetectEnchantment).mMagnitude; + + if (!dist) + return; + + dist = feetToGameUnits(dist); + + AddDetectedReference functor (out, ptr, type, dist*dist); + + const Scene::CellStoreCollection& active = mWorldScene->getActiveCells(); + for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it) + { + MWWorld::CellStore* cellStore = *it; + cellStore->forEach(functor); + } + } + + float World::feetToGameUnits(float feet) + { + // Looks like there is no GMST for this. This factor was determined in experiments + // with the Telekinesis effect. + return feet * 22; + } + + MWWorld::Ptr World::getPlayerPtr() + { + return mPlayer->getPlayer(); + } + + void World::updateDialogueGlobals() + { + MWWorld::Ptr player = getPlayerPtr(); + int bounty = player.getClass().getNpcStats(player).getBounty(); + int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId); + + float fCrimeGoldDiscountMult = getStore().get().find("fCrimeGoldDiscountMult")->getFloat(); + float fCrimeGoldTurnInMult = getStore().get().find("fCrimeGoldTurnInMult")->getFloat(); + + int discount = bounty*fCrimeGoldDiscountMult; + int turnIn = bounty * fCrimeGoldTurnInMult; + + mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0); + + mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0); + mGlobalVariables["crimegolddiscount"].setInteger(discount); + + mGlobalVariables["crimegoldturnin"].setInteger(turnIn); + mGlobalVariables["pchasturnin"].setInteger((turnIn <= playerGold) ? 1 : 0); + } + + void World::confiscateStolenItems(const Ptr &ptr) + { + Ogre::Vector3 playerPos; + if (!findInteriorPositionInWorldSpace(ptr.getCell(), playerPos)) + playerPos = mPlayer->getLastKnownExteriorPosition(); + + MWWorld::Ptr closestChest; + float closestDistance = FLT_MAX; + + std::vector chests; + mCells.getInteriorPtrs("stolen_goods", chests); + + Ogre::Vector3 chestPos; + for (std::vector::iterator it = chests.begin(); it != chests.end(); ++it) + { + if (!findInteriorPositionInWorldSpace(it->getCell(), chestPos)) + continue; + + float distance = playerPos.squaredDistance(chestPos); + if (distance < closestDistance) + { + closestDistance = distance; + closestChest = *it; + } + } + + if (!closestChest.isEmpty()) + { + ContainerStore& store = ptr.getClass().getContainerStore(ptr); + for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (!it->getCellRef().mOwner.empty() && it->getCellRef().mOwner != "player") + { + closestChest.getClass().getContainerStore(closestChest).add(*it, it->getRefData().getCount(), closestChest); + store.remove(*it, it->getRefData().getCount(), ptr); + } + } + } + } + + void World::goToJail() + { + if (!mGoToJail) + { + // Save for next update, since the player should be able to read the dialog text first + mGoToJail = true; + return; + } + else + { + mGoToJail = false; + + MWWorld::Ptr player = getPlayerPtr(); + teleportToClosestMarker(player, "prisonmarker"); + int bounty = player.getClass().getNpcStats(player).getBounty(); + player.getClass().getNpcStats(player).setBounty(0); + confiscateStolenItems(player); + + int iDaysinPrisonMod = getStore().get().find("iDaysinPrisonMod")->getInt(); + int days = std::max(1, bounty / iDaysinPrisonMod); + + advanceTime(days * 24); + + std::set skills; + for (int day=0; day (RAND_MAX) + 1) * ESM::Skill::Length; + skills.insert(skill); + + MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill); + if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak) + value.setBase(std::min(100, value.getBase()+1)); + else + value.setBase(value.getBase()-1); + } + + const Store& gmst = getStore().get(); + + std::string message; + if (days == 1) + message = gmst.find("sNotifyMessage42")->getString(); + else + message = gmst.find("sNotifyMessage43")->getString(); + + std::stringstream dayStr; + dayStr << days; + if (message.find("%d") != std::string::npos) + message.replace(message.find("%d"), 2, dayStr.str()); + + for (std::set::iterator it = skills.begin(); it != skills.end(); ++it) + { + std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[*it])->getString(); + std::stringstream skillValue; + skillValue << player.getClass().getNpcStats(player).getSkill(*it).getBase(); + std::string skillMsg = gmst.find("sNotifyMessage44")->getString(); + if (*it == ESM::Skill::Sneak || *it == ESM::Skill::Security) + skillMsg = gmst.find("sNotifyMessage39")->getString(); + + if (skillMsg.find("%s") != std::string::npos) + skillMsg.replace(skillMsg.find("%s"), 2, skillName); + if (skillMsg.find("%d") != std::string::npos) + skillMsg.replace(skillMsg.find("%d"), 2, skillValue.str()); + message += "\n" + skillMsg; + } + + std::vector buttons; + buttons.push_back("#{sOk}"); + MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); + } + } + + void World::spawnRandomCreature(const std::string &creatureList) + { + const ESM::CreatureLevList* list = getStore().get().find(creatureList); + + int iNumberCreatures = getStore().get().find("iNumberCreatures")->getInt(); + int numCreatures = 1 + std::rand()/ (static_cast (RAND_MAX) + 1) * iNumberCreatures; // [1, iNumberCreatures] + + for (int i=0; igetPlayer().getRefData().getPosition(); + Ogre::Vector3 pos(ipos.pos); + Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + const float distance = 50; + pos = pos + distance*rot.yAxis(); + ipos.pos[0] = pos.x; + ipos.pos[1] = pos.y; + ipos.pos[2] = pos.z; + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + + MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); + MWWorld::ManualRef ref(getStore(), selectedCreature, 1); + ref.getPtr().getCellRef().mPos = ipos; + + safePlaceObject(ref.getPtr(),*cell,ipos); + } + } + + void World::spawnBloodEffect(const Ptr &ptr, const Vector3 &worldPosition) + { + int type = ptr.getClass().getBloodTexture(ptr); + std::string texture; + switch (type) + { + case 2: + texture = getFallback()->getFallbackString("Blood_Texture_2"); + break; + case 1: + texture = getFallback()->getFallbackString("Blood_Texture_1"); + break; + case 0: + default: + texture = getFallback()->getFallbackString("Blood_Texture_0"); + break; + } + + std::stringstream modelName; + modelName << "Blood_Model_"; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 3; // [0, 2] + modelName << roll; + std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str()); + + mRendering->spawnEffect(model, texture, worldPosition); + } + + void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster, + const std::string& id, const std::string& sourceName) + { + std::map > toApply; + for (std::vector::const_iterator effectIt = effects.mList.begin(); + effectIt != effects.mList.end(); ++effectIt) + { + const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); + + if (effectIt->mArea <= 0) + continue; // Not an area effect + + // Spawn the explosion orb effect + const ESM::Static* areaStatic; + if (!effect->mCasting.empty()) + areaStatic = getStore().get().find (effect->mArea); + else + areaStatic = getStore().get().find ("VFX_DefaultArea"); + + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, effectIt->mArea); + + // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mAreaSound.empty()) + sndMgr->playSound3D(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + else + sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + + // Get the actors in range of the effect + std::vector objects; + MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( + origin, feetToGameUnits(effectIt->mArea), objects); + + for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) + toApply[*affected].push_back(*effectIt); + } + + // Now apply the appropriate effects to each actor in range + for (std::map >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply) + { + MWWorld::Ptr source = caster; + // Vanilla-compatible behaviour of never applying the spell to the caster + // (could be changed by mods later) + if (apply->first == caster) + continue; + + if (source.isEmpty()) + source = apply->first; + + MWMechanics::CastSpell cast(source, apply->first); + cast.mHitPosition = origin; + cast.mId = id; + cast.mSourceName = sourceName; + cast.mStack = false; + ESM::EffectList effects; + effects.mList = apply->second; + cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true); + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index e275756e8..036cafe2d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -11,9 +11,12 @@ #include "localscripts.hpp" #include "timestamp.hpp" #include "fallback.hpp" +#include "globals.hpp" #include "../mwbase/world.hpp" +#include "contentloader.hpp" + namespace Ogre { class Vector3; @@ -39,8 +42,11 @@ namespace MWRender class SkyManager; class CellRender; class Animation; + class Camera; } +struct ContentLoader; + namespace MWWorld { class WeatherManager; @@ -60,7 +66,7 @@ namespace MWWorld std::vector mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; - MWWorld::Globals *mGlobalVariables; + MWWorld::Globals mGlobalVariables; MWWorld::PhysicsSystem *mPhysics; bool mSky; @@ -69,12 +75,13 @@ namespace MWWorld OEngine::Physic::PhysicEngine* mPhysEngine; bool mGodMode; + std::vector mContentFiles; // not implemented World (const World&); World& operator= (const World&); - Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + Ptr getPtrViaHandle (const std::string& handle, CellStore& cellStore); int mActivationDistanceOverride; std::string mFacedHandle; @@ -83,6 +90,26 @@ namespace MWWorld std::map mDoorStates; ///< only holds doors that are currently moving. 0 means closing, 1 opening + struct ProjectileState + { + // Id of spell or enchantment to apply when it hits + std::string mId; + + // Actor who casted this projectile + std::string mActorHandle; + + // Name of item to display as effect source in magic menu (in case we casted an enchantment) + std::string mSourceName; + + ESM::EffectList mEffects; + + float mSpeed; + + bool mStack; + }; + + std::map mProjectiles; + void updateWeather(float duration); int getDaysPerMonth (int month) const; void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); @@ -90,8 +117,7 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed - - Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); + Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, ESM::Position pos, bool adjustPos=true); void updateWindowManager (); void performUpdateSceneQueries (); @@ -102,27 +128,44 @@ namespace MWWorld float getObjectActivationDistance (); void removeContainerScripts(const Ptr& reference); - void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell); + void addContainerScripts(const Ptr& reference, CellStore* cell); void PCDropped (const Ptr& item); void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. + void moveProjectiles(float duration); + void doPhysics(float duration); ///< Run physics simulation and modify \a world accordingly. void ensureNeededRecords(); + /** + * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) + * @param fileCollections- Container which holds content file names and their paths + * @param content - Container which holds content file names + * @param contentLoader - + */ + void loadContentFiles(const Files::Collections& fileCollections, + const std::vector& content, ContentLoader& contentLoader); + int mPlayIntro; bool mTeleportEnabled; bool mLevitationEnabled; + bool mGoToJail; + + /// Called when \a object is moved to an inactive cell + void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr); + + float feetToGameUnits(float feet); public: World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::vector& master, const std::vector& plugins, + const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride); @@ -130,13 +173,27 @@ namespace MWWorld virtual void startNewGame(); + virtual void clear(); + + virtual int countSavedGameRecords() const; + + virtual void write (ESM::ESMWriter& writer) const; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap); + virtual OEngine::Render::Fader* getFader(); - ///< \ŧodo remove this function. Rendering details should not be exposed. + ///< \todo remove this function. Rendering details should not be exposed. virtual CellStore *getExterior (int x, int y); virtual CellStore *getInterior (const std::string& name); + virtual CellStore *getCell (const ESM::CellId& id); + + //switch to POV before showing player's death animation + virtual void useDeathCamera(); + virtual void setWaterHeight(const float height); virtual void toggleWater(); @@ -148,6 +205,7 @@ namespace MWWorld virtual const Fallback *getFallback() const; virtual Player& getPlayer(); + virtual MWWorld::Ptr getPlayerPtr(); virtual const MWWorld::ESMStore& getStore() const; @@ -165,7 +223,7 @@ namespace MWWorld virtual Ogre::Vector2 getNorthVector (CellStore* cell); ///< get north vector (OGRE coordinates) for given interior cell - virtual std::vector getDoorMarkers (MWWorld::CellStore* cell); + virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); ///< get a list of teleport door markers for a given cell, to be displayed on the local map virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); @@ -174,16 +232,26 @@ namespace MWWorld virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior); ///< see MWRender::LocalMap::isPositionExplored - virtual Globals::Data& getGlobalVariable (const std::string& name); + virtual void setGlobalInt (const std::string& name, int value); + ///< Set value independently from real type. + + virtual void setGlobalFloat (const std::string& name, float value); + ///< Set value independently from real type. + + virtual int getGlobalInt (const std::string& name) const; + ///< Get value independently from real type. - virtual Globals::Data getGlobalVariable (const std::string& name) const; + virtual float getGlobalFloat (const std::string& name) const; + ///< Get value independently from real type. 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 std::string getCellName (const MWWorld::CellStore *cell = 0) const; + ///< Return name of the cell. + /// + /// \note If cell==0, the cell the player is currently in will be used instead to + /// generate a name. virtual void removeRefScript (MWWorld::RefData *ref); //< Remove the script attached to ref from mLocalScripts @@ -192,6 +260,10 @@ namespace MWWorld ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. + virtual Ptr searchPtr (const std::string& name, bool activeOnly); + ///< Return a pointer to a liveCellRef with the given name. + /// \param activeOnly do non search inactive cells. + virtual Ptr getPtrViaHandle (const std::string& handle); ///< Return a pointer to a liveCellRef with the given Ogre handle. @@ -217,8 +289,12 @@ namespace MWWorld virtual void setDay (int day); ///< Set in-game time day. - virtual int getDay(); - virtual int getMonth(); + virtual int getDay() const; + virtual int getMonth() const; + virtual int getYear() const; + + virtual std::string getMonthName (int month = -1) const; + ///< Return name of month (-1: current month) virtual TimeStamp getTimeStamp() const; ///< Return current in-game time stamp. @@ -247,6 +323,8 @@ namespace MWWorld virtual void changeToExteriorCell (const ESM::Position& position); ///< Move to exterior cell. + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position); + virtual const ESM::Cell *getExterior (const std::string& cellName) const; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -273,7 +351,7 @@ namespace MWWorld virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); + virtual MWWorld::Ptr 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. virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -341,14 +419,19 @@ namespace MWWorld virtual void update (float duration, bool paused); - virtual bool placeObject (const Ptr& object, float cursorX, float cursorY); - ///< place an object into the gameworld at the specified cursor position + virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); + ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) + /// @param number of objects to place /// @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& actor, const Ptr& object); + virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount); + ///< copy and place an object into the gameworld at the given actor's position + /// @param actor giving the dropped object position + /// @param object + /// @param number of objects to place virtual bool canPlaceObject(float cursorX, float cursorY); ///< @return true if it is possible to place on object at specified cursor location @@ -360,7 +443,7 @@ namespace MWWorld ///Is the head of the creature underwater? virtual bool isSubmerged(const MWWorld::Ptr &object) const; virtual bool isSwimming(const MWWorld::Ptr &object) const; - virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const; virtual bool isOnGround(const MWWorld::Ptr &ptr) const; virtual void togglePOV() { @@ -407,9 +490,10 @@ namespace MWWorld virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); ///< get all items in active cells owned by this Npc - virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); + virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); + ///< get Line of Sight (morrowind stupid implementation) - virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); virtual int canRest(); ///< check if the player is allowed to rest \n @@ -425,6 +509,7 @@ namespace MWWorld virtual void playVideo(const std::string& name, bool allowSkipping); virtual void stopVideo(); virtual void frameStarted (float dt, bool paused); + virtual void screenshot (Ogre::Image& image, int w, int h); /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise @@ -453,6 +538,60 @@ namespace MWWorld virtual bool getGodModeState(); virtual bool toggleGodMode(); + + /** + * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. + * @param actor + * @return true if the spell can be casted (i.e. the animation should start) + */ + virtual bool startSpellCast (const MWWorld::Ptr& actor); + + /** + * @brief Cast the actual spell, should be called mid-animation + * @param actor + */ + virtual void castSpell (const MWWorld::Ptr& actor); + + virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName); + + + virtual const std::vector& getContentFiles() const; + + virtual void breakInvisibility (const MWWorld::Ptr& actor); + // Are we in an exterior or pseudo-exterior cell and it's night? + virtual bool isDark() const; + + virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result); + + /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) + /// @note id must be lower case + virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id); + + /// List all references (filtered by \a type) detected by \a ptr. The range + /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. + /// @note This also works for references in containers. + virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, + DetectionType type); + + /// Update the value of some globals according to the world state, which may be used by dialogue entries. + /// This should be called when initiating a dialogue. + virtual void updateDialogueGlobals(); + + /// Moves all stolen items from \a ptr to the closest evidence chest. + virtual void confiscateStolenItems(const MWWorld::Ptr& ptr); + + virtual void goToJail (); + + /// Spawn a random creature from a levelled list next to the player + virtual void spawnRandomCreature(const std::string& creatureList); + + /// Spawn a blood effect for \a ptr at \a worldPosition + virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); + + virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); }; } diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 97feddffe..b75f3105a 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -60,8 +60,6 @@ _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) _FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) _FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d) # handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if @@ -69,12 +67,11 @@ _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletS include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Bullet DEFAULT_MSG BULLET_DYNAMICS_LIBRARY BULLET_COLLISION_LIBRARY BULLET_MATH_LIBRARY - BULLET_SOFTBODY_LIBRARY BULLET_INCLUDE_DIR) + BULLET_INCLUDE_DIR) set(BULLET_INCLUDE_DIRS ${BULLET_INCLUDE_DIR}) if(BULLET_FOUND) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY) - _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_SOFTBODY_LIBRARY) endif() diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index fb4a090c6..96f93cf34 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -551,4 +551,3 @@ set(OGRE_MEDIA_SEARCH_SUFFIX clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR) find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH} PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX}) - diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 000000000..fecd1654d --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,157 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + + #get_git_head_revision(refspec hash) + + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + + #if(NOT hash) + # set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + # return() + #endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + #${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(get_git_tag_revision _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + rev-list + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + + diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 000000000..888ce13aa --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,38 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 04423dc6f..e27955684 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,4 +1,8 @@ project (Components) +set (CMAKE_BUILD_TYPE DEBUG) + +# Version file +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp") # source files @@ -19,7 +23,7 @@ add_component_dir (nif ) add_component_dir (nifogre - ogrenifloader skeleton material mesh + ogrenifloader skeleton material mesh particles controller ) add_component_dir (nifbullet @@ -40,6 +44,7 @@ add_component_dir (esm loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpcc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter + savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate ) add_component_dir (misc @@ -47,8 +52,8 @@ add_component_dir (misc ) add_component_dir (files - linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary ogreplugin constrainedfiledatastream lowlevelfile + linuxpath windowspath macospath fixedpath multidircollection collections configurationmanager + constrainedfiledatastream lowlevelfile ) add_component_dir (compiler @@ -65,30 +70,43 @@ add_component_dir (interpreter add_component_dir (translation translation ) - + add_component_dir (terrain quadtreenode chunk world storage material ) - + add_component_dir (loadinglistener loadinglistener ) +add_component_dir (ogreinit + ogreinit ogreplugin + ) + +add_component_dir (version + version + ) + +set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui + ) + 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 + add_component_qt_dir (contentselector + model/modelitem model/esmfile + model/naturalsort model/contentmodel + view/combobox view/contentselector ) include(${QT_USE_FILE}) + QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) -include_directories(${BULLET_INCLUDE_DIRS}) +include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS}) +add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 8f07b9e50..eb741fb10 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -23,6 +23,8 @@ #include "bsa_archive.hpp" +#include + #include #include #include diff --git a/components/bsa/bsa_archive.hpp b/components/bsa/bsa_archive.hpp index 18f7377ff..7f9ebaae1 100644 --- a/components/bsa/bsa_archive.hpp +++ b/components/bsa/bsa_archive.hpp @@ -22,8 +22,6 @@ */ #include -#include -#include #include #ifndef BSA_BSA_ARCHIVE_H diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 53ebfa31b..34551374a 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -1,5 +1,5 @@ #ifndef COMPILER_EXTENSIONS_H_INCLUDED -#define COMPILER_EXTENSINOS_H_INCLUDED +#define COMPILER_EXTENSIONS_H_INCLUDED #include #include diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 65f6e112a..baec9987f 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -59,10 +59,17 @@ namespace Compiler extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); + extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction("startcombat", "c", opcodeStartCombat, opcodeStartCombatExplicit); + extensions.registerInstruction("stopcombat", "", opcodeStopCombat, opcodeStopCombatExplicit); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit); + extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); + extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); + extensions.registerFunction("gettarget", 'l', "c", opcodeGetTarget, opcodeGetTargetExplicit); } } @@ -194,7 +201,7 @@ namespace Compiler extensions.registerInstruction ("enablerest", "", opcodeEnableRest); extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableRest); - extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu); + extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu, opcodeShowRestMenuExplicit); extensions.registerFunction ("getbuttonpressed", 'l', "", opcodeGetButtonPressed); @@ -218,6 +225,8 @@ namespace Compiler extensions.registerInstruction ("activate", "", opcodeActivate); extensions.registerInstruction ("lock", "/l", opcodeLock, opcodeLockExplicit); extensions.registerInstruction ("unlock", "", opcodeUnlock, opcodeUnlockExplicit); + extensions.registerInstruction ("cast", "SS", opcodeCast, opcodeCastExplicit); + extensions.registerInstruction ("explodespell", "S", opcodeExplodeSpell, opcodeExplodeSpellExplicit); extensions.registerInstruction ("togglecollisionboxes", "", opcodeToggleCollisionBoxes); extensions.registerInstruction ("togglecollisiongrid", "", opcodeToggleCollisionDebug); extensions.registerInstruction ("tcb", "", opcodeToggleCollisionBoxes); @@ -235,8 +244,12 @@ namespace Compiler extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode); extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); + extensions.registerFunction ("getpcjumping", 'l', "", opcodeGetPcJumping); extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc); extensions.registerInstruction ("playbink", "Sl", opcodePlayBink); + extensions.registerInstruction ("payfine", "", opcodePayFine); + extensions.registerInstruction ("payfinethief", "", opcodePayFineThief); + extensions.registerInstruction ("gotojail", "", opcodeGoToJail); extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); extensions.registerFunction ("geteffect", 'l', "S", opcodeGetEffect, opcodeGetEffectExplicit); extensions.registerInstruction ("addsoulgem", "cc", opcodeAddSoulGem, opcodeAddSoulGemExplicit); @@ -245,6 +258,7 @@ namespace Compiler extensions.registerInstruction ("dropsoulgem", "c", opcodeDropSoulGem, opcodeDropSoulGemExplicit); extensions.registerFunction ("getattacked", 'l', "", opcodeGetAttacked, opcodeGetAttackedExplicit); extensions.registerFunction ("getweapondrawn", 'l', "", opcodeGetWeaponDrawn, opcodeGetWeaponDrawnExplicit); + extensions.registerFunction ("getspellreadied", 'l', "", opcodeGetSpellReadied, opcodeGetSpellReadiedExplicit); extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit); extensions.registerFunction ("getcurrenttime", 'f', "", opcodeGetCurrentTime); extensions.registerInstruction ("setdelete", "l", opcodeSetDelete, opcodeSetDeleteExplicit); @@ -385,6 +399,12 @@ namespace Compiler extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); + extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects, + opcodeRemoveSpellEffectsExplicit); + extensions.registerInstruction ("removeeffects", "l", opcodeRemoveEffects, + opcodeRemoveEffectsExplicit); + extensions.registerInstruction ("resurrect", "", opcodeResurrect, + opcodeResurrectExplicit); extensions.registerFunction ("getspell", 'l', "c", opcodeGetSpell, opcodeGetSpellExplicit); extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank); diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index d93e73849..e2b1c5c96 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -15,27 +15,27 @@ namespace Compiler { case 's': return mShorts; case 'l': return mLongs; - case 'f': return mFloats; + case 'f': return mFloats; } - + throw std::logic_error ("unknown variable type"); } - + int Locals::searchIndex (char type, const std::string& name) const { const std::vector& collection = get (type); - + std::vector::const_iterator iter = std::find (collection.begin(), collection.end(), name); - + if (iter==collection.end()) return -1; - + return iter-collection.begin(); } - + bool Locals::search (char type, const std::string& name) const - { + { return searchIndex (type, name)!=-1; } @@ -45,10 +45,10 @@ namespace Compiler { case 's': return mShorts; case 'l': return mLongs; - case 'f': return mFloats; + case 'f': return mFloats; } - - throw std::logic_error ("unknown variable type"); + + throw std::logic_error ("unknown variable type"); } char Locals::getType (const std::string& name) const @@ -58,35 +58,35 @@ namespace Compiler if (search ('l', name)) return 'l'; - + if (search ('f', name)) return 'f'; - + return ' '; } - + int Locals::getIndex (const std::string& name) const { int index = searchIndex ('s', name); - + if (index!=-1) return index; - + index = searchIndex ('l', name); if (index!=-1) return index; - return searchIndex ('f', name); + return searchIndex ('f', name); } - + void Locals::write (std::ostream& localFile) const { localFile << get ('s').size() << ' ' << get ('l').size() << ' ' << get ('f').size() << std::endl; - + std::copy (get ('s').begin(), get ('s').end(), std::ostream_iterator (localFile, " ")); std::copy (get ('l').begin(), get ('l').end(), @@ -94,12 +94,12 @@ namespace Compiler std::copy (get ('f').begin(), get ('f').end(), std::ostream_iterator (localFile, " ")); } - + void Locals::declare (char type, const std::string& name) { get (type).push_back (name); } - + void Locals::clear() { get ('s').clear(); diff --git a/components/compiler/locals.hpp b/components/compiler/locals.hpp index e54b7798c..d5bf05253 100644 --- a/components/compiler/locals.hpp +++ b/components/compiler/locals.hpp @@ -8,35 +8,35 @@ namespace Compiler { /// \brief Local variable declarations - + class Locals { std::vector mShorts; std::vector mLongs; std::vector mFloats; - + int searchIndex (char type, const std::string& name) const; bool search (char type, const std::string& name) const; - - std::vector& get (char type); - + + std::vector& get (char type); + public: - + char getType (const std::string& name) const; ///< 's': short, 'l': long, 'f': float, ' ': does not exist. - + int getIndex (const std::string& name) const; ///< return index for local variable \a name (-1: does not exist). - + const std::vector& get (char type) const; void write (std::ostream& localFile) const; ///< write declarations to file. - + void declare (char type, const std::string& name); ///< declares a variable. - + void clear(); ///< remove all declarations. }; diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index aca24e0d3..57d86e62b 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -25,7 +25,7 @@ namespace Compiler const int opcodeAiFollowExplicit = 0x20023; const int opcodeAiFollowCell = 0x20024; const int opcodeAiFollowCellExplicit = 0x20025; - const int opcodeSetHello = 0x200015e; + const int opcodeSetHello = 0x200015c; const int opcodeSetHelloExplicit = 0x200015d; const int opcodeSetFight = 0x200015e; const int opcodeSetFightExplicit = 0x200015f; @@ -49,6 +49,16 @@ namespace Compiler const int opcodeGetFleeExplicit = 0x20001c4; const int opcodeGetAlarm = 0x20001c5; const int opcodeGetAlarmExplicit = 0x20001c6; + const int opcodeGetLineOfSight = 0x2000222; + const int opcodeGetLineOfSightExplicit = 0x2000223; + const int opcodeToggleAI = 0x2000224; + const int opcodeToggleAIExplicit = 0x2000225; + const int opcodeGetTarget = 0x2000238; + const int opcodeGetTargetExplicit = 0x2000239; + const int opcodeStartCombat = 0x200023a; + const int opcodeStartCombatExplicit = 0x200023b; + const int opcodeStopCombat = 0x200023c; + const int opcodeStopCombatExplicit = 0x200023d; } namespace Animation @@ -65,7 +75,7 @@ namespace Compiler { const int opcodeCellChanged = 0x2000000; const int opcodeCOC = 0x2000026; - const int opcodeCOE = 0x200008e; + const int opcodeCOE = 0x2000226; const int opcodeGetInterior = 0x2000131; const int opcodeGetPCCell = 0x2000136; const int opcodeGetWaterLevel = 0x2000141; @@ -157,6 +167,7 @@ namespace Compiler const int opcodeEnableStatsMenu = 0x2000016; const int opcodeEnableRest = 0x2000017; const int opcodeShowRestMenu = 0x2000018; + const int opcodeShowRestMenuExplicit = 0x2000234; const int opcodeGetButtonPressed = 0x2000137; const int opcodeToggleFogOfWar = 0x2000145; const int opcodeToggleFullHelp = 0x2000151; @@ -184,6 +195,7 @@ namespace Compiler const int opcodeDontSaveObject = 0x2000153; const int opcodeToggleVanityMode = 0x2000174; const int opcodeGetPcSleep = 0x200019f; + const int opcodeGetPcJumping = 0x2000233; const int opcodeWakeUpPc = 0x20001a2; const int opcodeGetLocked = 0x20001c7; const int opcodeGetLockedExplicit = 0x20001c8; @@ -201,6 +213,8 @@ namespace Compiler const int opcodeGetAttackedExplicit = 0x20001d4; const int opcodeGetWeaponDrawn = 0x20001d7; const int opcodeGetWeaponDrawnExplicit = 0x20001d8; + const int opcodeGetSpellReadied = 0x2000231; + const int opcodeGetSpellReadiedExplicit = 0x2000232; const int opcodeGetSpellEffects = 0x20001db; const int opcodeGetSpellEffectsExplicit = 0x20001dc; const int opcodeGetCurrentTime = 0x20001dd; @@ -215,6 +229,9 @@ namespace Compiler const int opcodeGetStandingActorExplicit = 0x200020f; const int opcodeGetWindSpeed = 0x2000212; const int opcodePlayBink = 0x20001f7; + const int opcodeGoToJail = 0x2000235; + const int opcodePayFine = 0x2000236; + const int opcodePayFineThief = 0x2000237; const int opcodeHitOnMe = 0x2000213; const int opcodeHitOnMeExplicit = 0x2000214; const int opcodeDisableTeleporting = 0x2000215; @@ -224,6 +241,10 @@ namespace Compiler const int opcodeToggleGodMode = 0x200021f; const int opcodeDisableLevitation = 0x2000220; const int opcodeEnableLevitation = 0x2000221; + const int opcodeCast = 0x2000227; + const int opcodeCastExplicit = 0x2000228; + const int opcodeExplodeSpell = 0x2000229; + const int opcodeExplodeSpellExplicit = 0x200022a; } namespace Sky @@ -361,6 +382,13 @@ namespace Compiler const int opcodeIsWerewolfExplicit = 0x20001fe; const int opcodeGetWerewolfKills = 0x20001e2; + + const int opcodeRemoveSpellEffects = 0x200022b; + const int opcodeRemoveSpellEffectsExplicit = 0x200022c; + const int opcodeRemoveEffects = 0x200022d; + const int opcodeRemoveEffectsExplicit = 0x200022e; + const int opcodeResurrect = 0x200022f; + const int opcodeResurrectExplicit = 0x2000230; } namespace Transformation @@ -388,7 +416,7 @@ namespace Compiler const int opcodePlaceItemCell = 0x200019a; const int opcodePlaceItem = 0x200019b; - const int opcodePlaceAtPc = 0x200019c; + const int opcodePlaceAtPc = 0x200019c; const int opcodePlaceAtMe = 0x200019d; const int opcodePlaceAtMeExplicit = 0x200019e; const int opcodeModScale = 0x20001e3; diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp new file mode 100644 index 000000000..0d274474c --- /dev/null +++ b/components/contentselector/model/contentmodel.cpp @@ -0,0 +1,621 @@ +#include "contentmodel.hpp" +#include "esmfile.hpp" + +#include + +#include +#include +#include + +#include "components/esm/esmreader.hpp" + +ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : + QAbstractTableModel(parent), + mMimeType ("application/omwcontent"), + mMimeTypes (QStringList() << mMimeType), + mColumnCount (1), + mDragDropFlags (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled), + mDropActions (Qt::CopyAction | Qt::MoveAction) +{ + setEncoding ("win1252"); + uncheckAll(); +} + +void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) +{ + if (encoding == QLatin1String("win1252")) + mCodec = QTextCodec::codecForName("windows-1252"); + + else if (encoding == QLatin1String("win1251")) + mCodec = QTextCodec::codecForName("windows-1251"); + + else if (encoding == QLatin1String("win1250")) + mCodec = QTextCodec::codecForName("windows-1250"); + + else + return; // This should never happen; +} + +int ContentSelectorModel::ContentModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return mColumnCount; +} + +int ContentSelectorModel::ContentModel::rowCount(const QModelIndex &parent) const +{ + if(parent.isValid()) + return 0; + + return mFiles.size(); +} + +const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) const +{ + if (row >= 0 && row < mFiles.size()) + return mFiles.at(row); + + return 0; +} + +ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) +{ + if (row >= 0 && row < mFiles.count()) + return mFiles.at(row); + + return 0; +} +const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(const QString &name) const +{ + EsmFile::FileProperty fp = EsmFile::FileProperty_FileName; + + if (name.contains ('/')) + fp = EsmFile::FileProperty_FilePath; + + foreach (const EsmFile *file, mFiles) + { + if (name == file->fileProperty (fp).toString()) + return file; + } + return 0; +} + +QModelIndex ContentSelectorModel::ContentModel::indexFromItem(const EsmFile *item) const +{ + //workaround: non-const pointer cast for calls from outside contentmodel/contentselector + EsmFile *non_const_file_ptr = const_cast(item); + + if (item) + return index(mFiles.indexOf(non_const_file_ptr),0); + + return QModelIndex(); +} + +Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + const EsmFile *file = item(index.row()); + + if (!file) + return Qt::NoItemFlags; + + //game files can always be checked + if (file->isGameFile()) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + Qt::ItemFlags returnFlags; + bool allDependenciesFound = true; + bool gamefileChecked = false; + + //addon can be checked if its gamefile is and all other dependencies exist + foreach (const QString &fileName, file->gameFiles()) + { + bool depFound = false; + foreach (EsmFile *dependency, mFiles) + { + //compare filenames only. Multiple instances + //of the filename (with different paths) is not relevant here. + depFound = (dependency->fileName() == fileName); + + if (!depFound) + continue; + + if (!gamefileChecked) + { + if (isChecked (dependency->filePath())) + gamefileChecked = (dependency->isGameFile()); + } + + // force it to iterate all files in cases where the current + // dependency is a game file to ensure that a later duplicate + // game file is / is not checked. + // (i.e., break only if it's not a gamefile or the game file has been checked previously) + if (gamefileChecked || !(dependency->isGameFile())) + break; + } + + allDependenciesFound = allDependenciesFound && depFound; + } + + if (gamefileChecked) + { + if (allDependenciesFound) + returnFlags = returnFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | mDragDropFlags; + else + returnFlags = Qt::ItemIsSelectable; + } + + return returnFlags; +} + +QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= mFiles.size()) + return QVariant(); + + const EsmFile *file = item(index.row()); + + if (!file) + return QVariant(); + + const int column = index.column(); + + switch (role) + { + case Qt::EditRole: + case Qt::DisplayRole: + { + if (column >=0 && column <=EsmFile::FileProperty_GameFile) + return file->fileProperty(static_cast(column)); + + return QVariant(); + break; + } + + case Qt::TextAlignmentRole: + { + switch (column) + { + case 0: + case 1: + return Qt::AlignLeft + Qt::AlignVCenter; + case 2: + case 3: + return Qt::AlignRight + Qt::AlignVCenter; + default: + return Qt::AlignLeft + Qt::AlignVCenter; + } + return QVariant(); + break; + } + + case Qt::ToolTipRole: + { + if (column != 0) + return QVariant(); + + return file->toolTip(); + break; + } + + case Qt::CheckStateRole: + { + if (file->isGameFile()) + return QVariant(); + + return mCheckStates[file->filePath()]; + + break; + } + + case Qt::UserRole: + { + if (file->isGameFile()) + return ContentType_GameFile; + else + if (flags(index)) + return ContentType_Addon; + + break; + } + + case Qt::UserRole + 1: + return isChecked(file->filePath()); + break; + } + return QVariant(); +} + +bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if(!index.isValid()) + return false; + + EsmFile *file = item(index.row()); + QString fileName = file->filePath(); + bool success = false; + + switch(role) + { + case Qt::EditRole: + { + QStringList list = value.toStringList(); + + for (int i = 0; i < EsmFile::FileProperty_GameFile; i++) + file->setFileProperty(static_cast(i), list.at(i)); + + for (int i = EsmFile::FileProperty_GameFile; i < list.size(); i++) + file->setFileProperty (EsmFile::FileProperty_GameFile, list.at(i)); + + emit dataChanged(index, index); + + success = true; + } + break; + + case Qt::UserRole+1: + { + success = (flags (index) & Qt::ItemIsEnabled); + + if (success) + { + success = setCheckState(fileName, value.toBool()); + emit dataChanged(index, index); + } + } + break; + + case Qt::CheckStateRole: + { + int checkValue = value.toInt(); + bool success = false; + bool setState = false; + if ((checkValue==Qt::Checked) && !isChecked(fileName)) + { + setState = true; + success = true; + } + else if ((checkValue == Qt::Checked) && isChecked (fileName)) + setState = true; + else if (checkValue == Qt::Unchecked) + setState = true; + + if (setState) + { + setCheckState(fileName, success); + emit dataChanged(index, index); + + } + else + return success; + + + foreach (EsmFile *file, mFiles) + { + if (file->gameFiles().contains(fileName)) + { + QModelIndex idx = indexFromItem(file); + emit dataChanged(idx, idx); + } + } + + success = true; + } + break; + } + + return success; +} + +bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) +{ + if (parent.isValid()) + return false; + + beginInsertRows(parent, position, position+rows-1); + { + for (int row = 0; row < rows; ++row) + mFiles.insert(position, new EsmFile); + + } endInsertRows(); + + return true; +} + +bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent) +{ + if (parent.isValid()) + return false; + + beginRemoveRows(parent, position, position+rows-1); + { + for (int row = 0; row < rows; ++row) + delete mFiles.takeAt(position); + + } endRemoveRows(); + + return true; +} + +Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const +{ + return mDropActions; +} + +QStringList ContentSelectorModel::ContentModel::mimeTypes() const +{ + return mMimeTypes; +} + +QMimeData *ContentSelectorModel::ContentModel::mimeData(const QModelIndexList &indexes) const +{ + QByteArray encodedData; + + foreach (const QModelIndex &index, indexes) + { + if (!index.isValid()) + continue; + + encodedData.append(item(index.row())->encodedData()); + } + + QMimeData *mimeData = new QMimeData(); + mimeData->setData(mMimeType, encodedData); + + return mimeData; +} + +bool ContentSelectorModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) + return true; + + if (column > 0) + return false; + + if (!data->hasFormat(mMimeType)) + return false; + + int beginRow = rowCount(); + + if (row != -1) + beginRow = row; + + else if (parent.isValid()) + beginRow = parent.row(); + + QByteArray encodedData = data->data(mMimeType); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + + while (!stream.atEnd()) + { + + QString value; + QStringList values; + QStringList gamefiles; + + for (int i = 0; i < EsmFile::FileProperty_GameFile; ++i) + { + stream >> value; + values << value; + } + + stream >> gamefiles; + + insertRows(beginRow, 1); + + QModelIndex idx = index(beginRow++, 0, QModelIndex()); + setData(idx, QStringList() << values << gamefiles, Qt::EditRole); + } + + return true; +} + +void ContentSelectorModel::ContentModel::addFile(EsmFile *file) +{ + beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); + mFiles.append(file); + endInsertRows(); + + QModelIndex idx = index (mFiles.size() - 2, 0, QModelIndex()); + + emit dataChanged (idx, idx); +} + +void ContentSelectorModel::ContentModel::addFiles(const QString &path) +{ + QDir dir(path); + QStringList filters; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; + dir.setNameFilters(filters); + + QTextCodec *codec = QTextCodec::codecForName("UTF8"); + + // Create a decoder for non-latin characters in esx metadata + QTextDecoder *decoder = codec->makeDecoder(); + + foreach (const QString &path, dir.entryList()) + { + QFileInfo info(dir.absoluteFilePath(path)); + EsmFile *file = new EsmFile(path); + + try { + ESM::ESMReader fileReader; + ToUTF8::Utf8Encoder encoder = + ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString()); + fileReader.setEncoder(&encoder); + fileReader.open(dir.absoluteFilePath(path).toStdString()); + + foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) + file->addGameFile(QString::fromStdString(item.name)); + + file->setAuthor (decoder->toUnicode(fileReader.getAuthor().c_str())); + file->setDate (info.lastModified()); + file->setFormat (fileReader.getFormat()); + file->setFilePath (info.absoluteFilePath()); + file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); + + + // Put the file in the table + if (item(file->filePath()) == 0) + addFile(file); + + } catch(std::runtime_error &e) { + // An error occurred while reading the .esp + qWarning() << "Error reading addon file: " << e.what(); + continue; + } + + } + + delete decoder; + + sortFiles(); +} + +void ContentSelectorModel::ContentModel::sortFiles() +{ + //first, sort the model such that all dependencies are ordered upstream (gamefile) first. + bool movedFiles = true; + int fileCount = mFiles.size(); + + //Dependency sort + //iterate until no sorting of files occurs + while (movedFiles) + { + movedFiles = false; + //iterate each file, obtaining a reference to it's gamefiles list + for (int i = 0; i < fileCount; i++) + { + QModelIndex idx1 = index (i, 0, QModelIndex()); + const QStringList &gamefiles = mFiles.at(i)->gameFiles(); + //iterate each file after the current file, verifying that none of it's + //dependencies appear. + for (int j = i + 1; j < fileCount; j++) + { + if (gamefiles.contains(mFiles.at(j)->fileName())) + { + mFiles.move(j, i); + + QModelIndex idx2 = index (j, 0, QModelIndex()); + + emit dataChanged (idx1, idx2); + + movedFiles = true; + } + } + if (movedFiles) + break; + } + } +} + +bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const +{ + if (mCheckStates.contains(name)) + return (mCheckStates[name] == Qt::Checked); + + return false; +} + +bool ContentSelectorModel::ContentModel::isEnabled (QModelIndex index) const +{ + return (flags(index) & Qt::ItemIsEnabled); +} + +void ContentSelectorModel::ContentModel::setCheckStates (const QStringList &fileList, bool isChecked) +{ + foreach (const QString &file, fileList) + { + setCheckState (file, isChecked); + } +} + +void ContentSelectorModel::ContentModel::refreshModel() +{ + emit dataChanged (index(0,0), index(rowCount()-1,0)); +} + +bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) +{ + if (name.isEmpty()) + return false; + + const EsmFile *file = item(name); + + if (!file) + return false; + + Qt::CheckState state = Qt::Unchecked; + + if (checkState) + state = Qt::Checked; + + mCheckStates[name] = state; + emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); + + if (file->isGameFile()) + refreshModel(); + + //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. + if (state == Qt::Checked) + { + foreach (QString upstreamName, file->gameFiles()) + { + const EsmFile *upstreamFile = item(upstreamName); + + if (!upstreamFile) + continue; + + if (!isChecked(upstreamFile->filePath())) + mCheckStates[upstreamFile->filePath()] = Qt::Checked; + + emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); + + } + } + //otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. + if (state == Qt::Unchecked) + { + foreach (const EsmFile *downstreamFile, mFiles) + { + if (downstreamFile->gameFiles().contains(name)) + { + if (mCheckStates.contains(downstreamFile->filePath())) + mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; + + emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); + } + } + } + + return true; +} + +ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checkedItems() const +{ + ContentFileList list; + + // TODO: + // First search for game files and next addons, + // so we get more or less correct game files vs addons order. + foreach (EsmFile *file, mFiles) + if (isChecked(file->filePath())) + list << file; + + return list; +} + +void ContentSelectorModel::ContentModel::uncheckAll() +{ + emit layoutAboutToBeChanged(); + mCheckStates.clear(); + emit layoutChanged(); +} diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp new file mode 100644 index 000000000..8c8c2124b --- /dev/null +++ b/components/contentselector/model/contentmodel.hpp @@ -0,0 +1,77 @@ +#ifndef CONTENTMODEL_HPP +#define CONTENTMODEL_HPP + +#include +#include + +namespace ContentSelectorModel +{ + class EsmFile; + + typedef QList ContentFileList; + + enum ContentType + { + ContentType_GameFile, + ContentType_Addon + }; + + class ContentModel : public QAbstractTableModel + { + Q_OBJECT + public: + explicit ContentModel(QObject *parent = 0); + + void setEncoding(const QString &encoding); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()); + bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()); + + Qt::DropActions supportedDropActions() const; + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + + void addFiles(const QString &path); + + QModelIndex indexFromItem(const EsmFile *item) const; + const EsmFile *item(const QString &name) const; + + bool isEnabled (QModelIndex index) const; + bool isChecked(const QString &name) const; + bool setCheckState(const QString &name, bool isChecked); + void setCheckStates (const QStringList &fileList, bool isChecked); + ContentFileList checkedItems() const; + void uncheckAll(); + + void refreshModel(); + + private: + + void addFile(EsmFile *file); + const EsmFile *item(int row) const; + EsmFile *item(int row); + + void sortFiles(); + + ContentFileList mFiles; + QHash mCheckStates; + QTextCodec *mCodec; + + public: + + QString mMimeType; + QStringList mMimeTypes; + int mColumnCount; + Qt::ItemFlags mDragDropFlags; + Qt::DropActions mDropActions; + }; +} +#endif // CONTENTMODEL_HPP diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp new file mode 100644 index 000000000..a0a09105a --- /dev/null +++ b/components/contentselector/model/esmfile.cpp @@ -0,0 +1,137 @@ +#include "esmfile.hpp" + +#include +#include + +int ContentSelectorModel::EsmFile::sPropertyCount = 7; +QString ContentSelectorModel::EsmFile::sToolTip = QString("Author: %1
\ + Version: %2
\ + Path:
%3
\ +
Description:
%4
\ +
Dependencies: %5
"); + + +ContentSelectorModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) + : ModelItem(parent), mFileName(fileName), mFormat(0) +{} + +void ContentSelectorModel::EsmFile::setFileName(const QString &fileName) +{ + mFileName = fileName; +} + +void ContentSelectorModel::EsmFile::setAuthor(const QString &author) +{ + mAuthor = author; +} + +void ContentSelectorModel::EsmFile::setDate(const QDateTime &modified) +{ + mModified = modified; +} + +void ContentSelectorModel::EsmFile::setFormat(int format) +{ + mFormat = format; +} + +void ContentSelectorModel::EsmFile::setFilePath(const QString &path) +{ + mPath = path; +} + +void ContentSelectorModel::EsmFile::setGameFiles(const QStringList &gamefiles) +{ + mGameFiles = gamefiles; +} + +void ContentSelectorModel::EsmFile::setDescription(const QString &description) +{ + mDescription = description; +} + +QByteArray ContentSelectorModel::EsmFile::encodedData() const +{ + QByteArray encodedData; + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + stream << mFileName << mAuthor << QString::number(mFormat) + << mModified.toString() << mPath << mDescription + << mGameFiles; + + return encodedData; +} + +QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) const +{ + switch (prop) + { + case FileProperty_FileName: + return mFileName; + break; + + case FileProperty_Author: + return mAuthor; + break; + + case FileProperty_Format: + return mFormat; + break; + + case FileProperty_DateModified: + return mModified.toString(Qt::ISODate); + break; + + case FileProperty_FilePath: + return mPath; + break; + + case FileProperty_Description: + return mDescription; + break; + + case FileProperty_GameFile: + return mGameFiles; + break; + + default: + break; + } + return QVariant(); +} +void ContentSelectorModel::EsmFile::setFileProperty (const FileProperty prop, const QString &value) +{ + switch (prop) + { + case FileProperty_FileName: + mFileName = value; + break; + + case FileProperty_Author: + mAuthor = value; + break; + + case FileProperty_Format: + mFormat = value.toInt(); + break; + + case FileProperty_DateModified: + mModified = QDateTime::fromString(value); + break; + + case FileProperty_FilePath: + mPath = value; + break; + + case FileProperty_Description: + mDescription = value; + break; + + case FileProperty_GameFile: + mGameFiles << value; + break; + + default: + break; + } +} diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp new file mode 100644 index 000000000..ca24b52d1 --- /dev/null +++ b/components/contentselector/model/esmfile.hpp @@ -0,0 +1,86 @@ +#ifndef ESMFILE_HPP +#define ESMFILE_HPP + +#include +#include + +#include "modelitem.hpp" + +class QMimeData; + +namespace ContentSelectorModel +{ + class EsmFile : public ModelItem + { + Q_OBJECT + Q_PROPERTY(QString filename READ fileName) + + public: + + enum FileProperty + { + FileProperty_FileName = 0, + FileProperty_Author = 1, + FileProperty_Format = 2, + FileProperty_DateModified = 3, + FileProperty_FilePath = 4, + FileProperty_Description = 5, + FileProperty_GameFile = 6 + }; + + EsmFile(QString fileName = QString(), ModelItem *parent = 0); + // EsmFile(const EsmFile &); + + ~EsmFile() + {} + + void setFileProperty (const FileProperty prop, const QString &value); + + void setFileName(const QString &fileName); + void setAuthor(const QString &author); + void setSize(const int size); + void setDate(const QDateTime &modified); + void setFormat(const int format); + void setFilePath(const QString &path); + void setGameFiles(const QStringList &gameFiles); + void setDescription(const QString &description); + + inline void addGameFile (const QString &name) {mGameFiles.append(name); } + QVariant fileProperty (const FileProperty prop) const; + + inline QString fileName() const { return mFileName; } + inline QString author() const { return mAuthor; } + inline QDateTime modified() const { return mModified; } + inline float format() const { return mFormat; } + inline QString filePath() const { return mPath; } + inline const QStringList &gameFiles() const { return mGameFiles; } + inline QString description() const { return mDescription; } + inline QString toolTip() const { return sToolTip.arg(mAuthor) + .arg(mFormat) + .arg(mPath) + .arg(mDescription) + .arg(mGameFiles.join(", ")); + } + + inline bool isGameFile() const { return (mGameFiles.size() == 0); } + QByteArray encodedData() const; + + public: + static int sPropertyCount; + static QString sToolTip; + + private: + + QString mFileName; + QString mAuthor; + QDateTime mModified; + int mFormat; + QString mPath; + QStringList mGameFiles; + QString mDescription; + QString mToolTip; + + }; +} + +#endif diff --git a/components/contentselector/model/modelitem.cpp b/components/contentselector/model/modelitem.cpp new file mode 100644 index 000000000..e1d737c2d --- /dev/null +++ b/components/contentselector/model/modelitem.cpp @@ -0,0 +1,69 @@ +#include "modelitem.hpp" + +ContentSelectorModel::ModelItem::ModelItem(ModelItem *parent) + : mParentItem(parent) +{ +} +/* +ContentSelectorModel::ModelItem::ModelItem(const ModelItem *parent) + // : mParentItem(parent) +{ +} +*/ + +ContentSelectorModel::ModelItem::~ModelItem() +{ + qDeleteAll(mChildItems); +} + + +ContentSelectorModel::ModelItem *ContentSelectorModel::ModelItem::parent() const +{ + return mParentItem; +} + +bool ContentSelectorModel::ModelItem::hasFormat(const QString &mimetype) const +{ + if (mimetype == "application/omwcontent") + return true; + + return QMimeData::hasFormat(mimetype); +} +int ContentSelectorModel::ModelItem::row() const +{ + if (mParentItem) + return 1; + //return mParentItem->childRow(const_cast(this)); + //return mParentItem->mChildItems.indexOf(const_cast(this)); + + return -1; +} + + +int ContentSelectorModel::ModelItem::childCount() const +{ + return mChildItems.count(); +} + +int ContentSelectorModel::ModelItem::childRow(ModelItem *child) const +{ + Q_ASSERT(child); + + return mChildItems.indexOf(child); +} + +ContentSelectorModel::ModelItem *ContentSelectorModel::ModelItem::child(int row) +{ + return mChildItems.value(row); +} + + +void ContentSelectorModel::ModelItem::appendChild(ModelItem *item) +{ + mChildItems.append(item); +} + +void ContentSelectorModel::ModelItem::removeChild(int row) +{ + mChildItems.removeAt(row); +} diff --git a/components/contentselector/model/modelitem.hpp b/components/contentselector/model/modelitem.hpp new file mode 100644 index 000000000..57214b09c --- /dev/null +++ b/components/contentselector/model/modelitem.hpp @@ -0,0 +1,39 @@ +#ifndef MODELITEM_HPP +#define MODELITEM_HPP + +#include +#include + +namespace ContentSelectorModel +{ + class ModelItem : public QMimeData + { + Q_OBJECT + + public: + ModelItem(ModelItem *parent = 0); + //ModelItem(const ModelItem *parent = 0); + + ~ModelItem(); + + ModelItem *parent() const; + int row() const; + + int childCount() const; + int childRow(ModelItem *child) const; + ModelItem *child(int row); + + void appendChild(ModelItem *child); + void removeChild(int row); + + bool hasFormat(const QString &mimetype) const; + + //virtual bool acceptChild(ModelItem *child); + + protected: + ModelItem *mParentItem; + QList mChildItems; + }; +} + +#endif diff --git a/components/fileorderlist/utils/naturalsort.cpp b/components/contentselector/model/naturalsort.cpp similarity index 100% rename from components/fileorderlist/utils/naturalsort.cpp rename to components/contentselector/model/naturalsort.cpp diff --git a/components/contentselector/model/naturalsort.hpp b/components/contentselector/model/naturalsort.hpp new file mode 100644 index 000000000..8386e4e9f --- /dev/null +++ b/components/contentselector/model/naturalsort.hpp @@ -0,0 +1,11 @@ +#ifndef NATURALSORT_H +#define NATURALSORT_H + +#include + + bool naturalSortLessThanCS( const QString &left, const QString &right ); + bool naturalSortLessThanCI( const QString &left, const QString &right ); + bool naturalSortGreaterThanCS( const QString &left, const QString &right ); + bool naturalSortGreaterThanCI( const QString &left, const QString &right ); + +#endif diff --git a/components/contentselector/view/combobox.cpp b/components/contentselector/view/combobox.cpp new file mode 100644 index 000000000..1d773b62d --- /dev/null +++ b/components/contentselector/view/combobox.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +#include "combobox.hpp" + +ContentSelectorView::ComboBox::ComboBox(QWidget *parent) : + QComboBox(parent) +{ + mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore + setValidator(mValidator); + setCompleter(0); + setEnabled (true); + + setInsertPolicy(QComboBox::NoInsert); +} + +void ContentSelectorView::ComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected + opt.currentText = mPlaceholderText; + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + +void ContentSelectorView::ComboBox::setPlaceholderText(const QString &text) +{ + mPlaceholderText = text; +} diff --git a/components/contentselector/view/combobox.hpp b/components/contentselector/view/combobox.hpp new file mode 100644 index 000000000..e3888af2c --- /dev/null +++ b/components/contentselector/view/combobox.hpp @@ -0,0 +1,30 @@ +#ifndef COMBOBOX_HPP +#define COMBOBOX_HPP + +#include +#include + +class QString; +class QRegExpValidator; + +namespace ContentSelectorView +{ + class ComboBox : public QComboBox + { + Q_OBJECT + + public: + explicit ComboBox (QWidget *parent = 0); + + void setPlaceholderText(const QString &text); + + private: + QString mPlaceholderText; + + protected: + void paintEvent(QPaintEvent *); + QRegExpValidator *mValidator; + }; +} + +#endif // COMBOBOX_HPP diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp new file mode 100644 index 000000000..e9599de49 --- /dev/null +++ b/components/contentselector/view/contentselector.cpp @@ -0,0 +1,203 @@ +#include "contentselector.hpp" + +#include "../model/esmfile.hpp" + +#include + +#include +#include + +#include +#include +#include +#include + +ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : + QObject(parent) +{ + ui.setupUi (parent); + + buildContentModel(); + buildGameFileView(); + buildAddonView(); +} + +void ContentSelectorView::ContentSelector::buildContentModel() +{ + mContentModel = new ContentSelectorModel::ContentModel(); +} + +void ContentSelectorView::ContentSelector::buildGameFileView() +{ + ui.gameFileView->setVisible (true); + + mGameFileProxyModel = new QSortFilterProxyModel(this); + mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); + mGameFileProxyModel->setFilterRole (Qt::UserRole); + mGameFileProxyModel->setSourceModel (mContentModel); + + ui.gameFileView->setPlaceholderText(QString("Select a game file...")); + ui.gameFileView->setModel(mGameFileProxyModel); + + connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), + this, SLOT (slotCurrentGameFileIndexChanged(int))); + + ui.gameFileView->setCurrentIndex(-1); + ui.gameFileView->setCurrentIndex(0); +} + +void ContentSelectorView::ContentSelector::buildAddonView() +{ + ui.addonView->setVisible (true); + + mAddonProxyModel = new QSortFilterProxyModel(this); + mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); + mAddonProxyModel->setFilterRole (Qt::UserRole); + mAddonProxyModel->setDynamicSortFilter (true); + mAddonProxyModel->setSourceModel (mContentModel); + + ui.addonView->setModel(mAddonProxyModel); + + connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); +} + +void ContentSelectorView::ContentSelector::setProfileContent(const QStringList &fileList) +{ + clearCheckStates(); + bool foundGamefile = false; + + foreach (const QString &filepath, fileList) + { + if (!foundGamefile) + { + const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath); + + foundGamefile = (file->isGameFile()); + + if (foundGamefile) + { + setGameFile (filepath); + break; + } + } + } + +/* if (!foundGameFile) + { + //throw gamefile error here. + }*/ + + setCheckStates (fileList); +} + +void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) +{ + int index = -1; + + if (!filename.isEmpty()) + { + const ContentSelectorModel::EsmFile *file = mContentModel->item (filename); + index = ui.gameFileView->findText (file->fileName()); + + //verify that the current index is also checked in the model + if (!mContentModel->setCheckState(filename, true)) + { + //throw error in case file not found? + return; + } + } + + ui.gameFileView->setCurrentIndex(index); +} + +void ContentSelectorView::ContentSelector::clearCheckStates() +{ + mContentModel->uncheckAll(); +} + +void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &list) +{ + if (list.isEmpty()) + { + slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); + } + else + mContentModel->setCheckStates (list, true); +} + +ContentSelectorModel::ContentFileList + ContentSelectorView::ContentSelector::selectedFiles() const +{ + if (!mContentModel) + return ContentSelectorModel::ContentFileList(); + + return mContentModel->checkedItems(); +} + +void ContentSelectorView::ContentSelector::addFiles(const QString &path) +{ + mContentModel->addFiles(path); + + if (ui.gameFileView->currentIndex() != -1) + ui.gameFileView->setCurrentIndex(-1); + + mContentModel->uncheckAll(); +} + +QString ContentSelectorView::ContentSelector::currentFile() const +{ + QModelIndex currentIdx = ui.addonView->currentIndex(); + + if (!currentIdx.isValid()) + return ui.gameFileView->currentText(); + + QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); + return mContentModel->data(idx, Qt::DisplayRole).toString(); +} + +void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) +{ + static int oldIndex = -1; + + QAbstractItemModel *const model = ui.gameFileView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); + + if (proxy) + proxy->setDynamicSortFilter(false); + + if (index != oldIndex) + { + if (oldIndex > -1) + model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + + oldIndex = index; + + model->setData(model->index(index, 0), true, Qt::UserRole + 1); + } + + if (proxy) + proxy->setDynamicSortFilter(true); + + emit signalCurrentGamefileIndexChanged (index); +} + +void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) +{ + QModelIndex sourceIndex = mAddonProxyModel->mapToSource (index); + + if (!mContentModel->isEnabled (sourceIndex)) + return; + + Qt::CheckState checkState = Qt::Unchecked; + + if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() == Qt::Unchecked) + checkState = Qt::Checked; + + mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole); + + if (checkState == Qt::Checked) + emit signalAddonFileSelected (index.row()); + else + emit signalAddonFileUnselected (index.row()); + +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp new file mode 100644 index 000000000..a25eb20ae --- /dev/null +++ b/components/contentselector/view/contentselector.hpp @@ -0,0 +1,68 @@ +#ifndef CONTENTSELECTOR_HPP +#define CONTENTSELECTOR_HPP + +#include + +#include "ui_contentselector.h" +#include "../model/contentmodel.hpp" + +class QSortFilterProxyModel; + +namespace ContentSelectorView +{ + class ContentSelector : public QObject + { + Q_OBJECT + + QStringList mFilePaths; + + protected: + + ContentSelectorModel::ContentModel *mContentModel; + QSortFilterProxyModel *mGameFileProxyModel; + QSortFilterProxyModel *mAddonProxyModel; + + public: + + explicit ContentSelector(QWidget *parent = 0); + + QString currentFile() const; + + void addFiles(const QString &path); + void setProfileContent (const QStringList &fileList); + + void clearCheckStates(); + void setCheckStates (const QStringList &list); + + ContentSelectorModel::ContentFileList selectedFiles() const; + + void setGameFile (const QString &filename = QString("")); + + bool isGamefileSelected() const + { return ui.gameFileView->currentIndex() != -1; } + + QWidget *uiWidget() const + { return ui.contentGroupBox; } + + + private: + + Ui::ContentSelector ui; + + void buildContentModel(); + void buildGameFileView(); + void buildAddonView(); + + signals: + void signalCurrentGamefileIndexChanged (int); + void signalAddonFileSelected (int); + void signalAddonFileUnselected (int); + + private slots: + + void slotCurrentGameFileIndexChanged(int index); + void slotAddonTableItemClicked(const QModelIndex &index); + }; +} + +#endif // CONTENTSELECTOR_HPP diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index 1440dbd13..cf4951de7 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -44,9 +44,9 @@ namespace ESM } } - void AIPackageList::save(ESMWriter &esm) + void AIPackageList::save(ESMWriter &esm) const { - typedef std::vector::iterator PackageIter; + typedef std::vector::const_iterator PackageIter; for (PackageIter it = mList.begin(); it != mList.end(); ++it) { switch (it->mType) { case AI_Wander: diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp index 38499b2dd..8a31aadf5 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm/aipackage.hpp @@ -30,7 +30,7 @@ namespace ESM short mDuration; unsigned char mTimeOfDay; unsigned char mIdle[8]; - unsigned char mUnk; + unsigned char mShouldRepeat; }; struct AITravel @@ -93,7 +93,7 @@ namespace ESM /// it needs to use retSubName() if needed. But, hey, there /// is only one field left (XSCL) and only two records uses AI void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp new file mode 100644 index 000000000..5bc8b7aef --- /dev/null +++ b/components/esm/cellid.cpp @@ -0,0 +1,26 @@ + +#include "cellid.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::CellId::load (ESMReader &esm) +{ + mWorldspace = esm.getHNString ("SPAC"); + + if (esm.isNextSub ("CIDX")) + { + esm.getHT (mIndex, 8); + mPaged = true; + } + else + mPaged = false; +} + +void ESM::CellId::save (ESMWriter &esm) const +{ + esm.writeHNString ("SPAC", mWorldspace); + + if (mPaged) + esm.writeHNT ("CIDX", mIndex, 8); +} \ No newline at end of file diff --git a/components/esm/cellid.hpp b/components/esm/cellid.hpp new file mode 100644 index 000000000..54dbdae78 --- /dev/null +++ b/components/esm/cellid.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESM_CELLID_H +#define OPENMW_ESM_CELLID_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct CellId + { + struct CellIndex + { + int mX; + int mY; + }; + + std::string mWorldspace; + CellIndex mIndex; + bool mPaged; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 95cf24d33..00b15f4a3 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,11 +1,87 @@ #include "cellref.hpp" +#include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::CellRef::save(ESMWriter &esm) +void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) { - esm.writeHNT("FRMR", mRefnum); + // NAM0 sometimes appears here, sometimes further on + mNam0 = 0; + if (esm.isNextSub ("NAM0")) + esm.getHT (mNam0); + + if (wideRefNum) + esm.getHNT (mRefNum, "FRMR", 8); + else + esm.getHNT (mRefNum.mIndex, "FRMR"); + + mRefID = esm.getHNString ("NAME"); + + // Again, UNAM sometimes appears after NAME and sometimes later. + // Or perhaps this UNAM means something different? + mReferenceBlocked = -1; + esm.getHNOT (mReferenceBlocked, "UNAM"); + + mScale = 1.0; + esm.getHNOT (mScale, "XSCL"); + + mOwner = esm.getHNOString ("ANAM"); + mGlob = esm.getHNOString ("BNAM"); + mSoul = esm.getHNOString ("XSOL"); + + mFaction = esm.getHNOString ("CNAM"); + mFactIndex = -2; + esm.getHNOT (mFactIndex, "INDX"); + + mGoldValue = 1; + mCharge = -1; + mEnchantmentCharge = -1; + + esm.getHNOT (mEnchantmentCharge, "XCHG"); + + esm.getHNOT (mCharge, "INTV"); + + esm.getHNOT (mGoldValue, "NAM9"); + + // Present for doors that teleport you to another cell. + if (esm.isNextSub ("DODT")) + { + mTeleport = true; + esm.getHT (mDoorDest); + mDestCell = esm.getHNOString ("DNAM"); + } + else + mTeleport = false; + + mLockLevel = -1; + esm.getHNOT (mLockLevel, "FLTV"); + mKey = esm.getHNOString ("KNAM"); + mTrap = esm.getHNOString ("TNAM"); + + mFltv = 0; + esm.getHNOT (mReferenceBlocked, "UNAM"); + esm.getHNOT (mFltv, "FLTV"); + + esm.getHNOT(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. + if (esm.isNextSub ("NAM0")) + esm.getHT (mNam0); +} + +void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const +{ + if (wideRefNum) + esm.writeHNT ("FRMR", mRefNum, 8); + else + esm.writeHNT ("FRMR", mRefNum.mIndex, 4); + esm.writeHNCString("NAME", mRefID); if (mScale != 1.0) { @@ -31,34 +107,38 @@ void ESM::CellRef::save(ESMWriter &esm) esm.writeHNT("NAM9", mGoldValue); } - if (mTeleport) + if (mTeleport && !inInventory) { esm.writeHNT("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != -1) { + if (mLockLevel != -1 && !inInventory) esm.writeHNT("FLTV", mLockLevel); - } - esm.writeHNOCString("KNAM", mKey); - esm.writeHNOCString("TNAM", mTrap); - if (mReferenceBlocked != -1) { + if (!inInventory) + esm.writeHNOCString ("KNAM", mKey); + + if (!inInventory) + esm.writeHNOCString ("TNAM", mTrap); + + if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); - } - if (mFltv != 0) { + + if (mFltv != 0 && !inInventory) esm.writeHNT("FLTV", mFltv); - } - esm.writeHNT("DATA", mPos, 24); - if (mNam0 != 0) { + if (!inInventory) + esm.writeHNT("DATA", mPos, 24); + + if (mNam0 != 0 && !inInventory) esm.writeHNT("NAM0", mNam0); - } } void ESM::CellRef::blank() { - mRefnum = 0; + mRefNum.mIndex = 0; + mRefNum.mContentFile = -1; mRefID.clear(); mScale = 1; mOwner.clear(); @@ -84,4 +164,9 @@ void ESM::CellRef::blank() mPos.pos[i] = 0; mPos.rot[i] = 0; } -} \ No newline at end of file +} + +bool ESM::operator== (const CellRef::RefNum& left, const CellRef::RefNum& right) +{ + return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; +} diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 31889914c..16f6603a2 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -8,6 +8,7 @@ namespace ESM { class ESMWriter; + class ESMReader; /* Cell reference. This represents ONE object (of many) inside the cell. The cell references are not loaded as part of the normal @@ -19,7 +20,13 @@ namespace ESM { public: - int mRefnum; // Reference number + struct RefNum + { + int mIndex; + int mContentFile; // -1 no content file + }; + + RefNum mRefNum; // Reference number std::string mRefID; // ID of object being referenced float mScale; // Scale applied to mesh @@ -34,8 +41,8 @@ namespace ESM // ID of creature trapped in this soul gem (?) std::string mSoul; - // ?? CNAM has a faction name, might be for objects/beds etc - // belonging to a faction. + // The faction that owns this object (and will get angry if + // you take it and are not a faction member) std::string mFaction; // INDX might be PC faction rank required to use the item? Sometimes @@ -71,9 +78,6 @@ namespace ESM // -1 is not blocked, otherwise it is blocked. signed char mReferenceBlocked; - // 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 // activators. @@ -83,10 +87,14 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; - void save(ESMWriter &esm); + void load (ESMReader& esm, bool wideRefNum = false); + + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; void blank(); }; + + bool operator== (const CellRef::RefNum& left, const CellRef::RefNum& right); } -#endif \ No newline at end of file +#endif diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp new file mode 100644 index 000000000..1f7e8197e --- /dev/null +++ b/components/esm/cellstate.cpp @@ -0,0 +1,17 @@ + +#include "cellstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::CellState::load (ESMReader &esm) +{ + mWaterLevel = 0; + esm.getHNOT (mWaterLevel, "WLVL"); +} + +void ESM::CellState::save (ESMWriter &esm) const +{ + if (!mId.mPaged) + esm.writeHNT ("WLVL", mWaterLevel); +} \ No newline at end of file diff --git a/components/esm/cellstate.hpp b/components/esm/cellstate.hpp new file mode 100644 index 000000000..cd0db3067 --- /dev/null +++ b/components/esm/cellstate.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_ESM_CELLSTATE_H +#define OPENMW_ESM_CELLSTATE_H + +#include "cellid.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + /// \note Does not include references + struct CellState + { + CellId mId; + + float mWaterLevel; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/containerstate.cpp b/components/esm/containerstate.cpp new file mode 100644 index 000000000..5dcf17733 --- /dev/null +++ b/components/esm/containerstate.cpp @@ -0,0 +1,16 @@ + +#include "containerstate.hpp" + +void ESM::ContainerState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mInventory.load (esm); +} + +void ESM::ContainerState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + mInventory.save (esm); +} \ No newline at end of file diff --git a/components/esm/containerstate.hpp b/components/esm/containerstate.hpp new file mode 100644 index 000000000..1ecf2b46e --- /dev/null +++ b/components/esm/containerstate.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESM_CONTAINERSTATE_H +#define OPENMW_ESM_CONTAINERSTATE_H + +#include "objectstate.hpp" +#include "inventorystate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct ContainerState : public ObjectState + { + InventoryState mInventory; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp new file mode 100644 index 000000000..43cde3025 --- /dev/null +++ b/components/esm/creaturestate.cpp @@ -0,0 +1,16 @@ + +#include "creaturestate.hpp" + +void ESM::CreatureState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mInventory.load (esm); +} + +void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + mInventory.save (esm); +} \ No newline at end of file diff --git a/components/esm/creaturestate.hpp b/components/esm/creaturestate.hpp new file mode 100644 index 000000000..f7f9b8038 --- /dev/null +++ b/components/esm/creaturestate.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESM_CREATURESTATE_H +#define OPENMW_ESM_CREATURESTATE_H + +#include "objectstate.hpp" +#include "inventorystate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct CreatureState : public ObjectState + { + InventoryState mInventory; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index bd86f9ba0..1b0125e78 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -36,6 +36,7 @@ struct Position enum RecNameInts { + // format 0 / legacy REC_ACTI = 0x49544341, REC_ALCH = 0x48434c41, REC_APPA = 0x41505041, @@ -80,7 +81,19 @@ enum RecNameInts REC_SPEL = 0x4c455053, REC_SSCR = 0x52435353, REC_STAT = 0x54415453, - REC_WEAP = 0x50414557 + REC_WEAP = 0x50414557, + + // format 0 - saved games + REC_SAVE = 0x45564153, + REC_JOUR = 0x524f55a4, + REC_QUES = 0x53455551, + REC_GSCR = 0x52435347, + REC_PLAY = 0x59414c50, + REC_CSTA = 0x41545343, + REC_GMAP = 0x50414d47, + + // format 1 + REC_FILT = 0x544C4946 }; } diff --git a/components/esm/effectlist.cpp b/components/esm/effectlist.cpp index 88f87d6e2..bc126846b 100644 --- a/components/esm/effectlist.cpp +++ b/components/esm/effectlist.cpp @@ -14,9 +14,9 @@ void EffectList::load(ESMReader &esm) } } -void EffectList::save(ESMWriter &esm) +void EffectList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) { + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNT("ENAM", *it, 24); } } diff --git a/components/esm/effectlist.hpp b/components/esm/effectlist.hpp index 9f5b87aed..04adcc5cd 100644 --- a/components/esm/effectlist.hpp +++ b/components/esm/effectlist.hpp @@ -35,9 +35,9 @@ namespace ESM std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; - + } #endif diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 51d86a2ee..ebdb1e41f 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -8,6 +8,11 @@ namespace ESM using namespace Misc; + std::string ESMReader::getName() const + { + return mCtx.filename; + } + ESM_Context ESMReader::getContext() { // Update the file position before returning @@ -302,8 +307,14 @@ std::string ESMReader::getString(int size) char *ptr = &mBuffer[0]; getExact(ptr, size); + if (size>0 && ptr[size-1]==0) + --size; + // Convert to UTF8 and return - return mEncoder->getUtf8(ptr, size); + if (mEncoder) + return mEncoder->getUtf8(ptr, size); + + return std::string (ptr, size); } void ESMReader::fail(const std::string &msg) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index edc724cd2..897c8fe73 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -34,10 +34,11 @@ public: float getFVer() const { if(mHeader.mData.version == VER_12) return 1.2; else return 1.3; } const std::string getAuthor() const { return mHeader.mData.author.toString(); } const std::string getDesc() const { return mHeader.mData.desc.toString(); } - const std::vector &getMasters() const { return mHeader.mMaster; } + const std::vector &getGameFiles() const { return mHeader.mMaster; } int getFormat() const; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } + std::string getName() const; /************************************************************************* * diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 3ea6bd350..f38591b7b 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -2,185 +2,202 @@ #include #include -#include - -bool count = true; +#include namespace ESM { + ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} -int ESMWriter::getVersion() -{ - return mHeader.mData.version; -} - -void ESMWriter::setVersion(int ver) -{ - mHeader.mData.version = ver; -} + unsigned int ESMWriter::getVersion() const + { + return mHeader.mData.version; + } -void ESMWriter::setAuthor(const std::string& auth) -{ - mHeader.mData.author.assign (auth); -} + void ESMWriter::setVersion(unsigned int ver) + { + mHeader.mData.version = ver; + } -void ESMWriter::setDescription(const std::string& desc) -{ - mHeader.mData.desc.assign (desc); -} + void ESMWriter::setAuthor(const std::string& auth) + { + mHeader.mData.author.assign (auth); + } -void ESMWriter::setRecordCount (int count) -{ - mHeader.mData.records = count; -} + void ESMWriter::setDescription(const std::string& desc) + { + mHeader.mData.desc.assign (desc); + } -void ESMWriter::setFormat (int format) -{ - mHeader.mFormat = format; -} + void ESMWriter::setRecordCount (int count) + { + mHeader.mData.records = count; + } -void ESMWriter::addMaster(const std::string& name, uint64_t size) -{ - Header::MasterData d; - d.name = name; - d.size = size; - mHeader.mMaster.push_back(d); -} + void ESMWriter::setFormat (int format) + { + mHeader.mFormat = format; + } -void ESMWriter::save(const std::string& file) -{ - std::ofstream fs(file.c_str(), std::ios_base::out | std::ios_base::trunc); - save(fs); -} + void ESMWriter::clearMaster() + { + mHeader.mMaster.clear(); + } -void ESMWriter::save(std::ostream& file) -{ - m_recordCount = 0; - m_stream = &file; + void ESMWriter::addMaster(const std::string& name, uint64_t size) + { + Header::MasterData d; + d.name = name; + d.size = size; + mHeader.mMaster.push_back(d); + } - startRecord("TES3", 0); + void ESMWriter::save(std::ostream& file) + { + mRecordCount = 0; + mRecords.clear(); + mCounting = true; + mStream = &file; - mHeader.save (*this); + startRecord("TES3", 0); - endRecord("TES3"); -} + mHeader.save (*this); -void ESMWriter::close() -{ - m_stream->flush(); + endRecord("TES3"); + } - if (!m_records.empty()) - throw "Unclosed record remaining"; -} + void ESMWriter::close() + { + if (!mRecords.empty()) + throw std::runtime_error ("Unclosed record remaining"); + } -void ESMWriter::startRecord(const std::string& name, uint32_t flags) -{ - m_recordCount++; - - writeName(name); - RecordData rec; - rec.name = name; - rec.position = m_stream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? - writeT(flags); - m_records.push_back(rec); - - assert(m_records.back().size == 0); -} + void ESMWriter::startRecord(const std::string& name, uint32_t flags) + { + mRecordCount++; + + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + writeT(0); // Unused header? + writeT(flags); + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } -void ESMWriter::startSubRecord(const std::string& name) -{ - writeName(name); - RecordData rec; - rec.name = name; - rec.position = m_stream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - m_records.push_back(rec); - - assert(m_records.back().size == 0); -} + void ESMWriter::startRecord (uint32_t name, uint32_t flags) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; -void ESMWriter::endRecord(const std::string& name) -{ - RecordData rec = m_records.back(); - assert(rec.name == name); - m_records.pop_back(); + startRecord (type, flags); + } - m_stream->seekp(rec.position); + void ESMWriter::startSubRecord(const std::string& name) + { + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } - count = false; - write((char*)&rec.size, sizeof(int)); - count = true; + void ESMWriter::endRecord(const std::string& name) + { + RecordData rec = mRecords.back(); + assert(rec.name == name); + mRecords.pop_back(); - m_stream->seekp(0, std::ios::end); + mStream->seekp(rec.position); -} + mCounting = false; + write (reinterpret_cast (&rec.size), sizeof(int)); + mCounting = true; -void ESMWriter::writeHNString(const std::string& name, const std::string& data) -{ - startSubRecord(name); - writeHString(data); - endRecord(name); -} + mStream->seekp(0, std::ios::end); -void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) -{ - assert(data.size() <= size); - startSubRecord(name); - writeHString(data); + } - if (data.size() < size) + void ESMWriter::endRecord (uint32_t name) { - for (size_t i = data.size(); i < size; ++i) - write("\0",1); + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + endRecord (type); } - endRecord(name); -} + void ESMWriter::writeHNString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHString(data); + endRecord(name); + } -void ESMWriter::writeHString(const std::string& data) -{ - if (data.size() == 0) - write("\0", 1); - else + void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) { - // Convert to UTF8 and return - std::string ascii = m_encoder->getLegacyEnc(data); + assert(data.size() <= size); + startSubRecord(name); + writeHString(data); + + if (data.size() < size) + { + for (size_t i = data.size(); i < size; ++i) + write("\0",1); + } - write(ascii.c_str(), ascii.size()); + endRecord(name); } -} -void ESMWriter::writeHCString(const std::string& data) -{ - writeHString(data); - if (data.size() > 0 && data[data.size()-1] != '\0') - write("\0", 1); -} + void ESMWriter::writeHString(const std::string& data) + { + if (data.size() == 0) + write("\0", 1); + else + { + // Convert to UTF8 and return + std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + + write(string.c_str(), string.size()); + } + } -void ESMWriter::writeName(const std::string& name) -{ - assert((name.size() == 4 && name[3] != '\0')); - write(name.c_str(), name.size()); -} + void ESMWriter::writeHCString(const std::string& data) + { + writeHString(data); + if (data.size() > 0 && data[data.size()-1] != '\0') + write("\0", 1); + } -void ESMWriter::write(const char* data, size_t size) -{ - if (count && !m_records.empty()) + void ESMWriter::writeName(const std::string& name) { - for (std::list::iterator it = m_records.begin(); it != m_records.end(); ++it) - it->size += size; + assert((name.size() == 4 && name[3] != '\0')); + write(name.c_str(), name.size()); } - m_stream->write(data, size); -} + void ESMWriter::write(const char* data, size_t size) + { + if (mCounting && !mRecords.empty()) + { + for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) + it->size += size; + } -void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) -{ - m_encoder = encoder; -} + mStream->write(data, size); + } + void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) + { + mEncoder = encoder; + } } diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index be3ae33ab..94f0a1004 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -13,92 +13,102 @@ namespace ESM { class ESMWriter { - struct RecordData - { - std::string name; - std::streampos position; - size_t size; + struct RecordData + { + std::string name; + std::streampos position; + size_t size; + }; + + public: + + ESMWriter(); + + unsigned int getVersion() const; + void setVersion(unsigned int ver = 0x3fa66666); + void setEncoder(ToUTF8::Utf8Encoder *encoding); + void setAuthor(const std::string& author); + void setDescription(const std::string& desc); + void setRecordCount (int count); + void setFormat (int format); + + void clearMaster(); + + void addMaster(const std::string& name, uint64_t size); + + void save(std::ostream& file); + ///< Start saving a file by writing the TES3 header. + + void close(); + ///< \note Does not close the stream. + + void writeHNString(const std::string& name, const std::string& data); + 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); + writeHCString(data); + endRecord(name); + } + void writeHNOString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNString(name, data); + } + void writeHNOCString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNCString(name, data); + } + + template + void writeHNT(const std::string& name, const T& data) + { + startSubRecord(name); + writeT(data); + endRecord(name); + } + + template + void writeHNT(const std::string& name, const T& data, int size) + { + startSubRecord(name); + writeT(data, size); + endRecord(name); + } + + template + void writeT(const T& data) + { + write((char*)&data, sizeof(T)); + } + + template + void writeT(const T& data, size_t size) + { + write((char*)&data, size); + } + + void startRecord(const std::string& name, uint32_t flags = 0); + void startRecord(uint32_t name, uint32_t flags = 0); + void startSubRecord(const std::string& name); + void endRecord(const std::string& name); + void endRecord(uint32_t name); + void writeHString(const std::string& data); + void writeHCString(const std::string& data); + void writeName(const std::string& data); + void write(const char* data, size_t size); + + private: + std::list mRecords; + std::ostream* mStream; + std::streampos mHeaderPos; + ToUTF8::Utf8Encoder* mEncoder; + int mRecordCount; + bool mCounting; + + Header mHeader; }; - -public: - int getVersion(); - void setVersion(int ver); - void setEncoder(ToUTF8::Utf8Encoder *encoding); // Write strings as UTF-8? - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); - void setRecordCount (int count); - void setFormat (int format); - - void addMaster(const std::string& name, uint64_t size); - - void save(const std::string& file); - void save(std::ostream& file); - void close(); - - void writeHNString(const std::string& name, const std::string& data); - 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); - writeHCString(data); - endRecord(name); - } - void writeHNOString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNString(name, data); - } - void writeHNOCString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNCString(name, data); - } - - template - void writeHNT(const std::string& name, const T& data) - { - startSubRecord(name); - writeT(data); - endRecord(name); - } - - template - void writeHNT(const std::string& name, const T& data, int size) - { - startSubRecord(name); - writeT(data, size); - endRecord(name); - } - - template - void writeT(const T& data) - { - write((char*)&data, sizeof(T)); - } - - template - void writeT(const T& data, size_t size) - { - write((char*)&data, size); - } - - void startRecord(const std::string& name, uint32_t flags); - void startSubRecord(const std::string& name); - void endRecord(const std::string& name); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); - void writeName(const std::string& data); - void write(const char* data, size_t size); - -private: - std::list m_records; - std::ostream* m_stream; - std::streampos m_headerPos; - ToUTF8::Utf8Encoder* m_encoder; - int m_recordCount; - - Header mHeader; -}; - } + #endif diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 7d4851a5f..a80427bbe 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -3,6 +3,9 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::Filter::sRecordId = REC_FILT; void ESM::Filter::load (ESMReader& esm) { @@ -10,7 +13,7 @@ void ESM::Filter::load (ESMReader& esm) mDescription = esm.getHNString ("DESC"); } -void ESM::Filter::save (ESMWriter& esm) +void ESM::Filter::save (ESMWriter& esm) const { esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index 0fd564361..bc3dd7bdc 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -10,6 +10,8 @@ namespace ESM struct Filter { + static unsigned int sRecordId; + std::string mId; std::string mDescription; @@ -17,7 +19,7 @@ namespace ESM std::string mFilter; void load (ESMReader& esm); - void save (ESMWriter& esm); + void save (ESMWriter& esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/globalmap.cpp b/components/esm/globalmap.cpp new file mode 100644 index 000000000..1fa5f907e --- /dev/null +++ b/components/esm/globalmap.cpp @@ -0,0 +1,26 @@ +#include "globalmap.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP; + +void ESM::GlobalMap::load (ESMReader &esm) +{ + esm.getHNT(mBounds, "BNDS"); + + esm.getSubNameIs("DATA"); + esm.getSubHeader(); + mImageData.resize(esm.getSubSize()); + esm.getExact(&mImageData[0], mImageData.size()); +} + +void ESM::GlobalMap::save (ESMWriter &esm) const +{ + esm.writeHNT("BNDS", mBounds); + + esm.startSubRecord("DATA"); + esm.write(&mImageData[0], mImageData.size()); + esm.endRecord("DATA"); +} diff --git a/components/esm/globalmap.hpp b/components/esm/globalmap.hpp new file mode 100644 index 000000000..5d036c736 --- /dev/null +++ b/components/esm/globalmap.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_ESM_GLOBALMAP_H +#define OPENMW_COMPONENTS_ESM_GLOBALMAP_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + ///< \brief An image containing the explored areas on the global map. + struct GlobalMap + { + static unsigned int sRecordId; + + // The minimum and maximum cell coordinates + struct Bounds + { + int mMinX, mMaxX, mMinY, mMaxY; + }; + + Bounds mBounds; + + std::vector mImageData; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp new file mode 100644 index 000000000..dcbd91140 --- /dev/null +++ b/components/esm/globalscript.cpp @@ -0,0 +1,25 @@ + +#include "globalscript.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::GlobalScript::load (ESMReader &esm) +{ + mId = esm.getHNString ("NAME"); + + mLocals.load (esm); + + mRunning = 0; + esm.getHNOT (mRunning, "RUN_"); +} + +void ESM::GlobalScript::save (ESMWriter &esm) const +{ + esm.writeHNString ("NAME", mId); + + mLocals.save (esm); + + if (mRunning) + esm.writeHNT ("RUN_", mRunning); +} \ No newline at end of file diff --git a/components/esm/globalscript.hpp b/components/esm/globalscript.hpp new file mode 100644 index 000000000..4fb8b7c48 --- /dev/null +++ b/components/esm/globalscript.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_ESM_GLOBALSCRIPT_H +#define OPENMW_ESM_GLOBALSCRIPT_H + +#include "locals.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + /// \brief Storage structure for global script state (only used in saved games) + + struct GlobalScript + { + std::string mId; + Locals mLocals; + int mRunning; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp new file mode 100644 index 000000000..4d8cbc622 --- /dev/null +++ b/components/esm/inventorystate.cpp @@ -0,0 +1,60 @@ + +#include "inventorystate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace +{ + void read (ESM::ESMReader &esm, ESM::ObjectState& state, int& slot) + { + slot = -1; + esm.getHNOT (slot, "SLOT"); + + state.load (esm); + } + + void write (ESM::ESMWriter &esm, const ESM::ObjectState& state, unsigned int type, int slot) + { + esm.writeHNT ("IOBJ", type); + + if (slot!=-1) + esm.writeHNT ("SLOT", slot); + + state.save (esm, true); + } +} + +void ESM::InventoryState::load (ESMReader &esm) +{ + while (esm.isNextSub ("IOBJ")) + { + unsigned int id = 0; + esm.getHT (id); + + if (id==ESM::REC_LIGH) + { + LightState state; + int slot; + read (esm, state, slot); + mLights.push_back (std::make_pair (state, slot)); + } + else + { + ObjectState state; + int slot; + read (esm, state, slot); + mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); + } + } +} + +void ESM::InventoryState::save (ESMWriter &esm) const +{ + for (std::vector > >::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter) + write (esm, iter->first, iter->second.first, iter->second.second); + + for (std::vector >::const_iterator iter (mLights.begin()); + iter!=mLights.end(); ++iter) + write (esm, iter->first, ESM::REC_LIGH, iter->second); +} \ No newline at end of file diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp new file mode 100644 index 000000000..3cfffbccc --- /dev/null +++ b/components/esm/inventorystate.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESM_INVENTORYSTATE_H +#define OPENMW_ESM_INVENTORYSTATE_H + +#include "objectstate.hpp" +#include "lightstate.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + /// \brief State for inventories and containers + struct InventoryState + { + // anything but lights (type, slot) + std::vector > > mItems; + + // lights (slot) + std::vector > mLights; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp new file mode 100644 index 000000000..445213de4 --- /dev/null +++ b/components/esm/journalentry.cpp @@ -0,0 +1,39 @@ + +#include "journalentry.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::JournalEntry::load (ESMReader &esm) +{ + esm.getHNOT (mType, "JETY"); + mTopic = esm.getHNString ("YETO"); + mInfo = esm.getHNString ("YEIN"); + mText = esm.getHNString ("TEXT"); + + if (mType==Type_Journal) + { + esm.getHNT (mDay, "JEDA"); + esm.getHNT (mMonth, "JEMO"); + esm.getHNT (mDayOfMonth, "JEDM"); + } + else if (mType==Type_Topic) + mActorName = esm.getHNOString("ACT_"); +} + +void ESM::JournalEntry::save (ESMWriter &esm) const +{ + esm.writeHNT ("JETY", mType); + esm.writeHNString ("YETO", mTopic); + esm.writeHNString ("YEIN", mInfo); + esm.writeHNString ("TEXT", mText); + + if (mType==Type_Journal) + { + esm.writeHNT ("JEDA", mDay); + esm.writeHNT ("JEMO", mMonth); + esm.writeHNT ("JEDM", mDayOfMonth); + } + else if (mType==Type_Topic) + esm.writeHNString ("ACT_", mActorName); +} diff --git a/components/esm/journalentry.hpp b/components/esm/journalentry.hpp new file mode 100644 index 000000000..76901a4b6 --- /dev/null +++ b/components/esm/journalentry.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_ESM_JOURNALENTRY_H +#define OPENMW_ESM_JOURNALENTRY_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct JournalEntry + { + enum Type + { + Type_Journal = 0, + Type_Topic = 1, + Type_Quest = 2 + }; + + int mType; + std::string mTopic; + std::string mInfo; + std::string mText; + std::string mActorName; // Could also be Actor ID to allow switching of localisation, but since mText is plaintext anyway... + int mDay; // time stamp + int mMonth; + int mDayOfMonth; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/lightstate.cpp b/components/esm/lightstate.cpp new file mode 100644 index 000000000..1ef040823 --- /dev/null +++ b/components/esm/lightstate.cpp @@ -0,0 +1,21 @@ + +#include "lightstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::LightState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mTime = 0; + esm.getHNOT (mTime, "LTIM"); +} + +void ESM::LightState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + if (mTime) + esm.writeHNT ("LTIM", mTime); +} \ No newline at end of file diff --git a/components/esm/lightstate.hpp b/components/esm/lightstate.hpp new file mode 100644 index 000000000..a22735e07 --- /dev/null +++ b/components/esm/lightstate.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_ESM_LIGHTSTATE_H +#define OPENMW_ESM_LIGHTSTATE_H + +#include "objectstate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct LightState : public ObjectState + { + float mTime; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index fd022af7e..6ba0df0b3 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -2,16 +2,19 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Activator::sRecordId = REC_ACTI; + void Activator::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); mName = esm.getHNString("FNAM"); mScript = esm.getHNOString("SCRI"); } -void Activator::save(ESMWriter &esm) +void Activator::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index a62990590..88f27de27 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -11,10 +11,12 @@ class ESMWriter; struct Activator { + static unsigned int sRecordId; + std::string mId, mName, mScript, mModel; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index dbb69c066..f6bfc6a11 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Potion::sRecordId = REC_ALCH; + void Potion::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); @@ -14,7 +17,7 @@ void Potion::load(ESMReader &esm) esm.getHNT(mData, "ALDT", 12); mEffects.load(esm); } -void Potion::save(ESMWriter &esm) +void Potion::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index 3ede85342..141765aa8 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Potion { + static unsigned int sRecordId; + struct ALDTstruct { float mWeight; @@ -29,7 +31,7 @@ struct Potion EffectList mEffects; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 4b8d2b763..29ea78acc 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Apparatus::sRecordId = REC_APPA; + void Apparatus::load(ESMReader &esm) { // we will not treat duplicated subrecords as errors here @@ -28,7 +31,7 @@ void Apparatus::load(ESMReader &esm) } } -void Apparatus::save(ESMWriter &esm) +void Apparatus::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index ed9d335be..adc8e071f 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Apparatus { + static unsigned int sRecordId; + enum AppaType { MortarPestle = 0, @@ -35,7 +37,7 @@ struct Apparatus std::string mId, mModel, mIcon, mScript, mName; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index e64c8705d..ec8ff4f20 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -18,9 +19,9 @@ void PartReferenceList::load(ESMReader &esm) } } -void PartReferenceList::save(ESMWriter &esm) +void PartReferenceList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mParts.begin(); it != mParts.end(); ++it) + for (std::vector::const_iterator it = mParts.begin(); it != mParts.end(); ++it) { esm.writeHNT("INDX", it->mPart); esm.writeHNOString("BNAM", it->mMale); @@ -28,6 +29,8 @@ void PartReferenceList::save(ESMWriter &esm) } } +unsigned int Armor::sRecordId = REC_ARMO; + void Armor::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); @@ -39,7 +42,7 @@ void Armor::load(ESMReader &esm) mEnchant = esm.getHNOString("ENAM"); } -void Armor::save(ESMWriter &esm) +void Armor::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index eaef42be8..991f4e185 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -56,11 +56,13 @@ struct PartReferenceList std::vector mParts; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; struct Armor { + static unsigned int sRecordId; + enum Type { Helmet = 0, @@ -89,7 +91,7 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index e95a8a860..4015e6c91 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int BodyPart::sRecordId = REC_BODY; + void BodyPart::load(ESMReader &esm) { @@ -12,7 +15,7 @@ void BodyPart::load(ESMReader &esm) mRace = esm.getHNString("FNAM"); esm.getHNT(mData, "BYDT", 4); } -void BodyPart::save(ESMWriter &esm) +void BodyPart::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mRace); diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index 3ad9b1b95..9623caa31 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct BodyPart { + static unsigned int sRecordId; + enum MeshPart { MP_Head = 0, @@ -57,7 +59,7 @@ struct BodyPart std::string mId, mModel, mRace; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 3a70ac786..c8b7e9478 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Book::sRecordId = REC_BOOK; void Book::load(ESMReader &esm) { @@ -16,7 +18,7 @@ void Book::load(ESMReader &esm) mText = esm.getHNOString("TEXT"); mEnchant = esm.getHNOString("ENAM"); } -void Book::save(ESMWriter &esm) +void Book::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 68042e246..f96fbd709 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -14,6 +14,8 @@ class ESMWriter; struct Book { + static unsigned int sRecordId; + struct BKDTstruct { float mWeight; @@ -25,7 +27,7 @@ struct Book std::string mId; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index cb500f674..55e1e7f65 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int BirthSign::sRecordId = REC_BSGN; void BirthSign::load(ESMReader &esm) { @@ -15,7 +17,7 @@ void BirthSign::load(ESMReader &esm) mPowers.load(esm); } -void BirthSign::save(ESMWriter &esm) +void BirthSign::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 434ddf68e..9f9435c8f 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -13,13 +13,15 @@ class ESMWriter; struct BirthSign { + static unsigned int sRecordId; + std::string mId, mName, mDescription, mTexture; // List of powers and abilities that come with this birth sign. SpellList mPowers; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index d8d0c1291..dd7bf3e42 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -3,24 +3,52 @@ #include #include #include + #include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" +#include "cellid.hpp" -namespace ESM +namespace { + ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum + void adjustRefNum (ESM::CellRef::RefNum& refNum, ESM::ESMReader& reader) + { + int local = (refNum.mIndex & 0xff000000) >> 24; -/// Some overloaded compare operators. -bool operator==(const MovedCellRef& ref, int pRefnum) -{ - return (ref.mRefnum == pRefnum); + 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. + refNum.mIndex &= 0x00ffffff; // delete old plugin ID + refNum.mContentFile = reader.getGameFiles()[local-1].index; + } + else + { + // This is an addition by the present plugin. Set the corresponding plugin index. + refNum.mContentFile = reader.getIndex(); + } + } } -bool operator==(const CellRef& ref, int pRefnum) +namespace ESM { - return (ref.mRefnum == pRefnum); -} + unsigned int Cell::sRecordId = REC_CELL; + + // Some overloaded compare operators. + bool operator== (const MovedCellRef& ref, const CellRef::RefNum& refNum) + { + return ref.mRefNum == refNum; + } + + bool operator== (const CellRef& ref, const CellRef::RefNum& refNum) + { + return ref.mRefNum == refNum; + } void Cell::load(ESMReader &esm, bool saveContext) @@ -34,8 +62,6 @@ void Cell::load(ESMReader &esm, bool saveContext) esm.getHNT(mData, "DATA", 12); - // Water level - mWater = -1; mNAM0 = 0; if (mData.mFlags & Interior) @@ -47,9 +73,13 @@ void Cell::load(ESMReader &esm, bool saveContext) esm.getHT(waterl); mWater = (float) waterl; mWaterInt = true; + mHasWaterLevelRecord = true; } else if (esm.isNextSub("WHGT")) + { esm.getHT(mWater); + mHasWaterLevelRecord = true; + } // Quasi-exterior cells have a region (which determines the // weather), pure interior cells have ambient lighting @@ -89,12 +119,12 @@ void Cell::postLoad(ESMReader &esm) esm.skipRecord(); } -void Cell::save(ESMWriter &esm) +void Cell::save(ESMWriter &esm) const { esm.writeHNT("DATA", mData, 12); if (mData.mFlags & Interior) { - if (mWater != -1) { + if (mHasWaterLevelRecord) { if (mWaterInt) { int water = (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); @@ -139,7 +169,7 @@ std::string Cell::getDescription() const } } -bool Cell::getNextRef(ESMReader &esm, CellRef &ref) +bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted) { // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) @@ -153,132 +183,28 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) // That should be it, I haven't seen any other fields yet. } - // NAM0 sometimes appears here, sometimes further on - ref.mNam0 = 0; - if (esm.isNextSub("NAM0")) - { - esm.getHT(ref.mNam0); - //esm.getHNOT(NAM0, "NAM0"); - } - - esm.getHNT(ref.mRefnum, "FRMR"); - ref.mRefID = esm.getHNString("NAME"); + ref.load (esm); // 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 std::vector &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"); - - ref.mFaction = esm.getHNOString("CNAM"); - ref.mFactIndex = -2; - esm.getHNOT(ref.mFactIndex, "INDX"); - - ref.mGoldValue = 1; - ref.mCharge = -1; - ref.mEnchantmentCharge = -1; - - esm.getHNOT(ref.mEnchantmentCharge, "XCHG"); - - esm.getHNOT(ref.mCharge, "INTV"); - - esm.getHNOT(ref.mGoldValue, "NAM9"); + adjustRefNum (ref.mRefNum, esm); - // Present for doors that teleport you to another cell. - if (esm.isNextSub("DODT")) + if (esm.isNextSub("DELE")) { - ref.mTeleport = true; - esm.getHT(ref.mDoorDest); - ref.mDestCell = esm.getHNOString("DNAM"); - } else { - ref.mTeleport = false; - } - - // Integer, despite the name suggesting otherwise - ref.mLockLevel = -1; - esm.getHNOT(ref.mLockLevel, "FLTV"); - ref.mKey = esm.getHNOString("KNAM"); - ref.mTrap = esm.getHNOString("TNAM"); - - ref.mReferenceBlocked = -1; - ref.mFltv = 0; - esm.getHNOT(ref.mReferenceBlocked, "UNAM"); - esm.getHNOT(ref.mFltv, "FLTV"); - - 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. - if (esm.isNextSub("NAM0")) - { - esm.getHT(ref.mNam0); - //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; + deleted = true; + } + else + deleted = false; return true; } bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) { - esm.getHT(mref.mRefnum); + esm.getHT(mref.mRefNum.mIndex); 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 std::vector &masters = esm.getMasters(); - global = masters[local-1].index + 1; - mref.mRefnum |= global << 24; // insert global plugin ID + adjustRefNum (mref.mRefNum, esm); return true; } @@ -301,4 +227,37 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mAmbi.mFog = 0; mAmbi.mFogDensity = 0; } + + void Cell::merge(Cell *original, Cell *modified) + { + float waterLevel = original->mWater; + if (modified->mHasWaterLevelRecord) + { + waterLevel = modified->mWater; + } + // else: keep original water level, instead of resetting to 0 + + *original = *modified; + original->mWater = waterLevel; + } + + CellId Cell::getCellId() const + { + CellId id; + + id.mPaged = !(mData.mFlags & Interior); + + if (id.mPaged) + { + id.mWorldspace = "default"; + id.mIndex.mX = mData.mX; + id.mIndex.mY = mData.mY; + } + else + { + id.mWorldspace = Misc::StringUtils::lowerCase (mName); + } + + return id; + } } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 51288b291..f01c88c65 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -16,9 +16,9 @@ namespace MWWorld namespace ESM { - class ESMReader; class ESMWriter; + class CellId; /* 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 @@ -28,7 +28,7 @@ class ESMWriter; class MovedCellRef { public: - int mRefnum; + CellRef::RefNum mRefNum; // Target cell (if exterior) int mTarget[2]; @@ -38,9 +38,9 @@ public: // 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); +/// Overloaded compare operator used to search inside a list of cell refs. +bool operator==(const MovedCellRef& ref, const CellRef::RefNum& refNum); +bool operator==(const CellRef& ref, const CellRef::RefNum& refNum); typedef std::list MovedCellRefTracker; typedef std::list CellRefTracker; @@ -55,6 +55,8 @@ typedef std::list CellRefTracker; */ struct Cell { + static unsigned int sRecordId; + enum Flags { Interior = 0x01, // Interior cell @@ -76,6 +78,11 @@ struct Cell float mFogDensity; }; + Cell() : mWater(0), mHasWaterLevelRecord(false) {} + + /// Merge \a modified into \a original + static void merge (Cell* original, Cell* modified); + // Interior cells are indexed by this (it's the 'id'), for exterior // cells it is optional. std::string mName; @@ -87,6 +94,7 @@ struct Cell DATAstruct mData; AMBIstruct mAmbi; float mWater; // Water level + bool mHasWaterLevelRecord; bool mWaterInt; int mMapColor; int mNAM0; @@ -102,7 +110,7 @@ struct Cell // 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); + void save(ESMWriter &esm) const; bool isExterior() const { @@ -140,7 +148,7 @@ struct Cell All fields of the CellRef struct are overwritten. You can safely reuse one memory location without blanking it between calls. */ - static bool getNextRef(ESMReader &esm, CellRef &ref); + static bool getNextRef(ESMReader &esm, CellRef &ref, bool& deleted); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. @@ -149,6 +157,8 @@ struct Cell void blank(); ///< Set record to default state (does not touch the ID/index). + + CellId getCellId() const; }; } #endif diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index bdc461462..33489eec4 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -4,9 +4,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Class::sRecordId = REC_CLAS; const Class::Specialization Class::sSpecializationIds[3] = { Class::Combat, @@ -47,7 +49,7 @@ void Class::load(ESMReader &esm) mDescription = esm.getHNOString("DESC"); } -void Class::save(ESMWriter &esm) +void Class::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 4f85e6ee8..3e489bb58 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -17,6 +17,8 @@ class ESMWriter; // class struct Class { + static unsigned int sRecordId; + enum AutoCalc { Weapon = 0x00001, @@ -70,7 +72,7 @@ struct Class CLDTstruct mData; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 10b00970f..d64564d77 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Clothing::sRecordId = REC_CLOT; void Clothing::load(ESMReader &esm) { @@ -20,7 +22,7 @@ void Clothing::load(ESMReader &esm) mEnchant = esm.getHNOString("ENAM"); } -void Clothing::save(ESMWriter &esm) +void Clothing::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 816d03cb2..50896622a 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Clothing { + static unsigned int sRecordId; + enum Type { Pants = 0, @@ -45,7 +47,7 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 853c8bd50..7bdf9f05b 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -16,14 +17,16 @@ void InventoryList::load(ESMReader &esm) } } -void InventoryList::save(ESMWriter &esm) +void InventoryList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNT("NPCO", *it, 36); } } + unsigned int Container::sRecordId = REC_CONT; + void Container::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); @@ -41,7 +44,7 @@ void Container::load(ESMReader &esm) mInventory.load(esm); } -void Container::save(ESMWriter &esm) +void Container::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index b2bbab73d..2808b67b5 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -27,11 +27,13 @@ struct InventoryList std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; struct Container { + static unsigned int sRecordId; + enum Flags { Organic = 1, // Objects cannot be placed in this container @@ -46,7 +48,7 @@ struct Container InventoryList mInventory; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 86d05b8a5..650de0801 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Creature::sRecordId = REC_CREA; + void Creature::load(ESMReader &esm) { mPersistent = esm.getRecordFlags() & 0x0400; @@ -35,7 +38,7 @@ void Creature::load(ESMReader &esm) esm.skipRecord(); } -void Creature::save(ESMWriter &esm) +void Creature::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 279e2ea3f..817c0e43c 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -20,25 +20,31 @@ class ESMWriter; struct Creature { + static unsigned int sRecordId; + // Default is 0x48? enum Flags { - Biped = 0x001, - Respawn = 0x002, - Weapon = 0x004, // Has weapon and shield - None = 0x008, // ?? + // Movement types + Bipedal = 0x001, Swims = 0x010, Flies = 0x020, // Don't know what happens if several Walks = 0x040, // of these are set + + Respawn = 0x002, + Weapon = 0x004, // Has weapon and shield + None = 0x008, // ?? Essential = 0x080, - Skeleton = 0x400, // Does not have normal blood - Metal = 0x800 // Has 'golden' blood + + // Blood types + Skeleton = 0x400, + Metal = 0x800 }; enum Type { Creatures = 0, - Deadra = 1, + Daedra = 1, Undead = 2, Humanoid = 3 }; @@ -61,10 +67,12 @@ struct Creature int mHealth, mMana, mFatigue; // Stats int mSoul; // The creatures soul value (used with soul gems.) - int mCombat, mMagic, mStealth; // Don't know yet. + // Creatures have generalized combat, magic and stealth stats which substitute for + // the specific skills (in the same way as specializations). + int mCombat, mMagic, mStealth; int mAttack[6]; // AttackMin1, AttackMax1, ditto2, ditto3 int mGold; - }; // 96 bytes + }; // 96 byte NPDTstruct mData; @@ -86,7 +94,7 @@ struct Creature AIPackageList mAiPackage; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrec.hpp b/components/esm/loadcrec.hpp index 6904df15a..280739aca 100644 --- a/components/esm/loadcrec.hpp +++ b/components/esm/loadcrec.hpp @@ -17,6 +17,8 @@ class ESMWriter; /// Changes a creature struct LoadCREC { + static unsigned int sRecordId; + std::string mId; void load(ESMReader &esm) @@ -24,7 +26,7 @@ struct LoadCREC esm.skipRecord(); } - void save(ESMWriter &esm) + void save(ESMWriter &esm) const { } }; @@ -39,7 +41,7 @@ struct LoadCNTC esm.skipRecord(); } - void save(ESMWriter &esm) + void save(ESMWriter &esm) const { } }; diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index fb50d5e9f..fb43ee858 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Dialogue::sRecordId = REC_DIAL; void Dialogue::load(ESMReader &esm) { @@ -25,7 +27,7 @@ void Dialogue::load(ESMReader &esm) esm.fail("Unknown sub record size"); } -void Dialogue::save(ESMWriter &esm) +void Dialogue::save(ESMWriter &esm) const { if (mType != Deleted) esm.writeHNT("DATA", mType); @@ -36,4 +38,9 @@ void Dialogue::save(ESMWriter &esm) } } + void Dialogue::blank() + { + mInfo.clear(); + } + } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 61f3f763d..63d78833e 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct Dialogue { + static unsigned int sRecordId; + enum Type { Topic = 0, @@ -34,7 +36,10 @@ struct Dialogue std::vector mInfo; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; + + void blank(); + ///< Set record to default state (does not touch the ID and does not change the type). }; } #endif diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index a4c7b7d58..c56b06337 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Door::sRecordId = REC_DOOR; void Door::load(ESMReader &esm) { @@ -15,7 +17,7 @@ void Door::load(ESMReader &esm) mCloseSound = esm.getHNOString("ANAM"); } -void Door::save(ESMWriter &esm) +void Door::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 77ffc6489..ee2b7f7ac 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -11,10 +11,12 @@ class ESMWriter; struct Door { + static unsigned int sRecordId; + std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index c4e278368..a1e885f23 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Enchantment::sRecordId = REC_ENCH; void Enchantment::load(ESMReader &esm) { @@ -12,7 +14,7 @@ void Enchantment::load(ESMReader &esm) mEffects.load(esm); } -void Enchantment::save(ESMWriter &esm) +void Enchantment::save(ESMWriter &esm) const { esm.writeHNT("ENDT", mData, 16); mEffects.save(esm); diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index 999f93ad9..f6ba8c6ab 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Enchantment { + static unsigned int sRecordId; + enum Type { CastOnce = 0, @@ -39,7 +41,7 @@ struct Enchantment EffectList mEffects; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index e2712d462..61fa90263 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -4,9 +4,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Faction::sRecordId = REC_FACT; + int& Faction::FADTstruct::getSkill (int index, bool ignored) { if (index<0 || index>=6) @@ -47,7 +50,7 @@ void Faction::load(ESMReader &esm) mReactions.push_back(r); } } -void Faction::save(ESMWriter &esm) +void Faction::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); @@ -61,7 +64,7 @@ void Faction::save(ESMWriter &esm) esm.writeHNT("FADT", mData, 240); - for (std::vector::iterator it = mReactions.begin(); it != mReactions.end(); ++it) + for (std::vector::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) { esm.writeHNString("ANAM", it->mFaction); esm.writeHNT("INTV", it->mReaction); diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 891b99647..9c257e068 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -29,6 +29,8 @@ struct RankData struct Faction { + static unsigned int sRecordId; + std::string mId, mName; struct FADTstruct @@ -63,7 +65,7 @@ struct Faction std::string mRanks[10]; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index ccb519acd..a78ed1a1b 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,13 +1,17 @@ #include "loadglob.hpp" +#include "defs.hpp" + namespace ESM { + unsigned int Global::sRecordId = REC_GLOB; + void Global::load (ESMReader &esm) { mValue.read (esm, ESM::Variant::Format_Global); } - void Global::save (ESMWriter &esm) + void Global::save (ESMWriter &esm) const { mValue.write (esm, ESM::Variant::Format_Global); } diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 72e16c0ce..51b2e2dc9 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -17,11 +17,13 @@ class ESMWriter; struct Global { + static unsigned int sRecordId; + std::string mId; Variant mValue; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index fe1cc1b04..21d66339a 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,13 +1,17 @@ #include "loadgmst.hpp" +#include "defs.hpp" + namespace ESM { + unsigned int GameSetting::sRecordId = REC_GMST; + void GameSetting::load (ESMReader &esm) { mValue.read (esm, ESM::Variant::Format_Gmst); } - void GameSetting::save (ESMWriter &esm) + void GameSetting::save (ESMWriter &esm) const { mValue.write (esm, ESM::Variant::Format_Gmst); } diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index a6e0c2ecb..6b66ac832 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -18,13 +18,15 @@ class ESMWriter; struct GameSetting { + static unsigned int sRecordId; + std::string mId; Variant mValue; void load(ESMReader &esm); - /// \todo remove the get* functions (redundant, since mValue as equivalent functions now). + /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). int getInt() const; ///< Throws an exception if GMST is not of type int or float. @@ -35,7 +37,7 @@ struct GameSetting std::string getString() const; ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 90f8fcf35..f86ad3b51 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -2,13 +2,14 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int DialInfo::sRecordId = REC_INFO; void DialInfo::load(ESMReader &esm) { - mId = esm.getHNString("INAM"); mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -48,8 +49,8 @@ void DialInfo::load(ESMReader &esm) mFactionLess = false; if (subName.val == REC_FNAM) { - mNpcFaction = esm.getHString(); - if (mNpcFaction == "FFFF") + mFaction = esm.getHString(); + if (mFaction == "FFFF") mFactionLess = true; if (esm.isEmptyOrGetName()) return; @@ -120,22 +121,21 @@ void DialInfo::load(ESMReader &esm) esm.skipRecord(); } -void DialInfo::save(ESMWriter &esm) +void DialInfo::save(ESMWriter &esm) const { - esm.writeHNCString("INAM", mId); esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); esm.writeHNT("DATA", mData, 12); esm.writeHNOCString("ONAM", mActor); esm.writeHNOCString("RNAM", mRace); esm.writeHNOCString("CNAM", mClass); - esm.writeHNOCString("FNAM", mNpcFaction); + esm.writeHNOCString("FNAM", mFaction); esm.writeHNOCString("ANAM", mCell); esm.writeHNOCString("DNAM", mPcFaction); esm.writeHNOCString("SNAM", mSound); esm.writeHNOString("NAME", mResponse); - for (std::vector::iterator it = mSelects.begin(); it != mSelects.end(); ++it) + for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) { esm.writeHNString("SCVR", it->mSelectRule); it->mValue.write (esm, Variant::Format_Info); @@ -153,4 +153,28 @@ void DialInfo::save(ESMWriter &esm) } } + void DialInfo::blank() + { + mData.mUnknown1 = 0; + mData.mDisposition = 0; + mData.mRank = 0; + mData.mGender = 0; + mData.mPCrank = 0; + mData.mUnknown2 = 0; + + mSelects.clear(); + mPrev.clear(); + mNext.clear(); + mActor.clear(); + mRace.clear(); + mClass.clear(); + mFaction.clear(); + mPcFaction.clear(); + mCell.clear(); + mSound.clear(); + mResponse.clear(); + mResultScript.clear(); + mFactionLess = false; + mQuestStatus = QS_None; + } } diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 2361ed9eb..0c0d662a8 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -20,6 +20,8 @@ class ESMWriter; struct DialInfo { + static unsigned int sRecordId; + enum Gender { Male = 0, @@ -48,10 +50,10 @@ struct DialInfo // Journal quest indices (introduced with the quest system in Tribunal) enum QuestStatus { - QS_None, - QS_Name, - QS_Finished, - QS_Restart, + QS_None = 0, + QS_Name = 1, + QS_Finished = 2, + QS_Restart = 3, QS_Deleted }; @@ -63,12 +65,12 @@ struct DialInfo std::string mId, mPrev, mNext; // Various references used in determining when to select this item. - std::string mActor, mRace, mClass, mNpcFaction, mPcFaction, mCell; + std::string mActor, mRace, mClass, mFaction, mPcFaction, mCell; // Sound and text associated with this item std::string mSound, mResponse; - // Result script (uncomiled) to run whenever this dialog item is + // Result script (uncompiled) to run whenever this dialog item is // selected std::string mResultScript; @@ -99,7 +101,10 @@ struct DialInfo }; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; + + void blank(); + ///< Set record to default state (does not touch the ID). }; } diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 7e31a4116..0e0243362 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Ingredient::sRecordId = REC_INGR; void Ingredient::load(ESMReader &esm) { @@ -37,7 +39,7 @@ void Ingredient::load(ESMReader &esm) } } -void Ingredient::save(ESMWriter &esm) +void Ingredient::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 5e286535f..85f2d5e7d 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Ingredient { + static unsigned int sRecordId; + struct IRDTstruct { float mWeight; @@ -28,7 +30,7 @@ struct Ingredient std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 60c475040..1b701229e 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Land::sRecordId = REC_LAND; void Land::LandData::save(ESMWriter &esm) { @@ -12,18 +14,18 @@ void Land::LandData::save(ESMWriter &esm) esm.writeHNT("VNML", mNormals, sizeof(VNML)); } if (mDataTypes & Land::DATA_VHGT) { - static VHGT offsets; + VHGT offsets; offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; offsets.mUnk1 = mUnk1; offsets.mUnk2 = mUnk2; - + float prevY = mHeights[0], prevX; int number = 0; // avoid multiplication for (int i = 0; i < LAND_SIZE; ++i) { float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - + prevX = prevY = mHeights[number]; ++number; @@ -132,7 +134,7 @@ void Land::load(ESMReader &esm) mLandData = NULL; } -void Land::save(ESMWriter &esm) +void Land::save(ESMWriter &esm) const { esm.startSubRecord("INTV"); esm.writeT(mX); @@ -140,18 +142,6 @@ void Land::save(ESMWriter &esm) esm.endRecord("INTV"); esm.writeHNT("DATA", mFlags); - - // TODO: Land! - bool wasLoaded = mDataLoaded; - if (mDataTypes) { - // Try to load all available data before saving - loadData(mDataTypes); - } - if (mDataLoaded) - mLandData->save(esm); - - if (!wasLoaded) - unloadData(); // Don't need to keep the data loaded if it wasn't already } /// \todo remove memory allocation when only defaults needed @@ -170,10 +160,10 @@ void Land::loadData(int flags) } mEsm->restoreContext(mContext); - memset(mLandData->mNormals, 0, LAND_NUM_VERTS * 3); + memset(mLandData->mNormals, 0, sizeof(mLandData->mNormals)); if (mEsm->isNextSub("VNML")) { - condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(VNML)); + condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); } if (mEsm->isNextSub("VHGT")) { diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 9c1fd1f5c..32abb7799 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Land { + static unsigned int sRecordId; + Land(); ~Land(); @@ -70,13 +72,13 @@ struct Land }; #pragma pack(pop) - typedef signed char VNML[LAND_NUM_VERTS * 3]; + typedef signed char VNML; struct LandData { float mHeightOffset; float mHeights[LAND_NUM_VERTS]; - VNML mNormals; + VNML mNormals[LAND_NUM_VERTS * 3]; uint16_t mTextures[LAND_NUM_TEXTURES]; bool mUsingColours; @@ -94,7 +96,7 @@ struct Land LandData *mLandData; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; /** * Actually loads data diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index b54a91276..6385b9a71 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -33,13 +34,13 @@ void LeveledListBase::load(ESMReader &esm) esm.getHNT(li.mLevel, "INTV"); } } -void LeveledListBase::save(ESMWriter &esm) +void LeveledListBase::save(ESMWriter &esm) const { esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNCString(mRecName, it->mId); esm.writeHNT("INTV", it->mLevel); @@ -52,4 +53,8 @@ void LeveledListBase::save(ESMWriter &esm) mChanceNone = 0; mList.clear(); } + + unsigned int CreatureLevList::sRecordId = REC_LEVC; + + unsigned int ItemLevList::sRecordId = REC_LEVI; } diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index 7339cac56..a4e1b85c2 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -20,20 +20,6 @@ class ESMWriter; struct LeveledListBase { - enum Flags - { - - Each = 0x01, // Select a new item each time this - // list is instantiated, instead of - // giving several identical items - // (used when a container has more - // than one instance of one leveled - // list.) - AllLevels = 0x02 // Calculate from all levels <= player - // level, not just the closest below - // player. - }; - int mFlags; unsigned char mChanceNone; // Chance that none are selected (0-100) std::string mId; @@ -51,7 +37,7 @@ struct LeveledListBase std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). @@ -59,6 +45,16 @@ struct LeveledListBase struct CreatureLevList: LeveledListBase { + static unsigned int sRecordId; + + enum Flags + { + + AllLevels = 0x01 // Calculate from all levels <= player + // level, not just the closest below + // player. + }; + CreatureLevList() { mRecName = "CNAM"; @@ -67,6 +63,22 @@ struct CreatureLevList: LeveledListBase struct ItemLevList: LeveledListBase { + static unsigned int sRecordId; + + enum Flags + { + + Each = 0x01, // Select a new item each time this + // list is instantiated, instead of + // giving several identical items + // (used when a container has more + // than one instance of one leveled + // list.) + AllLevels = 0x02 // Calculate from all levels <= player + // level, not just the closest below + // player. + }; + ItemLevList() { mRecName = "INAM"; diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 89a2b8c65..c02bb46b6 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Light::sRecordId = REC_LIGH; void Light::load(ESMReader &esm) { @@ -16,7 +18,7 @@ void Light::load(ESMReader &esm) mScript = esm.getHNOString("SCRI"); mSound = esm.getHNOString("SNAM"); } -void Light::save(ESMWriter &esm) +void Light::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 3f0b76d6e..74eb37197 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Light { + static unsigned int sRecordId; + enum Flags { Dynamic = 0x001, @@ -44,7 +46,7 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 03eac52bd..9ffce78a7 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Lockpick::sRecordId = REC_LOCK; void Lockpick::load(ESMReader &esm) { @@ -17,7 +19,7 @@ void Lockpick::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); } -void Lockpick::save(ESMWriter &esm) +void Lockpick::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 953066cb2..c44e2b006 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Lockpick { + static unsigned int sRecordId; + struct Data { float mWeight; @@ -24,7 +26,7 @@ struct Lockpick std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index e523e9fa7..bd28c8488 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -2,16 +2,18 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int LandTexture::sRecordId = REC_LTEX; void LandTexture::load(ESMReader &esm) { esm.getHNT(mIndex, "INTV"); mTexture = esm.getHNString("DATA"); } -void LandTexture::save(ESMWriter &esm) +void LandTexture::save(ESMWriter &esm) const { esm.writeHNT("INTV", mIndex); esm.writeHNCString("DATA", mTexture); diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 6e6d987d4..5e84428b2 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -27,11 +27,13 @@ class ESMWriter; struct LandTexture { + static unsigned int sRecordId; + std::string mId, mTexture; int mIndex; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 19d535600..f60191539 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -6,6 +6,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace { @@ -34,6 +35,7 @@ namespace namespace ESM { + unsigned int MagicEffect::sRecordId = REC_MGEF; void MagicEffect::load(ESMReader &esm) { @@ -58,15 +60,11 @@ void MagicEffect::load(ESMReader &esm) mDescription = esm.getHNOString("DESC"); } -void MagicEffect::save(ESMWriter &esm) +void MagicEffect::save(ESMWriter &esm) const { esm.writeHNT("INDX", mIndex); - mData.mFlags &= 0xe00; esm.writeHNT("MEDT", mData, 36); - if (mIndex>=0 && mIndex + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = ResistMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = ResistMagicka; + + effects[Burden] = ResistMagicka; + effects[Charm] = ResistMagicka; + effects[Silence] = ResistMagicka; + effects[Blind] = ResistMagicka; + effects[Sound] = ResistMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = ResistMagicka; + effects[FrenzyHumanoid] = ResistMagicka; + effects[DemoralizeHumanoid] = ResistMagicka; + effects[RallyHumanoid] = ResistMagicka; + } + + effects[TurnUndead] = ResistMagicka; + + effects[FireDamage] = ResistFire; + effects[FrostDamage] = ResistFrost; + effects[ShockDamage] = ResistShock; + effects[Vampirism] = ResistCommonDisease; + effects[Corprus] = ResistCorprusDisease; + effects[Poison] = ResistPoison; + effects[Paralyze] = ResistParalysis; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} + +short MagicEffect::getWeaknessEffect(short effect) +{ + std::map effects; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = WeaknessToMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = WeaknessToMagicka; + + effects[Burden] = WeaknessToMagicka; + effects[Charm] = WeaknessToMagicka; + effects[Silence] = WeaknessToMagicka; + effects[Blind] = WeaknessToMagicka; + effects[Sound] = WeaknessToMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = WeaknessToMagicka; + effects[FrenzyHumanoid] = WeaknessToMagicka; + effects[DemoralizeHumanoid] = WeaknessToMagicka; + effects[RallyHumanoid] = WeaknessToMagicka; + } + + effects[TurnUndead] = WeaknessToMagicka; + + effects[FireDamage] = WeaknessToFire; + effects[FrostDamage] = WeaknessToFrost; + effects[ShockDamage] = WeaknessToShock; + effects[Vampirism] = WeaknessToCommonDisease; + effects[Corprus] = WeaknessToCorprusDisease; + effects[Poison] = WeaknessToPoison; + + // Weakness to magicka or -1 ? + effects[Paralyze] = WeaknessToMagicka; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} static std::map genNameMap() { diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index f139fa32c..8281f4969 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct MagicEffect { + static unsigned int sRecordId; + enum Flags { TargetSkill = 0x1, // Affects a specific skill, which is specified elsewhere in the effect structure. @@ -26,11 +28,7 @@ struct MagicEffect UncappedDamage = 0x1000, // Negates multiple cap behaviours. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second. NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally. - CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. - SpellMaking = 0x0200, - Enchanting = 0x0400, - Negative = 0x0800 // A harmful effect. Will determine whether - // eg. NPCs regard this spell as an attack. (same as 0x10?) + CasterLinked = 0x20000 // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. }; enum MagnitudeDisplayType { @@ -47,8 +45,9 @@ struct MagicEffect int mSchool; // SpellSchool, see defs.hpp float mBaseCost; int mFlags; - // Properties of the fired magic 'ball' I think - int mRed, mBlue, mGreen; + // Glow color for enchanted items with this effect + int mRed, mGreen, mBlue; + // Properties of the fired magic 'ball' float mSpeed, mSize, mSizeCap; }; // 36 bytes @@ -56,14 +55,20 @@ struct MagicEffect static const std::string &effectIdToString(short effectID); static short effectStringToId(const std::string &effect); + + /// Returns the effect that provides resistance against \a effect (or -1 if there's none) + static short getResistanceEffect(short effect); + /// Returns the effect that induces weakness against \a effect (or -1 if there's none) + static short getWeaknessEffect(short effect); + MagnitudeDisplayType getMagnitudeDisplayType() const; MEDTstruct mData; std::string mIcon, mParticle; // Textures - std::string mCasting, mHit, mArea; // Statics - std::string mBolt; // Weapon + std::string mCasting, mHit, mArea; // ESM::Static + std::string mBolt; // ESM::Weapon std::string mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds std::string mDescription; @@ -79,7 +84,7 @@ struct MagicEffect int mIndex; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; enum Effects @@ -230,7 +235,9 @@ struct MagicEffect SummonBear = 139, SummonBonewolf = 140, SummonCreature04 = 141, - SummonCreature05 = 142 + SummonCreature05 = 142, + + Length }; }; } diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 6006334ea..2ca09e8ae 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Miscellaneous::sRecordId = REC_MISC; void Miscellaneous::load(ESMReader &esm) { @@ -14,7 +16,7 @@ void Miscellaneous::load(ESMReader &esm) mScript = esm.getHNOString("SCRI"); mIcon = esm.getHNOString("ITEX"); } -void Miscellaneous::save(ESMWriter &esm) +void Miscellaneous::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 25a9c5865..576bd18c0 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Miscellaneous { + static unsigned int sRecordId; + struct MCDTstruct { float mWeight; @@ -29,7 +31,7 @@ struct Miscellaneous std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 7e17a93dc..e5b851bf0 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -2,13 +2,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int NPC::sRecordId = REC_NPC_; void NPC::load(ESMReader &esm) { - mNpdt52.mGold = -10; + //mNpdt52.mGold = -10; mPersistent = esm.getRecordFlags() & 0x0400; @@ -27,12 +29,12 @@ void NPC::load(ESMReader &esm) esm.getSubHeader(); if (esm.getSubSize() == 52) { - mNpdtType = 52; + mNpdtType = NPC_DEFAULT; esm.getExact(&mNpdt52, 52); } else if (esm.getSubSize() == 12) { - mNpdtType = 12; + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; esm.getExact(&mNpdt12, 12); } else @@ -63,7 +65,7 @@ void NPC::load(ESMReader &esm) mAiPackage.load(esm); esm.skipRecord(); } -void NPC::save(ESMWriter &esm) +void NPC::save(ESMWriter &esm) const { esm.writeHNOCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); @@ -74,9 +76,9 @@ void NPC::save(ESMWriter &esm) esm.writeHNCString("KNAM", mHair); esm.writeHNOCString("SCRI", mScript); - if (mNpdtType == 52) + if (mNpdtType == NPC_DEFAULT) esm.writeHNT("NPDT", mNpdt52, 52); - else if (mNpdtType == 12) + else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) esm.writeHNT("NPDT", mNpdt12, 12); esm.writeHNT("FLAG", mFlags); @@ -87,7 +89,7 @@ void NPC::save(ESMWriter &esm) esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } - typedef std::vector::iterator DestIter; + typedef std::vector::const_iterator DestIter; for (DestIter it = mTransport.begin(); it != mTransport.end(); ++it) { esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); esm.writeHNOCString("DNAM", it->mCellName); @@ -112,7 +114,7 @@ void NPC::save(ESMWriter &esm) mNpdt52.mLevel = 0; mNpdt52.mStrength = mNpdt52.mIntelligence = mNpdt52.mWillpower = mNpdt52.mAgility = mNpdt52.mSpeed = mNpdt52.mEndurance = mNpdt52.mPersonality = mNpdt52.mLuck = 0; - for (int i=0; i<27; ++i) mNpdt52.mSkills[i] = 0; + for (int i=0; i< Skill::Length; ++i) mNpdt52.mSkills[i] = 0; mNpdt52.mReputation = 0; mNpdt52.mHealth = mNpdt52.mMana = mNpdt52.mFatigue = 0; mNpdt52.mDisposition = 0; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 009bc5ef3..08f678b45 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -8,6 +8,7 @@ #include "loadcont.hpp" #include "aipackage.hpp" #include "spelllist.hpp" +#include "loadskil.hpp" namespace ESM { @@ -20,6 +21,8 @@ class ESMWriter; struct NPC { + static unsigned int sRecordId; + // Services enum Services { @@ -56,6 +59,12 @@ struct NPC Metal = 0x0800 // Metal blood effect (golden?) }; + enum NpcType + { + NPC_WITH_AUTOCALCULATED_STATS = 12, + NPC_DEFAULT = 52 + }; + #pragma pack(push) #pragma pack(1) @@ -71,7 +80,7 @@ struct NPC mPersonality, mLuck; - char mSkills[27]; + char mSkills[Skill::Length]; char mReputation; short mHealth, mMana, mFatigue; char mDisposition, mFactionID, mRank; @@ -96,7 +105,7 @@ struct NPC char mNpdtType; NPDTstruct52 mNpdt52; - NPDTstruct12 mNpdt12; // Use this if npdt52.gold == -10 + NPDTstruct12 mNpdt12; //for autocalculated characters int mFlags; @@ -117,7 +126,7 @@ struct NPC std::string mHair, mHead; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; bool isMale() const; diff --git a/components/esm/loadnpcc.hpp b/components/esm/loadnpcc.hpp index 79d92397f..c87c2545f 100644 --- a/components/esm/loadnpcc.hpp +++ b/components/esm/loadnpcc.hpp @@ -78,13 +78,15 @@ class ESMWriter; struct LoadNPCC { + static unsigned int sRecordId; + std::string mId; void load(ESMReader &esm) { esm.skipRecord(); } - void save(ESMWriter &esm) + void save(ESMWriter &esm) const { } }; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 882addcb9..3b5330e9f 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Pathgrid::sRecordId = REC_PGRD; void Pathgrid::load(ESMReader &esm) { @@ -70,25 +72,25 @@ void Pathgrid::load(ESMReader &esm) } } } -void Pathgrid::save(ESMWriter &esm) +void Pathgrid::save(ESMWriter &esm) const { esm.writeHNT("DATA", mData, 12); esm.writeHNCString("NAME", mCell); - + if (!mPoints.empty()) { esm.startSubRecord("PGRP"); - for (PointList::iterator it = mPoints.begin(); it != mPoints.end(); ++it) + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) { esm.writeT(*it); } esm.endRecord("PGRP"); } - + if (!mEdges.empty()) { esm.startSubRecord("PGRC"); - for (std::vector::iterator it = mEdges.begin(); it != mEdges.end(); ++it) + for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) { esm.writeT(it->mV1); } diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index c3f50fc4d..9ee49552d 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -15,6 +15,8 @@ class ESMWriter; */ struct Pathgrid { + static unsigned int sRecordId; + struct DATAstruct { int mX, mY; // Grid location, matches cell for exterior cells @@ -46,7 +48,7 @@ struct Pathgrid EdgeList mEdges; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 729f8404e..caa3d7e0e 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Probe::sRecordId = REC_PROB; void Probe::load(ESMReader &esm) { @@ -17,7 +19,7 @@ void Probe::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); } -void Probe::save(ESMWriter &esm) +void Probe::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index 55b896bcd..b89b2ddeb 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Probe { + static unsigned int sRecordId; + struct Data { float mWeight; @@ -24,7 +26,7 @@ struct Probe std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 955424e2b..e50e43a74 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Race::sRecordId = REC_RACE; + int Race::MaleFemale::getValue (bool male) const { return male ? mMale : mFemale; @@ -22,7 +25,7 @@ void Race::load(ESMReader &esm) mPowers.load(esm); mDescription = esm.getHNOString("DESC"); } -void Race::save(ESMWriter &esm) +void Race::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 6ecec8ebb..7d5736d9b 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Race { + static unsigned int sRecordId; + struct SkillBonus { int mSkill; // SkillEnum @@ -65,7 +67,7 @@ struct Race SpellList mPowers; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 41c7f507a..fa4271e26 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Region::sRecordId = REC_REGN; void Region::load(ESMReader &esm) { @@ -28,7 +30,7 @@ void Region::load(ESMReader &esm) mSoundList.push_back(sr); } } -void Region::save(ESMWriter &esm) +void Region::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); @@ -40,7 +42,7 @@ void Region::save(ESMWriter &esm) esm.writeHNOCString("BNAM", mSleepList); esm.writeHNT("CNAM", mMapColor); - for (std::vector::iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) { esm.writeHNT("SNAM", *it); } diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index f2a3d9a10..1992c951b 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Region { + static unsigned int sRecordId; + #pragma pack(push) #pragma pack(1) struct WEATstruct @@ -47,7 +49,7 @@ struct Region std::vector mSoundList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index ced6daa2e..a7132828d 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Repair::sRecordId = REC_REPA; void Repair::load(ESMReader &esm) { @@ -17,7 +19,7 @@ void Repair::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); } -void Repair::save(ESMWriter &esm) +void Repair::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index 83812bad9..5b404b0e4 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Repair { + static unsigned int sRecordId; + struct Data { float mWeight; @@ -24,7 +26,7 @@ struct Repair std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 2c1b018d9..de679e815 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -12,6 +13,8 @@ struct SCHD Script::SCHDstruct mData; }; + unsigned int Script::sRecordId = REC_SCPT; + void Script::load(ESMReader &esm) { SCHD data; @@ -23,24 +26,24 @@ void Script::load(ESMReader &esm) if (esm.isNextSub("SCVR")) { int s = mData.mStringTableSize; - char* tmp = new char[s]; - esm.getHExact(tmp, s); + + std::vector tmp (s); + esm.getHExact (&tmp[0], s); // Set up the list of variable names mVarNames.resize(mData.mNumShorts + mData.mNumLongs + mData.mNumFloats); // The tmp buffer is a null-byte separated string list, we // just have to pick out one string at a time. - char* str = tmp; + char* str = &tmp[0]; for (size_t i = 0; i < mVarNames.size(); i++) { mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; - if (str - tmp > s) + if (str - &tmp[0] > s) esm.fail("String table overflow"); } - delete[] tmp; } // Script mData @@ -50,11 +53,11 @@ void Script::load(ESMReader &esm) // Script text mScriptText = esm.getHNOString("SCTX"); } -void Script::save(ESMWriter &esm) +void Script::save(ESMWriter &esm) const { std::string varNameString; if (!mVarNames.empty()) - for (std::vector::iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) varNameString.append(*it); SCHD data; @@ -68,7 +71,7 @@ void Script::save(ESMWriter &esm) if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); - for (std::vector::iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) { esm.writeHCString(*it); } diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index be7e83900..d5200d4c1 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -19,6 +19,8 @@ class ESMWriter; class Script { public: + static unsigned int sRecordId; + struct SCHDstruct { /* Script name. @@ -56,7 +58,7 @@ public: std::string mScriptText; // Uncompiled script void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 676a835c3..b6724e938 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -6,6 +6,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -126,6 +127,8 @@ namespace ESM HandToHand }}; + unsigned int Skill::sRecordId = REC_SKIL; + void Skill::load(ESMReader &esm) { esm.getHNT(mIndex, "INDX"); @@ -137,7 +140,7 @@ void Skill::load(ESMReader &esm) mId = indexToId (mIndex); } -void Skill::save(ESMWriter &esm) +void Skill::save(ESMWriter &esm) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 384f87454..1b9db5bcf 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct Skill { + static unsigned int sRecordId; + std::string mId; struct SKDTstruct @@ -75,7 +77,7 @@ struct Skill static const boost::array sSkillIds; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 42d524226..1a8ca6335 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int SoundGenerator::sRecordId = REC_SNDG; void SoundGenerator::load(ESMReader &esm) { @@ -13,7 +15,7 @@ void SoundGenerator::load(ESMReader &esm) mCreature = esm.getHNOString("CNAM"); mSound = esm.getHNOString("SNAM"); } -void SoundGenerator::save(ESMWriter &esm) +void SoundGenerator::save(ESMWriter &esm) const { esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index a6226c154..5509661c1 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct SoundGenerator { + static unsigned int sRecordId; + enum Type { LeftFoot = 0, @@ -33,7 +35,7 @@ struct SoundGenerator std::string mId, mCreature, mSound; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 07af2b5e9..49c9eb54e 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Sound::sRecordId = REC_SOUN; void Sound::load(ESMReader &esm) { @@ -17,7 +19,7 @@ void Sound::load(ESMReader &esm) << endl; */ } -void Sound::save(ESMWriter &esm) +void Sound::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index f8e38ac09..04a0984fd 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -16,11 +16,13 @@ struct SOUNstruct struct Sound { + static unsigned int sRecordId; + SOUNstruct mData; std::string mId, mSound; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 8149fe4ce..2c98d796d 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Spell::sRecordId = REC_SPEL; void Spell::load(ESMReader &esm) { @@ -13,7 +15,7 @@ void Spell::load(ESMReader &esm) mEffects.load(esm); } -void Spell::save(ESMWriter &esm) +void Spell::save(ESMWriter &esm) const { esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 3a620962d..cbf5366c4 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -13,6 +13,8 @@ class ESMWriter; struct Spell { + static unsigned int sRecordId; + enum SpellType { ST_Spell = 0, // Normal spell, must be cast and costs mana @@ -42,7 +44,7 @@ struct Spell EffectList mEffects; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index ae50de517..69b04bb23 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -2,16 +2,18 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int StartScript::sRecordId = REC_SSCR; void StartScript::load(ESMReader &esm) { mData = esm.getHNString("DATA"); mScript = esm.getHNString("NAME"); } -void StartScript::save(ESMWriter &esm) +void StartScript::save(ESMWriter &esm) const { esm.writeHNString("DATA", mData); esm.writeHNString("NAME", mScript); diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index 713fe96b5..d09ad883e 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -19,12 +19,14 @@ class ESMWriter; struct StartScript { + static unsigned int sRecordId; + std::string mData; std::string mId, mScript; // Load a record and add it to the list void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index c9346dafc..a71f22dc2 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -2,15 +2,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Static::sRecordId = REC_STAT; void Static::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); } -void Static::save(ESMWriter &esm) +void Static::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); } diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 1adb7d05b..d912d1058 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -22,10 +22,12 @@ class ESMWriter; struct Static { + static unsigned int sRecordId; + std::string mId, mModel; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 74d578ba7..d3d525049 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -4,6 +4,7 @@ #include "esmcommon.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" void ESM::Header::blank() { @@ -13,12 +14,11 @@ void ESM::Header::blank() mData.desc.assign (""); mData.records = 0; mFormat = CurrentFormat; + mMaster.clear(); } void ESM::Header::load (ESMReader &esm) { - esm.getHNT (mData, "HEDR", 300); - if (esm.isNextSub ("FORM")) { esm.getHT (mFormat); @@ -28,6 +28,16 @@ void ESM::Header::load (ESMReader &esm) else mFormat = 0; + if (esm.isNextSub("HEDR")) + { + esm.getSubHeader(); + esm.getT(mData.version); + esm.getT(mData.type); + mData.author.assign(esm.getString(sizeof(mData.author.name))); + mData.desc.assign(esm.getString(sizeof(mData.desc.name))); + esm.getT(mData.records); + } + while (esm.isNextSub ("MAST")) { MasterData m; @@ -39,15 +49,15 @@ void ESM::Header::load (ESMReader &esm) void ESM::Header::save (ESMWriter &esm) { - esm.writeHNT ("HEDR", mData, 300); - if (mFormat>0) esm.writeHNT ("FORM", mFormat); + esm.writeHNT ("HEDR", mData, 300); + for (std::vector::iterator iter = mMaster.begin(); iter != mMaster.end(); ++iter) { esm.writeHNCString ("MAST", iter->name); esm.writeHNT ("DATA", iter->size); } -} \ No newline at end of file +} diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index b73a4c31e..5614d295f 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -24,7 +24,7 @@ namespace ESM versions are 1.2 and 1.3. These correspond to: 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 */ - int version; + unsigned int version; int type; // 0=esp, 1=esm, 32=ess (unused) NAME32 author; // Author's name NAME256 desc; // File description diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 253712396..1d0b149df 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Weapon::sRecordId = REC_WEAP; void Weapon::load(ESMReader &esm) { @@ -15,7 +17,7 @@ void Weapon::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); mEnchant = esm.getHNOString("ENAM"); } -void Weapon::save(ESMWriter &esm) +void Weapon::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index b62179ccb..14ddb4708 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Weapon { + static unsigned int sRecordId; + enum Type { ShortBladeOneHand = 0, @@ -33,6 +35,13 @@ struct Weapon Bolt = 13 }; + enum AttackType + { + AT_Chop, + AT_Slash, + AT_Thrust + }; + enum Flags { Magical = 0x01, @@ -59,7 +68,7 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp new file mode 100644 index 000000000..9c470a025 --- /dev/null +++ b/components/esm/locals.cpp @@ -0,0 +1,28 @@ + +#include "locals.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::Locals::load (ESMReader &esm) +{ + while (esm.isNextSub ("LOCA")) + { + std::string id = esm.getHString(); + + Variant value; + value.read (esm, Variant::Format_Info); + + mVariables.push_back (std::make_pair (id, value)); + } +} + +void ESM::Locals::save (ESMWriter &esm) const +{ + for (std::vector >::const_iterator iter (mVariables.begin()); + iter!=mVariables.end(); ++iter) + { + esm.writeHNString ("LOCA", iter->first); + iter->second.write (esm, Variant::Format_Info); + } +} \ No newline at end of file diff --git a/components/esm/locals.hpp b/components/esm/locals.hpp new file mode 100644 index 000000000..af5afb23b --- /dev/null +++ b/components/esm/locals.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_ESM_LOCALS_H +#define OPENMW_ESM_LOCALS_H + +#include +#include + +#include "variant.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + /// \brief Storage structure for local variables (only used in saved games) + /// + /// \note This is not a top-level record. + + struct Locals + { + std::vector > mVariables; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp new file mode 100644 index 000000000..c452611a0 --- /dev/null +++ b/components/esm/npcstate.cpp @@ -0,0 +1,16 @@ + +#include "npcstate.hpp" + +void ESM::NpcState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mInventory.load (esm); +} + +void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + mInventory.save (esm); +} \ No newline at end of file diff --git a/components/esm/npcstate.hpp b/components/esm/npcstate.hpp new file mode 100644 index 000000000..ceb18b88b --- /dev/null +++ b/components/esm/npcstate.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESM_NPCSTATE_H +#define OPENMW_ESM_NPCSTATE_H + +#include "objectstate.hpp" +#include "inventorystate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct NpcState : public ObjectState + { + InventoryState mInventory; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp new file mode 100644 index 000000000..be00f3ef6 --- /dev/null +++ b/components/esm/objectstate.cpp @@ -0,0 +1,51 @@ + +#include "objectstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::ObjectState::load (ESMReader &esm) +{ + mRef.load (esm, true); + + mHasLocals = 0; + esm.getHNOT (mHasLocals, "HLOC"); + + if (mHasLocals) + mLocals.load (esm); + + mEnabled = 1; + esm.getHNOT (mEnabled, "ENAB"); + + mCount = 1; + esm.getHNOT (mCount, "COUN"); + + esm.getHNOT (mPosition, "POS_", 24); + + esm.getHNOT (mLocalRotation, "LROT", 12); +} + +void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const +{ + mRef.save (esm, true, inInventory); + + if (mHasLocals) + { + esm.writeHNT ("HLOC", mHasLocals); + mLocals.save (esm); + } + + if (!mEnabled && !inInventory) + esm.writeHNT ("ENAB", mEnabled); + + if (mCount!=1) + esm.writeHNT ("COUN", mCount); + + if (!inInventory) + { + esm.writeHNT ("POS_", mPosition, 24); + esm.writeHNT ("LROT", mLocalRotation, 12); + } +} + +ESM::ObjectState::~ObjectState() {} \ No newline at end of file diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp new file mode 100644 index 000000000..9c9ca5f2e --- /dev/null +++ b/components/esm/objectstate.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_ESM_OBJECTSTATE_H +#define OPENMW_ESM_OBJECTSTATE_H + +#include +#include + +#include "cellref.hpp" +#include "locals.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + ///< \brief Save state for objects, that do not use custom data + struct ObjectState + { + CellRef mRef; + + unsigned char mHasLocals; + Locals mLocals; + unsigned char mEnabled; + int mCount; + ESM::Position mPosition; + float mLocalRotation[3]; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual ~ObjectState(); + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/player.cpp b/components/esm/player.cpp new file mode 100644 index 000000000..d5ddc74d0 --- /dev/null +++ b/components/esm/player.cpp @@ -0,0 +1,48 @@ + +#include "player.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::Player::load (ESMReader &esm) +{ + mObject.load (esm); + + mCellId.load (esm); + + esm.getHNT (mLastKnownExteriorPosition, "LKEP", 12); + + if (esm.isNextSub ("MARK")) + { + mHasMark = true; + esm.getHT (mMarkedPosition, 24); + mMarkedCell.load (esm); + } + else + mHasMark = false; + + mAutoMove = 0; + esm.getHNOT (mAutoMove, "AMOV"); + + mBirthsign = esm.getHNString ("SIGN"); +} + +void ESM::Player::save (ESMWriter &esm) const +{ + mObject.save (esm); + + mCellId.save (esm); + + esm.writeHNT ("LKEP", mLastKnownExteriorPosition, 12); + + if (mHasMark) + { + esm.writeHNT ("MARK", mMarkedPosition, 24); + mMarkedCell.save (esm); + } + + if (mAutoMove) + esm.writeHNT ("AMOV", mAutoMove); + + esm.writeHNString ("SIGN", mBirthsign); +} \ No newline at end of file diff --git a/components/esm/player.hpp b/components/esm/player.hpp new file mode 100644 index 000000000..0d70ee090 --- /dev/null +++ b/components/esm/player.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_ESM_PLAYER_H +#define OPENMW_ESM_PLAYER_H + +#include + +#include "npcstate.hpp" +#include "cellid.hpp" +#include "defs.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct Player + { + NpcState mObject; + CellId mCellId; + float mLastKnownExteriorPosition[3]; + unsigned char mHasMark; + ESM::Position mMarkedPosition; + CellId mMarkedCell; + unsigned char mAutoMove; + std::string mBirthsign; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp new file mode 100644 index 000000000..e93826725 --- /dev/null +++ b/components/esm/queststate.cpp @@ -0,0 +1,19 @@ + +#include "queststate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::QuestState::load (ESMReader &esm) +{ + mTopic = esm.getHNString ("YETO"); + esm.getHNOT (mState, "QSTA"); + esm.getHNOT (mFinished, "QFIN"); +} + +void ESM::QuestState::save (ESMWriter &esm) const +{ + esm.writeHNString ("YETO", mTopic); + esm.writeHNT ("QSTA", mState); + esm.writeHNT ("QFIN", mFinished); +} \ No newline at end of file diff --git a/components/esm/queststate.hpp b/components/esm/queststate.hpp new file mode 100644 index 000000000..1769336f2 --- /dev/null +++ b/components/esm/queststate.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_ESM_QUESTSTATE_H +#define OPENMW_ESM_QUESTSTATE_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct QuestState + { + std::string mTopic; + int mState; + unsigned char mFinished; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp new file mode 100644 index 000000000..d6887f170 --- /dev/null +++ b/components/esm/savedgame.cpp @@ -0,0 +1,46 @@ + +#include "savedgame.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; + +void ESM::SavedGame::load (ESMReader &esm) +{ + mPlayerName = esm.getHNString("PLNA"); + esm.getHNOT (mPlayerLevel, "PLLE"); + mPlayerClass = esm.getHNString("PLCL"); + mPlayerCell = esm.getHNString("PLCE"); + esm.getHNT (mInGameTime, "TSTM", 16); + esm.getHNT (mTimePlayed, "TIME"); + mDescription = esm.getHNString ("DESC"); + + while (esm.isNextSub ("DEPE")) + mContentFiles.push_back (esm.getHString()); + + esm.getSubNameIs("SCRN"); + esm.getSubHeader(); + mScreenshot.resize(esm.getSubSize()); + esm.getExact(&mScreenshot[0], mScreenshot.size()); +} + +void ESM::SavedGame::save (ESMWriter &esm) const +{ + esm.writeHNString ("PLNA", mPlayerName); + esm.writeHNT ("PLLE", mPlayerLevel); + esm.writeHNString ("PLCL", mPlayerClass); + esm.writeHNString ("PLCE", mPlayerCell); + esm.writeHNT ("TSTM", mInGameTime, 16); + esm.writeHNT ("TIME", mTimePlayed); + esm.writeHNString ("DESC", mDescription); + + for (std::vector::const_iterator iter (mContentFiles.begin()); + iter!=mContentFiles.end(); ++iter) + esm.writeHNString ("DEPE", *iter); + + esm.startSubRecord("SCRN"); + esm.write(&mScreenshot[0], mScreenshot.size()); + esm.endRecord("SCRN"); +} diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp new file mode 100644 index 000000000..9c7bf551d --- /dev/null +++ b/components/esm/savedgame.hpp @@ -0,0 +1,41 @@ +#ifndef OPENMW_ESM_SAVEDGAME_H +#define OPENMW_ESM_SAVEDGAME_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct SavedGame + { + static unsigned int sRecordId; + + struct TimeStamp + { + float mGameHour; + int mDay; + int mMonth; + int mYear; + }; + + std::vector mContentFiles; + std::string mPlayerName; + int mPlayerLevel; + std::string mPlayerClass; // this is the ID and not the name of the class + std::string mPlayerCell; + TimeStamp mInGameTime; + double mTimePlayed; + std::string mDescription; + std::vector mScreenshot; // raw jpg-encoded data + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/spelllist.cpp b/components/esm/spelllist.cpp index dd886cf7f..24d3c3d0a 100644 --- a/components/esm/spelllist.cpp +++ b/components/esm/spelllist.cpp @@ -12,9 +12,9 @@ void SpellList::load(ESMReader &esm) } } -void SpellList::save(ESMWriter &esm) +void SpellList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) { + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNString("NPCS", *it, 32); } } diff --git a/components/esm/spelllist.hpp b/components/esm/spelllist.hpp index 52999270a..934bdda7a 100644 --- a/components/esm/spelllist.hpp +++ b/components/esm/spelllist.hpp @@ -17,7 +17,7 @@ namespace ESM std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index 2bba60a15..8ba9bb34f 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -33,7 +33,7 @@ namespace ESM { Format_Global, Format_Gmst, - Format_Info + Format_Info // also used for local variables in saved game files }; Variant(); diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index 160402aa4..1bacdc077 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -193,7 +193,7 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var } else if (format==Variant::Format_Gmst || format==Variant::Format_Info) { - if (type==VT_Int) + if (type!=VT_Int) { std::ostringstream stream; stream diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp deleted file mode 100644 index 02a6766b0..000000000 --- a/components/fileorderlist/model/datafilesmodel.cpp +++ /dev/null @@ -1,445 +0,0 @@ -#include -#include -#include -#include - -#include - -#include - -#include "esm/esmfile.hpp" - -#include "datafilesmodel.hpp" - -#include - -DataFilesModel::DataFilesModel(QObject *parent) : - QAbstractTableModel(parent) -{ - mEncoding = QString("win1252"); -} - -DataFilesModel::~DataFilesModel() -{ -} - -void DataFilesModel::setEncoding(const QString &encoding) -{ - mEncoding = encoding; -} - -void DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState state) -{ - setData(index, state, Qt::CheckStateRole); -} - -Qt::CheckState DataFilesModel::checkState(const QModelIndex &index) -{ - EsmFile *file = item(index.row()); - return mCheckStates[file->fileName()]; -} - -int DataFilesModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 9; -} - -int DataFilesModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : mFiles.count(); -} - - -bool DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent) -{ - if (oldrow < 0 || row < 0 || oldrow == row) - return false; - - emit layoutAboutToBeChanged(); - //emit beginMoveRows(parent, oldrow, oldrow, parent, row); - mFiles.swap(oldrow, row); - //emit endInsertRows(); - emit layoutChanged(); - - return true; -} - -QVariant DataFilesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - EsmFile *file = item(index.row()); - - if (!file) - return QVariant(); - - const int column = index.column(); - - switch (role) { - case Qt::DisplayRole: { - - switch (column) { - case 0: - return file->fileName(); - case 1: - return file->author(); - case 2: - return QString("%1 kB").arg(int((file->size() + 1023) / 1024)); - case 3: - //return file->modified().toString(Qt::TextDate); - return file->modified().toString(Qt::ISODate); - case 4: - return file->accessed().toString(Qt::TextDate); - case 5: - return file->version(); - case 6: - return file->path(); - case 7: - return file->masters().join(", "); - case 8: - return file->description(); - } - } - - case Qt::TextAlignmentRole: { - switch (column) { - case 0: - case 1: - return Qt::AlignLeft + Qt::AlignVCenter; - case 2: - case 3: - case 4: - case 5: - return Qt::AlignRight + Qt::AlignVCenter; - default: - return Qt::AlignLeft + Qt::AlignVCenter; - } - } - - case Qt::CheckStateRole: { - if (column != 0) - return QVariant(); - return mCheckStates[file->fileName()]; - } - case Qt::ToolTipRole: - { - if (column != 0) - return QVariant(); - - if (file->version() == 0.0f) - return QVariant(); // Data not set - - QString tooltip = - QString("Author: %1
\ - Version: %2
\ -
Description:
%3
\ -
Dependencies: %4
") - .arg(file->author()) - .arg(QString::number(file->version())) - .arg(file->description()) - .arg(file->masters().join(", ")); - - - return tooltip; - - } - default: - return QVariant(); - } - -} - -Qt::ItemFlags DataFilesModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - - EsmFile *file = item(index.row()); - - if (!file) - return Qt::NoItemFlags; - - if (canBeChecked(file)) { - if (index.column() == 0) { - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; - } else { - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; - } - } else { - if (index.column() == 0) { - return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; - } else { - return Qt::NoItemFlags | Qt::ItemIsSelectable; - } - } - -} - -QVariant DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role != Qt::DisplayRole) - return QVariant(); - - if (orientation == Qt::Horizontal) { - switch (section) { - case 0: return tr("Name"); - case 1: return tr("Author"); - case 2: return tr("Size"); - case 3: return tr("Modified"); - case 4: return tr("Accessed"); - case 5: return tr("Version"); - case 6: return tr("Path"); - case 7: return tr("Masters"); - case 8: return tr("Description"); - } - } else { - // Show row numbers - return ++section; - } - - return QVariant(); -} - -bool DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid()) - return false; - - if (role == Qt::CheckStateRole) { - QString name = item(index.row())->fileName(); - mCheckStates[name] = static_cast(value.toInt()); - - // 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; - } -} - -void DataFilesModel::sort(int column, Qt::SortOrder order) -{ - emit layoutAboutToBeChanged(); - - if (column == 3) { - qSort(mFiles.begin(), mFiles.end(), lessThanDate); - } else { - qSort(mFiles.begin(), mFiles.end(), lessThanEsmFile); - } - - emit layoutChanged(); -} - -void DataFilesModel::addFile(EsmFile *file) -{ - emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); - mFiles.append(file); - emit endInsertRows(); -} - -void DataFilesModel::addFiles(const QString &path) -{ - QDir dir(path); - QStringList filters; - filters << "*.esp" << "*.esm"; - dir.setNameFilters(filters); - - // Create a decoder for non-latin characters in esx metadata - QTextCodec *codec; - - if (mEncoding == QLatin1String("win1252")) { - codec = QTextCodec::codecForName("windows-1252"); - } else if (mEncoding == QLatin1String("win1251")) { - codec = QTextCodec::codecForName("windows-1251"); - } else if (mEncoding == QLatin1String("win1250")) { - codec = QTextCodec::codecForName("windows-1250"); - } else { - return; // This should never happen; - } - - QTextDecoder *decoder = codec->makeDecoder(); - - foreach (const QString &path, dir.entryList()) { - QFileInfo info(dir.absoluteFilePath(path)); - EsmFile *file = new EsmFile(path); - - try { - ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); - fileReader.setEncoder(&encoder); - fileReader.open(dir.absoluteFilePath(path).toStdString()); - - std::vector mlist = fileReader.getMasters(); - - QStringList masters; - - for (unsigned int i = 0; i < mlist.size(); ++i) { - QString master = QString::fromStdString(mlist[i].name); - masters.append(master); - } - - file->setAuthor(decoder->toUnicode(fileReader.getAuthor().c_str())); - file->setSize(info.size()); - file->setDates(info.lastModified(), info.lastRead()); - file->setVersion(fileReader.getFVer()); - file->setPath(info.absoluteFilePath()); - file->setMasters(masters); - file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); - - - // Put the file in the table - if (findItem(path) == 0) - addFile(file); - } catch(std::runtime_error &e) { - // An error occurred while reading the .esp - qWarning() << "Error reading esp: " << e.what(); - continue; - } - - } - - delete decoder; -} - -QModelIndex DataFilesModel::indexFromItem(EsmFile *item) const -{ - if (item) - return createIndex(mFiles.indexOf(item), 0); - - return QModelIndex(); -} - -EsmFile* DataFilesModel::findItem(const QString &name) -{ - QList::ConstIterator it; - QList::ConstIterator itEnd = mFiles.constEnd(); - - int i = 0; - for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = item(i); - ++i; - - if (name == file->fileName()) - return file; - } - - // Not found - return 0; -} - -EsmFile* DataFilesModel::item(int row) const -{ - if (row >= 0 && row < mFiles.count()) - return mFiles.at(row); - else - return 0; -} - -QStringList DataFilesModel::checkedItems() -{ - 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; - - QString name = file->fileName(); - - // Only add the items that are in the checked list and available - 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(); - mCheckStates.clear(); - emit layoutChanged(); -} - -QStringList DataFilesModel::uncheckedItems() -{ - QStringList list; - QStringList checked = checkedItems(); - - QList::ConstIterator it; - QList::ConstIterator itEnd = mFiles.constEnd(); - - int i = 0; - for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = item(i); - ++i; - - // Add the items that are not in the checked list - if (!checked.contains(file->fileName())) - list << file->fileName(); - } - - return list; -} - -bool DataFilesModel::canBeChecked(EsmFile *file) const -{ - //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; - } - } - return canBeChecked; -} diff --git a/components/fileorderlist/model/datafilesmodel.hpp b/components/fileorderlist/model/datafilesmodel.hpp deleted file mode 100644 index 0a07a536f..000000000 --- a/components/fileorderlist/model/datafilesmodel.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef DATAFILESMODEL_HPP -#define DATAFILESMODEL_HPP - -#include -#include -#include -#include - - -class EsmFile; - -class DataFilesModel : public QAbstractTableModel -{ - Q_OBJECT - -public: - explicit DataFilesModel(QObject *parent = 0); - virtual ~DataFilesModel(); - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - - bool moveRow(int oldrow, int row, const QModelIndex &parent = QModelIndex()); - - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - - inline QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const - { return QAbstractTableModel::index(row, column, parent); } - - void setEncoding(const QString &encoding); - - 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); - - QModelIndex indexFromItem(EsmFile *item) const; - EsmFile* findItem(const QString &name); - EsmFile* item(int row) const; - -signals: - void checkedItemsChanged(const QStringList &items); - -private: - bool canBeChecked(EsmFile *file) const; - void addFile(EsmFile *file); - - QList mFiles; - QHash mCheckStates; - - QString mEncoding; - -}; - -#endif // DATAFILESMODEL_HPP diff --git a/components/fileorderlist/model/esm/esmfile.cpp b/components/fileorderlist/model/esm/esmfile.cpp deleted file mode 100644 index 93d83091e..000000000 --- a/components/fileorderlist/model/esm/esmfile.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "esmfile.hpp" - -EsmFile::EsmFile(QString fileName, ModelItem *parent) - : ModelItem(parent) -{ - mFileName = fileName; - mSize = 0; - mVersion = 0.0f; -} - -void EsmFile::setFileName(const QString &fileName) -{ - mFileName = fileName; -} - -void EsmFile::setAuthor(const QString &author) -{ - mAuthor = author; -} - -void EsmFile::setSize(const int size) -{ - mSize = size; -} - -void EsmFile::setDates(const QDateTime &modified, const QDateTime &accessed) -{ - mModified = modified; - mAccessed = accessed; -} - -void EsmFile::setVersion(float version) -{ - mVersion = version; -} - -void EsmFile::setPath(const QString &path) -{ - mPath = path; -} - -void EsmFile::setMasters(const QStringList &masters) -{ - mMasters = masters; -} - -void EsmFile::setDescription(const QString &description) -{ - mDescription = description; -} diff --git a/components/fileorderlist/model/esm/esmfile.hpp b/components/fileorderlist/model/esm/esmfile.hpp deleted file mode 100644 index 52b3fbd00..000000000 --- a/components/fileorderlist/model/esm/esmfile.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef ESMFILE_HPP -#define ESMFILE_HPP - -#include -#include - -#include "../modelitem.hpp" - -class EsmFile : public ModelItem -{ - Q_OBJECT - Q_PROPERTY(QString filename READ fileName) - -public: - EsmFile(QString fileName = QString(), ModelItem *parent = 0); - - ~EsmFile() - {} - - void setFileName(const QString &fileName); - void setAuthor(const QString &author); - void setSize(const int size); - void setDates(const QDateTime &modified, const QDateTime &accessed); - void setVersion(const float version); - void setPath(const QString &path); - void setMasters(const QStringList &masters); - void setDescription(const QString &description); - - 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: - QString mFileName; - QString mAuthor; - int mSize; - QDateTime mModified; - QDateTime mAccessed; - float mVersion; - QString mPath; - QStringList mMasters; - QString mDescription; - -}; - - -#endif diff --git a/components/fileorderlist/model/modelitem.cpp b/components/fileorderlist/model/modelitem.cpp deleted file mode 100644 index 0ff7e45cb..000000000 --- a/components/fileorderlist/model/modelitem.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "modelitem.hpp" - -ModelItem::ModelItem(ModelItem *parent) - : mParentItem(parent) - , QObject(parent) -{ -} - -ModelItem::~ModelItem() -{ - qDeleteAll(mChildItems); -} - - -ModelItem *ModelItem::parent() -{ - return mParentItem; -} - -int ModelItem::row() const -{ - if (mParentItem) - return 1; - //return mParentItem->childRow(const_cast(this)); - //return mParentItem->mChildItems.indexOf(const_cast(this)); - - return -1; -} - - -int ModelItem::childCount() const -{ - return mChildItems.count(); -} - -int ModelItem::childRow(ModelItem *child) const -{ - Q_ASSERT(child); - - return mChildItems.indexOf(child); -} - -ModelItem *ModelItem::child(int row) -{ - return mChildItems.value(row); -} - - -void ModelItem::appendChild(ModelItem *item) -{ - mChildItems.append(item); -} - -void ModelItem::removeChild(int row) -{ - mChildItems.removeAt(row); -} diff --git a/components/fileorderlist/model/modelitem.hpp b/components/fileorderlist/model/modelitem.hpp deleted file mode 100644 index f4cb4322f..000000000 --- a/components/fileorderlist/model/modelitem.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef MODELITEM_HPP -#define MODELITEM_HPP - -#include -#include - -class ModelItem : public QObject -{ - Q_OBJECT - -public: - ModelItem(ModelItem *parent = 0); - ~ModelItem(); - - ModelItem *parent(); - int row() const; - - int childCount() const; - int childRow(ModelItem *child) const; - ModelItem *child(int row); - - void appendChild(ModelItem *child); - void removeChild(int row); - - //virtual bool acceptChild(ModelItem *child); - -protected: - ModelItem *mParentItem; - QList mChildItems; -}; - -#endif diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp deleted file mode 100644 index 6be152b55..000000000 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#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 deleted file mode 100644 index 8fde73236..000000000 --- a/components/fileorderlist/model/pluginsproxymodel.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#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 deleted file mode 100644 index 4d62e1399..000000000 --- a/components/fileorderlist/utils/comboboxlineedit.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#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 deleted file mode 100644 index ba10731ae..000000000 --- a/components/fileorderlist/utils/comboboxlineedit.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/**************************************************************************** -** -** 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/components/fileorderlist/utils/naturalsort.hpp b/components/fileorderlist/utils/naturalsort.hpp deleted file mode 100644 index 59271547a..000000000 --- a/components/fileorderlist/utils/naturalsort.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NATURALSORT_H -#define NATURALSORT_H - -#include - -bool naturalSortLessThanCS( const QString &left, const QString &right ); -bool naturalSortLessThanCI( const QString &left, const QString &right ); -bool naturalSortGreaterThanCS( const QString &left, const QString &right ); -bool naturalSortGreaterThanCI( const QString &left, const QString &right ); - -#endif diff --git a/components/fileorderlist/utils/profilescombobox.hpp b/components/fileorderlist/utils/profilescombobox.hpp deleted file mode 100644 index 08ead9a7a..000000000 --- a/components/fileorderlist/utils/profilescombobox.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef PROFILESCOMBOBOX_HPP -#define PROFILESCOMBOBOX_HPP - -#include - -class QString; -class QRegExpValidator; - -class ProfilesComboBox : public QComboBox -{ - Q_OBJECT -public: - explicit ProfilesComboBox(QWidget *parent = 0); - void setEditEnabled(bool editable); - -signals: - void profileChanged(const QString &previous, const QString ¤t); - void profileRenamed(const QString &oldName, const QString &newName); - -private slots: - void slotEditingFinished(); - void slotIndexChanged(int index); - void slotTextChanged(const QString &text); - -private: - QString mOldProfile; - QRegExpValidator *mValidator; -}; - -#endif // PROFILESCOMBOBOX_HPP diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 75c877dc5..761b7ca5a 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -26,9 +26,10 @@ ConfigurationManager::ConfigurationManager() { setupTokensMapping(); - boost::filesystem::create_directories(mFixedPath.getUserPath()); + boost::filesystem::create_directories(mFixedPath.getUserConfigPath()); + boost::filesystem::create_directories(mFixedPath.getUserDataPath()); - mLogPath = mFixedPath.getUserPath(); + mLogPath = mFixedPath.getUserConfigPath(); } ConfigurationManager::~ConfigurationManager() @@ -39,19 +40,19 @@ void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(mwToken, &FixedPath<>::getInstallPath)); mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); - mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserPath)); + mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserConfigPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description) { - loadConfig(mFixedPath.getUserPath(), variables, description); + loadConfig(mFixedPath.getUserConfigPath(), variables, description); boost::program_options::notify(variables); loadConfig(mFixedPath.getLocalPath(), variables, description); boost::program_options::notify(variables); - loadConfig(mFixedPath.getGlobalPath(), variables, description); + loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); boost::program_options::notify(variables); } @@ -141,12 +142,17 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, const boost::filesystem::path& ConfigurationManager::getGlobalPath() const { - return mFixedPath.getGlobalPath(); + return mFixedPath.getGlobalConfigPath(); } -const boost::filesystem::path& ConfigurationManager::getUserPath() const +const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const { - return mFixedPath.getUserPath(); + return mFixedPath.getUserConfigPath(); +} + +const boost::filesystem::path& ConfigurationManager::getUserDataPath() const +{ + return mFixedPath.getUserDataPath(); } const boost::filesystem::path& ConfigurationManager::getLocalPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 4df871664..35144fe04 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -36,7 +36,7 @@ struct ConfigurationManager /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; - const boost::filesystem::path& getUserPath() const; + const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getGlobalDataPath() const; diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp deleted file mode 100644 index ce2d95f57..000000000 --- a/components/files/filelibrary.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "filelibrary.hpp" - -#include - -#include -#include <../components/misc/stringops.hpp> - -namespace Files -{ - // Looks for a string in a vector of strings - bool containsVectorString(const StringVector& list, const std::string& str) - { - for (StringVector::const_iterator iter = list.begin(); - iter != list.end(); ++iter) - { - if (*iter == str) - return true; - } - return false; - } - - // Searches a path and adds the results to the library - void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict, - const StringVector &acceptableExtensions) - { - if (!boost::filesystem::exists(root)) - { - std::cout << "Warning " << root.string() << " does not exist.\n"; - return; - } - - std::string fileExtension; - std::string type; - - // remember the last location of the priority list when listing new items - int length = mPriorityList.size(); - - // First makes a list of all candidate files - FileLister(root, mPriorityList, recursive); - - // Then sort these files into sections according to the folder they belong to - for (PathContainer::iterator listIter = mPriorityList.begin() + length; - listIter != mPriorityList.end(); ++listIter) - { - if( !acceptableExtensions.empty() ) - { - fileExtension = boost::filesystem::path (listIter->extension()).string(); - Misc::StringUtils::toLower(fileExtension); - if(!containsVectorString(acceptableExtensions, fileExtension)) - continue; - } - - type = boost::filesystem::path (listIter->parent_path().leaf()).string(); - if (!strict) - Misc::StringUtils::toLower(type); - - mMap[type].push_back(*listIter); - // std::cout << "Added path: " << listIter->string() << " in section "<< type <second); - } - } - - // Searches the library for an item and returns a boost path to it - boost::filesystem::path FileLibrary::locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName) - { - boost::filesystem::path result(""); - if (sectionName == "") - { - return FileListLocator(mPriorityList, boost::filesystem::path(item), strict, ignoreExtensions); - } - else - { - if (!containsSection(sectionName, strict)) - { - std::cout << "Warning: There is no section named " << sectionName << "\n"; - return result; - } - result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict, ignoreExtensions); - } - return result; - } - - // Prints all the available sections, used for debugging - void FileLibrary::printSections() - { - for(StringPathContMap::const_iterator mapIter = mMap.begin(); - mapIter != mMap.end(); ++mapIter) - { - std::cout << mapIter->first < - -namespace Files -{ - typedef std::map StringPathContMap; - typedef std::vector StringVector; - - /// Looks for a string in a vector of strings - bool containsVectorString(const StringVector& list, const std::string& str); - - /// \brief Searches directories and makes lists of files according to folder name - class FileLibrary - { - private: - StringPathContMap mMap; - PathContainer mEmptyPath; - PathContainer mPriorityList; - - public: - /// Searches a path and adds the results to the library - /// Recursive search and fs strict options are available - /// Takes a vector of acceptable files extensions, if none is given it lists everything. - void add(const boost::filesystem::path &root, bool recursive, bool strict, - const StringVector &acceptableExtensions); - - /// Returns true if the named section exists - /// You can run this check before running section() - bool containsSection(std::string sectionName, bool strict); - - /// Returns a pointer to const for a section of the library - /// which is essentially a PathContainer. - /// If the section does not exists it returns a pointer to an empty path. - const PathContainer* section(std::string sectionName, bool strict); - - /// Searches the library for an item and returns a boost path to it - /// Optionally you can provide a specific section - /// The result is the first that comes up according to alphabetical - /// section naming - boost::filesystem::path locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName=""); - - /// Prints all the available sections, used for debugging - void printSections(); - }; -} - -#endif diff --git a/components/files/fileops.cpp b/components/files/fileops.cpp deleted file mode 100644 index fbc2eef05..000000000 --- a/components/files/fileops.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "fileops.hpp" - -#include - -#include -#include -#include <../components/misc/stringops.hpp> - -namespace Files -{ - -bool isFile(const char *name) -{ - return boost::filesystem::exists(boost::filesystem::path(name)); -} - - // Returns true if the last part of the superset matches the subset - bool endingMatches(const std::string& superset, const std::string& subset) - { - if (subset.length() > superset.length()) - return false; - return superset.substr(superset.length() - subset.length()) == subset; - } - - // Makes a list of files from a directory - void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive) - { - if (!boost::filesystem::exists(currentPath)) - { - std::cout << "WARNING: " << currentPath.string() << " does not exist.\n"; - return ; - } - if (recursive) - { - for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string()); - itr != end; ++itr ) - { - if ( boost::filesystem::is_regular_file(*itr)) - list.push_back(itr->path()); - } - } - else - { - for ( boost::filesystem::directory_iterator end, itr(currentPath.string()); - itr != end; ++itr ) - { - if ( boost::filesystem::is_regular_file(*itr)) - list.push_back(itr->path()); - } - } - } - - // Locates path in path container - boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, - bool strict, bool ignoreExtensions) - { - boost::filesystem::path result(""); - if (list.empty()) - return result; - - std::string toFindStr; - if (ignoreExtensions) - toFindStr = boost::filesystem::basename(toFind); - else - toFindStr = toFind.string(); - - std::string fullPath; - - // The filesystems slash sets the default slash - std::string slash; - std::string wrongslash; - if(list[0].string().find("\\") != std::string::npos) - { - slash = "\\"; - wrongslash = "/"; - } - else - { - slash = "/"; - wrongslash = "\\"; - } - - // The file being looked for is converted to the new slash - if(toFindStr.find(wrongslash) != std::string::npos ) - { - boost::replace_all(toFindStr, wrongslash, slash); - } - - if (!strict) - { - Misc::StringUtils::toLower(toFindStr); - } - - for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it) - { - fullPath = it->string(); - if (ignoreExtensions) - fullPath.erase(fullPath.length() - - boost::filesystem::path (it->extension()).string().length()); - - if (!strict) - { - Misc::StringUtils::toLower(fullPath); - } - if(endingMatches(fullPath, toFindStr)) - { - result = *it; - break; - } - } - return result; - } - - // Overloaded form of the locator that takes a string and returns a string - std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions) - { - return FileListLocator(list, boost::filesystem::path(toFind), strict, ignoreExtensions).string(); - } - -} diff --git a/components/files/fileops.hpp b/components/files/fileops.hpp deleted file mode 100644 index bf1c51485..000000000 --- a/components/files/fileops.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef COMPONENTS_FILES_FILEOPS_HPP -#define COMPONENTS_FILES_FILEOPS_HPP - -#include -#include -#include - -#include - -namespace Files -{ - -///\brief Check if a given path is an existing file (not a directory) -///\param [in] name - filename -bool isFile(const char *name); - - /// A vector of Boost Paths, very handy - typedef std::vector PathContainer; - - /// Makes a list of files from a directory by taking a boost - /// path and a Path Container and adds to the Path container - /// all files in the path. It has a recursive option. - void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive); - - /// Locates boost path in path container - /// returns the path from the container - /// that contains the searched path. - /// If it's not found it returns and empty path - /// Takes care of slashes, backslashes and it has a strict option. - boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, - bool strict, bool ignoreExtensions); - - /// Overloaded form of the locator that takes a string and returns a string - std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions); - -} - -#endif /* COMPONENTS_FILES_FILEOPS_HPP */ diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp index a309dc9fb..cfd3458ce 100644 --- a/components/files/fixedpath.hpp +++ b/components/files/fixedpath.hpp @@ -48,8 +48,9 @@ struct FixedPath */ FixedPath(const std::string& application_name) : mPath(application_name + "/") - , mUserPath(mPath.getUserPath()) - , mGlobalPath(mPath.getGlobalPath()) + , mUserConfigPath(mPath.getUserConfigPath()) + , mUserDataPath(mPath.getUserDataPath()) + , mGlobalConfigPath(mPath.getGlobalConfigPath()) , mLocalPath(mPath.getLocalPath()) , mGlobalDataPath(mPath.getGlobalDataPath()) , mInstallPath(mPath.getInstallPath()) @@ -59,28 +60,27 @@ struct FixedPath /** * \brief Return path pointing to the user local configuration directory. - * - * \return boost::filesystem::path */ - const boost::filesystem::path& getUserPath() const + const boost::filesystem::path& getUserConfigPath() const + { + return mUserConfigPath; + } + + const boost::filesystem::path& getUserDataPath() const { - return mUserPath; + return mUserDataPath; } /** * \brief Return path pointing to the global (system) configuration directory. - * - * \return boost::filesystem::path */ - const boost::filesystem::path& getGlobalPath() const + const boost::filesystem::path& getGlobalConfigPath() const { - return mGlobalPath; + return mGlobalConfigPath; } /** * \brief Return path pointing to the directory where application was started. - * - * \return boost::filesystem::path */ const boost::filesystem::path& getLocalPath() const { @@ -105,8 +105,9 @@ struct FixedPath private: PathType mPath; - boost::filesystem::path mUserPath; /**< User path */ - boost::filesystem::path mGlobalPath; /**< Global path */ + boost::filesystem::path mUserConfigPath; /**< User path */ + boost::filesystem::path mUserDataPath; + boost::filesystem::path mGlobalConfigPath; /**< Global path */ boost::filesystem::path mLocalPath; /**< It is the same directory where application was run */ boost::filesystem::path mGlobalDataPath; /**< Global application data path */ diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index c974a91d3..d285f4229 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -8,6 +8,39 @@ #include #include + +namespace +{ + boost::filesystem::path getUserHome() + { + const char* dir = getenv("HOME"); + if (dir == NULL) + { + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) + { + dir = pwd->pw_dir; + } + } + if (dir == NULL) + return boost::filesystem::path(); + else + return boost::filesystem::path(dir); + } + + boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback) + { + const char* result = getenv(envVariable.c_str()); + if (!result) + return fallback; + boost::filesystem::path dir(result); + if (dir.empty()) + return fallback; + else + return dir; + } +} + /** * \namespace Files */ @@ -19,51 +52,22 @@ LinuxPath::LinuxPath(const std::string& application_name) { } -boost::filesystem::path LinuxPath::getUserPath() const +boost::filesystem::path LinuxPath::getUserConfigPath() const { - boost::filesystem::path userPath("."); - - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir); - } + return getEnv("XDG_CONFIG_HOME", getUserHome() / ".config") / mName; +} - return userPath / ".config" / mName; +boost::filesystem::path LinuxPath::getUserDataPath() const +{ + return getEnv("XDG_DATA_HOME", getUserHome() / ".local/share") / mName; } boost::filesystem::path LinuxPath::getCachePath() const { - boost::filesystem::path userPath("."); - - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir); - } - - return userPath / ".cache" / mName; + return getEnv("XDG_CACHE_HOME", getUserHome() / ".cache") / mName; } -boost::filesystem::path LinuxPath::getGlobalPath() const +boost::filesystem::path LinuxPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("/etc/"); return globalPath / mName; @@ -84,17 +88,9 @@ boost::filesystem::path LinuxPath::getInstallPath() const { boost::filesystem::path installPath; - char *homePath = getenv("HOME"); - if (homePath == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - homePath = pwd->pw_dir; - } - } + boost::filesystem::path homePath = getUserHome(); - if (homePath != NULL) + if (!homePath.empty()) { boost::filesystem::path wineDefaultRegistry(homePath); wineDefaultRegistry /= ".wine/system.reg"; diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp index 6acf2a2d5..b710165b4 100644 --- a/components/files/linuxpath.hpp +++ b/components/files/linuxpath.hpp @@ -20,44 +20,34 @@ struct LinuxPath /** * \brief Return path to the user directory. - * - * \return boost::filesystem::path */ - boost::filesystem::path getUserPath() const; + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; /** - * \brief Return path to the global (system) directory where game files could be placed. - * - * \return boost::filesystem::path + * \brief Return path to the global (system) directory where config files can be placed. */ - boost::filesystem::path getGlobalPath() const; + boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime configuration directory which is the * place where an application was started. - * - * \return boost::filesystem::path */ boost::filesystem::path getLocalPath() const; /** - * \brief - * - * \return boost::filesystem::path + * \brief Return path to the global (system) directory where game files can be placed. */ boost::filesystem::path getGlobalDataPath() const; /** * \brief - * - * \return boost::filesystem::path */ boost::filesystem::path getCachePath() const; /** * \brief Gets the path of the installed Morrowind version if there is one. - * - * \return boost::filesystem::path */ boost::filesystem::path getInstallPath() const; diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 9edcd6ef2..3e53f5306 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -11,9 +11,26 @@ * FIXME: Someone with MacOS system should check this and correct if necessary */ -/** - * \namespace Files - */ +namespace +{ + boost::filesystem::path getUserHome() + { + const char* dir = getenv("HOME"); + if (dir == NULL) + { + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) + { + dir = pwd->pw_dir; + } + } + if (dir == NULL) + return boost::filesystem::path(); + else + return boost::filesystem::path(dir); + } +} + namespace Files { @@ -22,28 +39,24 @@ MacOsPath::MacOsPath(const std::string& application_name) { } -boost::filesystem::path MacOsPath::getUserPath() const +boost::filesystem::path MacOsPath::getUserConfigPath() const { - boost::filesystem::path userPath("."); + boost::filesystem::path userPath (getUserHome()); + userPath /= "Library/Preferences/"; - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir) / "Library/Preferences/"; - } + return userPath / mName; +} + +boost::filesystem::path MacOsPath::getUserDataPath() const +{ + // TODO: probably wrong? + boost::filesystem::path userPath (getUserHome()); + userPath /= "Library/Preferences/"; return userPath / mName; } -boost::filesystem::path MacOsPath::getGlobalPath() const +boost::filesystem::path MacOsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("/Library/Preferences/"); return globalPath / mName; @@ -51,23 +64,9 @@ boost::filesystem::path MacOsPath::getGlobalPath() const boost::filesystem::path MacOsPath::getCachePath() const { - boost::filesystem::path userPath("."); - - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir) / "Library/Caches" / mName; - } - - return userPath; + boost::filesystem::path userPath (getUserHome()); + userPath /= "Library/Caches"; + return userPath / mName; } boost::filesystem::path MacOsPath::getLocalPath() const @@ -85,17 +84,9 @@ boost::filesystem::path MacOsPath::getInstallPath() const { boost::filesystem::path installPath; - char *homePath = getenv("HOME"); - if (homePath == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - homePath = pwd->pw_dir; - } - } + boost::filesystem::path homePath = getUserHome(); - if (homePath != NULL) + if (!homePath.empty()) { boost::filesystem::path wineDefaultRegistry(homePath); wineDefaultRegistry /= ".wine/system.reg"; diff --git a/components/files/macospath.hpp b/components/files/macospath.hpp index 576ec1681..7a7dc5577 100644 --- a/components/files/macospath.hpp +++ b/components/files/macospath.hpp @@ -23,14 +23,16 @@ struct MacOsPath * * \return boost::filesystem::path */ - boost::filesystem::path getUserPath() const; + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; /** * \brief Return path to the global (system) directory. * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalPath() const; + boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime directory which is the diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index e8f1a2b08..ea1ca56d3 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -25,7 +25,7 @@ WindowsPath::WindowsPath(const std::string& application_name) { } -boost::filesystem::path WindowsPath::getUserPath() const +boost::filesystem::path WindowsPath::getUserConfigPath() const { boost::filesystem::path userPath("."); @@ -41,7 +41,13 @@ boost::filesystem::path WindowsPath::getUserPath() const return userPath / mName; } -boost::filesystem::path WindowsPath::getGlobalPath() const +boost::filesystem::path WindowsPath::getUserDataPath() const +{ + // Have some chaos, windows people! + return getUserConfigPath(); +} + +boost::filesystem::path WindowsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("."); @@ -63,12 +69,12 @@ boost::filesystem::path WindowsPath::getLocalPath() const boost::filesystem::path WindowsPath::getGlobalDataPath() const { - return getGlobalPath(); + return getGlobalConfigPath(); } boost::filesystem::path WindowsPath::getCachePath() const { - return getUserPath() / "cache"; + return getUserConfigPath() / "cache"; } boost::filesystem::path WindowsPath::getInstallPath() const diff --git a/components/files/windowspath.hpp b/components/files/windowspath.hpp index 6044b67c2..31d0e0e7c 100644 --- a/components/files/windowspath.hpp +++ b/components/files/windowspath.hpp @@ -29,14 +29,16 @@ struct WindowsPath * * \return boost::filesystem::path */ - boost::filesystem::path getUserPath() const; + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; /** * \brief Returns "X:\Program Files\" * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalPath() const; + boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return local path which is a location where diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 5774c96ae..6f3b5bb5a 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -64,7 +64,7 @@ namespace Interpreter{ retval << context.getActionBinding("#{sRestKey}"); } else if((found = Check(temp, "actionmenumode", &i, &start))){ - retval << context.getActionBinding("#{sJournal}"); + retval << context.getActionBinding("#{sInventory}"); } else if((found = Check(temp, "actionactivate", &i, &start))){ retval << context.getActionBinding("#{sActivate}"); @@ -88,10 +88,10 @@ namespace Interpreter{ retval << context.getActionBinding("#{sBack}"); } else if((found = Check(temp, "actionuse", &i, &start))){ - retval << "PLACEHOLDER_ACTION_USE"; + retval << context.getActionBinding("#{sUse}"); } else if((found = Check(temp, "actionrun", &i, &start))){ - retval << "PLACEHOLDER_ACTION_RUN"; + retval << context.getActionBinding("#{sRun}"); } else if((found = Check(temp, "pcclass", &i, &start))){ retval << context.getPCClass(); diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index 10937e6bc..ea1e9fc91 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -166,31 +166,37 @@ namespace Interpreter void Interpreter::installSegment0 (int code, Opcode1 *opcode) { + assert(mSegment0.find(code) == mSegment0.end()); mSegment0.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment1 (int code, Opcode2 *opcode) { + assert(mSegment1.find(code) == mSegment1.end()); mSegment1.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment2 (int code, Opcode1 *opcode) { + assert(mSegment2.find(code) == mSegment2.end()); mSegment2.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment3 (int code, Opcode1 *opcode) { + assert(mSegment3.find(code) == mSegment3.end()); mSegment3.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment4 (int code, Opcode2 *opcode) { + assert(mSegment4.find(code) == mSegment4.end()); mSegment4.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment5 (int code, Opcode0 *opcode) { + assert(mSegment5.find(code) == mSegment5.end()); mSegment5.insert (std::make_pair (code, opcode)); } diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 1b4c823a0..1da8cf695 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -48,9 +48,10 @@ namespace Interpreter } else if (c=='f' || c=='F' || c=='.') { - while (c!='f' && i range) : - cur (range.first), nxt (range.first), end (range.second) + cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar()) { } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 011e0e445..e44f4a6f3 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -306,7 +306,7 @@ public: class NiFlipController : public Controller { public: - int mTexSlot; + int mTexSlot; // NiTexturingProperty::TextureType float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources NiSourceTextureList mSources; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 402eadefb..0f7e658fb 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -210,7 +210,7 @@ static const RecordFactoryEntry recordFactories [] = { { "AvoidNode", &construct , RC_AvoidNode }, { "NiBSParticleNode", &construct , RC_NiBSParticleNode }, { "NiBSAnimationNode", &construct , RC_NiBSAnimationNode }, - { "NiBillboardNode", &construct , RC_NiNode }, + { "NiBillboardNode", &construct , RC_NiBillboardNode }, { "NiTriShape", &construct , RC_NiTriShape }, { "NiRotatingParticles", &construct , RC_NiRotatingParticles }, { "NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles }, diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6e629772e..91ae93b40 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -200,7 +200,7 @@ struct KeyListT { } } else - nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } }; typedef KeyListT FloatKeyList; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 917bc8add..eebcd8be8 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -137,7 +137,11 @@ struct NiNode : Node AnimFlag_AutoPlay = 0x0020 }; enum BSParticleFlags { - ParticleFlag_AutoPlay = 0x0020 + ParticleFlag_AutoPlay = 0x0020, + ParticleFlag_LocalSpace = 0x0080 + }; + enum ControllerFlags { + ControllerFlag_Active = 0x8 }; void read(NIFStream *nif) diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 87e342dca..079b335f0 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,7 @@ enum RecordType { RC_MISSING = 0, RC_NiNode, + RC_NiBillboardNode, RC_AvoidNode, RC_NiTriShape, RC_NiRotatingParticles, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 9c4fee7a0..cdddb94d0 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -229,9 +229,13 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * { if(node->hasBounds) { - mShape->mBoxTranslation = node->boundPos; - mShape->mBoxRotation = node->boundRot; - mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ)); + // Checking for BBoxCollision flag causes issues with some actors :/ + if (!(node->flags & Nif::NiNode::Flag_Hidden)) + { + mShape->mBoxTranslation = node->boundPos; + mShape->mBoxRotation = node->boundRot; + mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ)); + } } else if(node->recType == Nif::RC_NiTriShape) { diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp new file mode 100644 index 000000000..6d7f6ab3f --- /dev/null +++ b/components/nifogre/controller.hpp @@ -0,0 +1,95 @@ +#ifndef COMPONENTS_NIFOGRE_CONTROLLER_H +#define COMPONENTS_NIFOGRE_CONTROLLER_H + +#include +#include + +namespace NifOgre +{ + + class ValueInterpolator + { + protected: + float interpKey(const Nif::FloatKeyList::VecType &keys, float time, float def=0.f) const + { + if (keys.size() == 0) + return def; + + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::FloatKeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } + + Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) const + { + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::Vector3KeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } + }; + + // FIXME: Should not be here. + class DefaultFunction : public Ogre::ControllerFunction + { + private: + float mFrequency; + float mPhase; + float mStartTime; + public: + float mStopTime; + + public: + DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) + : Ogre::ControllerFunction(deltaInput) + , mFrequency(ctrl->frequency) + , mPhase(ctrl->phase) + , mStartTime(ctrl->timeStart) + , mStopTime(ctrl->timeStop) + { + if(mDeltaInput) + mDeltaCount = mPhase; + } + + virtual Ogre::Real calculate(Ogre::Real value) + { + if(mDeltaInput) + { + mDeltaCount += value*mFrequency; + if(mDeltaCount < mStartTime) + mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, + mStopTime - mStartTime); + mDeltaCount = std::fmod(mDeltaCount - mStartTime, + mStopTime - mStartTime) + mStartTime; + return mDeltaCount; + } + + value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); + return value; + } + }; + +} + +#endif diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 55f064c55..4dae1a93d 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -10,8 +10,6 @@ #include #include -#include -#include #include @@ -152,11 +150,11 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, Nif::ControllerPtr ctrls = texprop->controller; while(!ctrls.empty()) { - warn("Unhandled texture controller "+ctrls->recName+" in "+name); + if (ctrls->recType != Nif::RC_NiFlipController) // Handled in ogrenifloader + warn("Unhandled texture controller "+ctrls->recName+" in "+name); ctrls = ctrls->next; } } - needTangents = !texName[Nif::NiTexturingProperty::BumpTexture].empty(); // Alpha modifiers if(alphaprop) @@ -237,7 +235,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, Nif::ControllerPtr ctrls = matprop->controller; while(!ctrls.empty()) { - warn("Unhandled material controller "+ctrls->recName+" in "+name); + if (ctrls->recType != Nif::RC_NiAlphaController && ctrls->recType != Nif::RC_NiMaterialColorController) + warn("Unhandled material controller "+ctrls->recName+" in "+name); ctrls = ctrls->next; } } @@ -276,6 +275,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, if (itr != sMaterialMap.end()) { // a suitable material exists already - use it + sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(itr->second); + needTangents = !sh::retrieveValue(instance->getProperty("normalMap"), instance).get().empty(); return itr->second; } // not found, create a new one @@ -323,6 +324,12 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture])); instance->setProperty("detailMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DetailTexture])); instance->setProperty("emissiveMap", sh::makeProperty(texName[Nif::NiTexturingProperty::GlowTexture])); + instance->setProperty("darkMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DarkTexture])); + if (!texName[Nif::NiTexturingProperty::BaseTexture].empty()) + { + instance->setProperty("use_diffuse_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("diffuseMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::BaseTexture].uvSet))); + } if (!texName[Nif::NiTexturingProperty::GlowTexture].empty()) { instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true))); @@ -333,30 +340,44 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("use_detail_map", sh::makeProperty(new sh::BooleanValue(true))); instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet))); } + if (!texName[Nif::NiTexturingProperty::DarkTexture].empty()) + { + instance->setProperty("use_dark_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("darkMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DarkTexture].uvSet))); + } + + bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() + && texName[Nif::NiTexturingProperty::BumpTexture].find("_nh.") != std::string::npos; + instance->setProperty("use_parallax", sh::makeProperty(new sh::BooleanValue(useParallax))); for(int i = 0;i < 7;i++) { if(i == Nif::NiTexturingProperty::BaseTexture || i == Nif::NiTexturingProperty::DetailTexture || + i == Nif::NiTexturingProperty::DarkTexture || i == Nif::NiTexturingProperty::BumpTexture || i == Nif::NiTexturingProperty::GlowTexture) continue; if(!texName[i].empty()) - warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i)); + warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i) + " in " + name); } 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[0]); - if (result.first) + // Override alpha flags based on our override list (transparency-overrides.cfg) + if (!texName[0].empty()) { - alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */ - alphaTest = result.second; - depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on + NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]); + if (result.first) + { + alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */ + alphaTest = result.second; + depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on + } } + // Add transparency if NiAlphaProperty was present if((alphaFlags&1)) { std::string blend_mode; @@ -385,7 +406,12 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off"))); // depth_func??? - sh::Factory::getInstance()._ensureMaterial(name, "Default"); + if (!texName[0].empty()) + NifOverrides::Overrides::getMaterialOverrides(texName[0], instance); + + // Don't use texName, as it may be overridden + needTangents = !sh::retrieveValue(instance->getProperty("normalMap"), instance).get().empty(); + return name; } diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index 8843ac6c6..b02c7c236 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -37,9 +37,9 @@ class NIFMaterialLoader { static std::map sMaterialMap; +public: static std::string findTextureName(const std::string &filename); -public: static Ogre::String getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index ca92f62d4..43622cb9a 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -116,19 +116,6 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape Ogre::HardwareBuffer::Usage vertUsage = Ogre::HardwareBuffer::HBU_STATIC; bool vertShadowBuffer = false; - if(!shape->controller.empty()) - { - Nif::ControllerPtr ctrl = shape->controller; - do { - if(ctrl->recType == Nif::RC_NiGeomMorpherController) - { - vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY; - vertShadowBuffer = true; - break; - } - } while(!(ctrl=ctrl->next).empty()); - } - if(skin != NULL) { vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY; @@ -347,6 +334,40 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest)) mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest); } + + + if(!shape->controller.empty()) + { + Nif::ControllerPtr ctrl = shape->controller; + do { + // Load GeomMorpherController into an Ogre::Pose and Animation + if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) + { + const Nif::NiGeomMorpherController *geom = + static_cast(ctrl.getPtr()); + + const std::vector& morphs = geom->data.getPtr()->mMorphs; + // Note we are not interested in morph 0, which just contains the original vertices + for (unsigned int i = 1; i < morphs.size(); ++i) + { + Ogre::Pose* pose = mesh->createPose(i); + const Nif::NiMorphData::MorphData& data = morphs[i]; + for (unsigned int v = 0; v < data.mVertices.size(); ++v) + pose->addVertex(v, data.mVertices[v]); + + Ogre::String animationID = Ogre::StringConverter::toString(ctrl->recIndex) + + "_" + Ogre::StringConverter::toString(i); + Ogre::VertexAnimationTrack* track = + mesh->createAnimation(animationID, 0) + ->createVertexTrack(1, Ogre::VAT_POSE); + Ogre::VertexPoseKeyFrame* keyframe = track->createVertexPoseKeyFrame(0); + keyframe->addPoseReference(i-1, 1); + } + + break; + } + } while(!(ctrl=ctrl->next).empty()); + } } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 3bb9ea230..43e8934c8 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -26,7 +26,6 @@ #include #include -#include #include #include #include @@ -36,6 +35,11 @@ #include #include #include +#include +#include +#include + +#include #include #include @@ -43,47 +47,245 @@ #include "skeleton.hpp" #include "material.hpp" #include "mesh.hpp" +#include "controller.hpp" namespace NifOgre { -// FIXME: Should not be here. -class DefaultFunction : public Ogre::ControllerFunction +Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableObject *movable) { -private: - float mFrequency; - float mPhase; - float mStartTime; - float mStopTime; + if (mClonedMaterials.find(movable) != mClonedMaterials.end()) + return mClonedMaterials[movable]; -public: - DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) - : Ogre::ControllerFunction(deltaInput) - , mFrequency(ctrl->frequency) - , mPhase(ctrl->phase) - , mStartTime(ctrl->timeStart) - , mStopTime(ctrl->timeStop) + else + { + Ogre::MaterialPtr mat; + if (Ogre::Entity* ent = dynamic_cast(movable)) + mat = ent->getSubEntity(0)->getMaterial(); + else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) + mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); + + static int count=0; + Ogre::String newName = mat->getName() + Ogre::StringConverter::toString(count++); + sh::Factory::getInstance().createMaterialInstance(newName, mat->getName()); + // Make sure techniques are created + sh::Factory::getInstance()._ensureMaterial(newName, "Default"); + mat = Ogre::MaterialManager::getSingleton().getByName(newName); + + mClonedMaterials[movable] = mat; + + if (Ogre::Entity* ent = dynamic_cast(movable)) + ent->getSubEntity(0)->setMaterial(mat); + else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) + partSys->setMaterialName(mat->getName()); + + return mat; + } +} + +MaterialControllerManager::~MaterialControllerManager() +{ + for (std::map::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it) + { + sh::Factory::getInstance().destroyMaterialInstance(it->second->getName()); + } +} + +ObjectScene::~ObjectScene() +{ + for(size_t i = 0;i < mLights.size();i++) + { + Ogre::Light *light = mLights[i]; + // If parent is a scene node, it was created specifically for this light. Destroy it now. + if(light->isAttached() && !light->isParentTagPoint()) + mSceneMgr->destroySceneNode(light->getParentSceneNode()); + mSceneMgr->destroyLight(light); + } + for(size_t i = 0;i < mParticles.size();i++) + mSceneMgr->destroyParticleSystem(mParticles[i]); + for(size_t i = 0;i < mEntities.size();i++) + mSceneMgr->destroyEntity(mEntities[i]); + mControllers.clear(); + mLights.clear(); + mParticles.clear(); + mEntities.clear(); + mSkelBase = NULL; +} + +void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera) +{ + for (std::vector::iterator it = mBillboardNodes.begin(); it != mBillboardNodes.end(); ++it) { - if(mDeltaInput) - mDeltaCount = mPhase; + assert(mSkelBase); + Ogre::Node* node = *it; + node->_setDerivedOrientation(mSkelBase->getParentNode()->_getDerivedOrientation().Inverse() * + camera->getRealOrientation()); } +} - virtual Ogre::Real calculate(Ogre::Real value) +// Animates a texture +class FlipController +{ +public: + class Value : public Ogre::ControllerValue { - if(mDeltaInput) + private: + Ogre::MovableObject* mMovable; + int mTexSlot; + float mDelta; + std::vector mTextures; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiFlipController *ctrl, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mMaterialControllerMgr(materialControllerMgr) { - mDeltaCount += value*mFrequency; - if(mDeltaCount < mStartTime) - mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, - mStopTime - mStartTime); - mDeltaCount = std::fmod(mDeltaCount - mStartTime, - mStopTime - mStartTime) + mStartTime; - return mDeltaCount; + mTexSlot = ctrl->mTexSlot; + mDelta = ctrl->mDelta; + for (unsigned int i=0; imSources.length(); ++i) + { + const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr(); + if (!tex->external) + std::cerr << "Warning: Found internal texture, ignoring." << std::endl; + mTextures.push_back(NIFMaterialLoader::findTextureName(tex->filename)); + } } - value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); - return value; - } + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + if (mDelta == 0) + return; + int curTexture = int(time / mDelta) % mTextures.size(); + + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::Pass::TextureUnitStateIterator textures = pass->getTextureUnitStateIterator(); + while (textures.hasMoreElements()) + { + Ogre::TextureUnitState *texture = textures.getNext(); + if ((texture->getName() == "diffuseMap" && mTexSlot == Nif::NiTexturingProperty::BaseTexture) + || (texture->getName() == "normalMap" && mTexSlot == Nif::NiTexturingProperty::BumpTexture) + || (texture->getName() == "detailMap" && mTexSlot == Nif::NiTexturingProperty::DetailTexture) + || (texture->getName() == "darkMap" && mTexSlot == Nif::NiTexturingProperty::DarkTexture) + || (texture->getName() == "emissiveMap" && mTexSlot == Nif::NiTexturingProperty::GlowTexture)) + texture->setTextureName(mTextures[curTexture]); + } + } + } + } + }; + + typedef DefaultFunction Function; +}; + +class AlphaController +{ +public: + class Value : public Ogre::ControllerValue, public ValueInterpolator + { + private: + Ogre::MovableObject* mMovable; + Nif::FloatKeyList mData; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiFloatData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mData(data->mKeyList) + , mMaterialControllerMgr(materialControllerMgr) + { + } + + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + float value = interpKey(mData.mKeys, time); + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = value; + pass->setDiffuse(diffuse); + } + } + } + }; + + typedef DefaultFunction Function; +}; + +class MaterialColorController +{ +public: + class Value : public Ogre::ControllerValue, public ValueInterpolator + { + private: + Ogre::MovableObject* mMovable; + Nif::Vector3KeyList mData; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiPosData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mData(data->mKeyList) + , mMaterialControllerMgr(materialControllerMgr) + { + } + + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + Ogre::Vector3 value = interpKey(mData.mKeys, time); + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.r = value.x; + diffuse.g = value.y; + diffuse.b = value.z; + pass->setDiffuse(diffuse); + } + } + } + }; + + typedef DefaultFunction Function; }; class VisController @@ -107,24 +309,28 @@ public: return mData.back().isSet; } - // FIXME: We are not getting all objects here. Skinned meshes get - // attached to the object's root node, and won't be connected via a - // TagPoint. static void setVisible(Ogre::Node *node, int vis) { + // Skinned meshes are attached to the scene node, not the bone. + // We use the Node's user data to connect it with the mesh. + Ogre::Any customData = node->getUserObjectBindings().getUserAny(); + + if (!customData.isEmpty()) + Ogre::any_cast(customData)->setVisible(vis); + + Ogre::TagPoint *tag = dynamic_cast(node); + if(tag != NULL) + { + Ogre::MovableObject *obj = tag->getChildObject(); + if(obj != NULL) + obj->setVisible(vis); + } + Ogre::Node::ChildNodeIterator iter = node->getChildIterator(); while(iter.hasMoreElements()) { node = iter.getNext(); setVisible(node, vis); - - Ogre::TagPoint *tag = dynamic_cast(node); - if(tag != NULL) - { - Ogre::MovableObject *obj = tag->getChildObject(); - if(obj != NULL) - obj->setVisible(vis); - } } } @@ -162,48 +368,14 @@ public: class KeyframeController { public: - class Value : public NodeTargetValue + class Value : public NodeTargetValue, public ValueInterpolator { private: Nif::QuaternionKeyList mRotations; Nif::Vector3KeyList mTranslations; Nif::FloatKeyList mScales; - static float interpKey(const Nif::FloatKeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } - - static Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::Vector3KeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } + using ValueInterpolator::interpKey; static Ogre::Quaternion interpKey(const Nif::QuaternionKeyList::VecType &keys, float time) { @@ -275,43 +447,24 @@ public: class UVController { public: - class Value : public Ogre::ControllerValue + class Value : public Ogre::ControllerValue, public ValueInterpolator { private: - Ogre::MaterialPtr mMaterial; + Ogre::MovableObject* mMovable; Nif::FloatKeyList mUTrans; Nif::FloatKeyList mVTrans; Nif::FloatKeyList mUScale; Nif::FloatKeyList mVScale; - - static float lookupValue(const Nif::FloatKeyList &keys, float time, float def) - { - if(keys.mKeys.size() == 0) - return def; - - if(time <= keys.mKeys.front().mTime) - return keys.mKeys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.mKeys.begin()+1); - for(;iter != keys.mKeys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.mKeys.back().mValue; - } + MaterialControllerManager* mMaterialControllerMgr; public: - Value(const Ogre::MaterialPtr &material, const Nif::NiUVData *data) - : mMaterial(material) + Value(Ogre::MovableObject* movable, const Nif::NiUVData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) , mUTrans(data->mKeyList[0]) , mVTrans(data->mKeyList[1]) , mUScale(data->mKeyList[2]) , mVScale(data->mKeyList[3]) + , mMaterialControllerMgr(materialControllerMgr) { } virtual Ogre::Real getValue() const @@ -322,12 +475,14 @@ public: virtual void setValue(Ogre::Real value) { - float uTrans = lookupValue(mUTrans, value, 0.0f); - float vTrans = lookupValue(mVTrans, value, 0.0f); - float uScale = lookupValue(mUScale, value, 1.0f); - float vScale = lookupValue(mVScale, value, 1.0f); + float uTrans = interpKey(mUTrans.mKeys, value, 0.0f); + float vTrans = interpKey(mVTrans.mKeys, value, 0.0f); + float uScale = interpKey(mUScale.mKeys, value, 1.0f); + float vScale = interpKey(mVScale.mKeys, value, 1.0f); - Ogre::Material::TechniqueIterator techs = mMaterial->getTechniqueIterator(); + Ogre::MaterialPtr material = mMaterialControllerMgr->getWritableMaterial(mMovable); + + Ogre::Material::TechniqueIterator techs = material->getTechniqueIterator(); while(techs.hasMoreElements()) { Ogre::Technique *tech = techs.getNext(); @@ -379,17 +534,22 @@ public: class GeomMorpherController { public: - class Value : public Ogre::ControllerValue + class Value : public Ogre::ControllerValue, public ValueInterpolator { private: - Ogre::SubEntity *mSubEntity; + Ogre::Entity *mEntity; std::vector mMorphs; + size_t mControllerIndex; + + std::vector mVertices; public: - Value(Ogre::SubEntity *subent, const Nif::NiMorphData *data) - : mSubEntity(subent) + Value(Ogre::Entity *ent, const Nif::NiMorphData *data, size_t controllerIndex) + : mEntity(ent) , mMorphs(data->mMorphs) - { } + , mControllerIndex(controllerIndex) + { + } virtual Ogre::Real getValue() const { @@ -397,9 +557,25 @@ public: return 0.0f; } - virtual void setValue(Ogre::Real value) + virtual void setValue(Ogre::Real time) { - // TODO: Implement + if (mMorphs.size() <= 1) + return; + int i = 1; + for (std::vector::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i) + { + float val = 0; + if (!it->mData.mKeys.empty()) + val = interpKey(it->mData.mKeys, time); + val = std::max(0.f, std::min(1.f, val)); + + Ogre::String animationID = Ogre::StringConverter::toString(mControllerIndex) + + "_" + Ogre::StringConverter::toString(i); + + Ogre::AnimationState* state = mEntity->getAnimationState(animationID); + state->setEnabled(val > 0); + state->setWeight(val); + } } }; @@ -418,15 +594,8 @@ class NIFObjectLoader std::cerr << "NIFObjectLoader: Warn: " << msg << std::endl; } - static void fail(const std::string &msg) - { - std::cerr << "NIFObjectLoader: Fail: "<< msg << std::endl; - abort(); - } - - static void createEntity(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectList &objectlist, + Ogre::SceneManager *sceneMgr, ObjectScenePtr scene, const Nif::Node *node, int flags, int animflags) { const Nif::NiTriShape *shape = static_cast(node); @@ -441,63 +610,147 @@ class NIFObjectLoader NIFMeshLoader::createMesh(name, fullname, group, shape->recIndex); Ogre::Entity *entity = sceneMgr->createEntity(fullname); + +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + // Enable skeleton-based bounding boxes. With the static bounding box, + // the animation may cause parts to go outside the box and cause culling problems. + if (entity->hasSkeleton()) + entity->setUpdateBoundingBoxFromSkeleton(true); +#endif + entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - objectlist.mEntities.push_back(entity); - if(objectlist.mSkelBase) + scene->mEntities.push_back(entity); + if(scene->mSkelBase) { + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast(entity))); + if(entity->hasSkeleton()) - entity->shareSkeletonInstanceWith(objectlist.mSkelBase); + entity->shareSkeletonInstanceWith(scene->mSkelBase); else - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), entity); - } + scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity); } Nif::ControllerPtr ctrl = node->controller; while(!ctrl.empty()) { - if(ctrl->recType == Nif::RC_NiUVController) + if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) { - const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); + if(ctrl->recType == Nif::RC_NiUVController) + { + const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); - const Ogre::MaterialPtr &material = entity->getSubEntity(0)->getMaterial(); - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(material, uv->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr)); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if(ctrl->recType == Nif::RC_NiGeomMorpherController) - { - const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); + UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); - Ogre::SubEntity *subent = entity->getSubEntity(0); - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(subent, geom->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + else if(ctrl->recType == Nif::RC_NiGeomMorpherController) + { + const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); + + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value( + entity, geom->data.getPtr(), geom->recIndex)); + + GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } } ctrl = ctrl->next; } + + createMaterialControllers(shape, entity, animflags, scene); } + static void createMaterialControllers (const Nif::Node* node, Ogre::MovableObject* movable, int animflags, ObjectScenePtr scene) + { + const Nif::NiTexturingProperty *texprop = NULL; + const Nif::NiMaterialProperty *matprop = NULL; + const Nif::NiAlphaProperty *alphaprop = NULL; + const Nif::NiVertexColorProperty *vertprop = NULL; + const Nif::NiZBufferProperty *zprop = NULL; + const Nif::NiSpecularProperty *specprop = NULL; + const Nif::NiWireframeProperty *wireprop = NULL; + node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + + if(matprop) + { + Nif::ControllerPtr ctrls = matprop->controller; + while(!ctrls.empty()) + { + if (ctrls->recType == Nif::RC_NiAlphaController) + { + const Nif::NiAlphaController *alphaCtrl = dynamic_cast(ctrls.getPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); + AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + else if (ctrls->recType == Nif::RC_NiMaterialColorController) + { + const Nif::NiMaterialColorController *matCtrl = dynamic_cast(ctrls.getPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); + MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + + ctrls = ctrls->next; + } + } + if (texprop) + { + Nif::ControllerPtr ctrls = texprop->controller; + while(!ctrls.empty()) + { + if (ctrls->recType == Nif::RC_NiFlipController) + { + const Nif::NiFlipController *flipCtrl = dynamic_cast(ctrls.getPtr()); + + + Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value( + movable, flipCtrl, &scene->mMaterialControllerMgr)); + FlipController::Function* function = OGRE_NEW FlipController::Function(flipCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + + ctrls = ctrls->next; + } + } + } - static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl) + static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, + const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone, + const std::string& skelBaseName) { Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif"); emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, partctrl->velocity + partctrl->velocityRandom*0.5f); emitter->setEmissionRate(partctrl->emitRate); - emitter->setTimeToLive(partctrl->lifetime - partctrl->lifetimeRandom*0.5f, - partctrl->lifetime + partctrl->lifetimeRandom*0.5f); + emitter->setTimeToLive(partctrl->lifetime, + partctrl->lifetime + partctrl->lifetimeRandom); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y)); emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z)); @@ -526,6 +779,8 @@ class NIFObjectLoader affector->setParameter("force_type", (gr->mType==0) ? "wind" : "point"); affector->setParameter("direction", Ogre::StringConverter::toString(gr->mDirection)); affector->setParameter("position", Ogre::StringConverter::toString(gr->mPosition)); + affector->setParameter("skelbase", skelBaseName); + affector->setParameter("bone", bone->getName()); } else if(e->recType == Nif::RC_NiParticleColorModifier) { @@ -558,8 +813,8 @@ class NIFObjectLoader } static void createParticleSystem(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectList &objectlist, - const Nif::Node *partnode, int flags, int partflags) + Ogre::SceneNode *sceneNode, ObjectScenePtr scene, + const Nif::Node *partnode, int flags, int partflags, int animflags) { const Nif::NiAutoNormalParticlesData *particledata = NULL; if(partnode->recType == Nif::RC_NiAutoNormalParticles) @@ -572,7 +827,7 @@ class NIFObjectLoader fullname += "@type="+partnode->name; Misc::StringUtils::toLower(fullname); - Ogre::ParticleSystem *partsys = sceneMgr->createParticleSystem(); + Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem(); const Nif::NiTexturingProperty *texprop = NULL; const Nif::NiMaterialProperty *matprop = NULL; @@ -589,83 +844,99 @@ class NIFObjectLoader vertprop, zprop, specprop, wireprop, needTangents)); - partsys->setDefaultDimensions(particledata->particleRadius*2.0f, - particledata->particleRadius*2.0f); partsys->setCullIndividually(false); partsys->setParticleQuota(particledata->numParticles); - // TODO: There is probably a field or flag to specify this, as some - // particle effects have it and some don't. - partsys->setKeepParticlesInLocalSpace(true); + partsys->setKeepParticlesInLocalSpace(partflags & (Nif::NiNode::ParticleFlag_LocalSpace)); + + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + scene->mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) { - if(ctrl->recType == Nif::RC_NiParticleSystemController) + if(ctrl->recType == Nif::RC_NiParticleSystemController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); - createParticleEmitterAffectors(partsys, partctrl); - if(!partctrl->emitter.empty() && !partsys->isAttached()) + partsys->setDefaultDimensions(partctrl->size*2, partctrl->size*2); + + if(!partctrl->emitter.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + // Set the emitter bone as user data on the particle system + // so the emitters/affectors can access it easily. + partsys->getUserObjectBindings().setUserAny(Ogre::Any(trgtbone)); + createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName()); } Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW ParticleSystemController::Value(partsys, partctrl)); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay))); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + ParticleSystemController::Function* function = + OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + + if (partflags&Nif::NiNode::ParticleFlag_AutoPlay) + partsys->fastForward(1, 0.1); } ctrl = ctrl->next; } - if(!partsys->isAttached()) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); - } - partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - objectlist.mParticles.push_back(partsys); + scene->mParticles.push_back(partsys); + + createMaterialControllers(partnode, partsys, animflags, scene); } - static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectList &objectlist, int animflags) + static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) { do { - if(ctrl->recType == Nif::RC_NiVisController) - { - const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); - - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); - - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if(ctrl->recType == Nif::RC_NiKeyframeController) + if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) { - const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - if(!key->data.empty()) + if(ctrl->recType == Nif::RC_NiVisController) { + const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + else if(ctrl->recType == Nif::RC_NiKeyframeController) + { + const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); + if(!key->data.empty()) + { + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + // The keyframe controller will control this bone manually + trgtbone->setManuallyControlled(true); + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); + KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } } } ctrl = ctrl->next; @@ -713,8 +984,8 @@ class NIFObjectLoader static void createObjects(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, const Nif::Node *node, - ObjectList &objectlist, int flags, int animflags, int partflags) + Ogre::SceneNode *sceneNode, const Nif::Node *node, + ObjectScenePtr scene, int flags, int animflags, int partflags) { // Do not create objects for the collision shape (includes all children) if(node->recType == Nif::RC_RootCollisionNode) @@ -732,6 +1003,17 @@ class NIFObjectLoader else flags |= node->flags; + if (node->recType == Nif::RC_NiBillboardNode) + { + // TODO: figure out what the flags mean. + // NifSkope has names for them, but doesn't implement them. + // Change mBillboardNodes to map + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); + Ogre::Bone* bone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + bone->setManuallyControlled(true); + scene->mBillboardNodes.push_back(bone); + } + Nif::ExtraPtr e = node->extra; while(!e.empty()) { @@ -739,8 +1021,11 @@ class NIFObjectLoader { const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); - extractTextKeys(tk, objectlist.mTextKeys[trgtid]); + if (scene->mSkelBase) + { + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); + extractTextKeys(tk, scene->mTextKeys[trgtid]); + } } else if(e->recType == Nif::RC_NiStringExtraData) { @@ -758,8 +1043,8 @@ class NIFObjectLoader e = e->extra; } - if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode)) - createNodeControllers(name, node->controller, objectlist, animflags); + if(!node->controller.empty()) + createNodeControllers(name, node->controller, scene, animflags); if(node->recType == Nif::RC_NiCamera) { @@ -768,13 +1053,13 @@ class NIFObjectLoader if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) { - createEntity(name, group, sceneMgr, objectlist, node, flags, animflags); + createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags); } if((node->recType == Nif::RC_NiAutoNormalParticles || node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) { - createParticleSystem(name, group, sceneMgr, objectlist, node, flags, partflags); + createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags); } const Nif::NiNode *ninode = dynamic_cast(node); @@ -784,14 +1069,14 @@ class NIFObjectLoader for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createObjects(name, group, sceneMgr, children[i].getPtr(), objectlist, flags, animflags, partflags); + createObjects(name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags); } } } static void createSkelBase(const std::string &name, const std::string &group, Ogre::SceneManager *sceneMgr, const Nif::Node *node, - ObjectList &objectlist) + ObjectScenePtr scene) { /* 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 @@ -800,12 +1085,12 @@ class NIFObjectLoader if(meshMgr.getByName(name).isNull()) NIFMeshLoader::createMesh(name, name, group, ~(size_t)0); - objectlist.mSkelBase = sceneMgr->createEntity(name); - objectlist.mEntities.push_back(objectlist.mSkelBase); + scene->mSkelBase = sceneMgr->createEntity(name); + scene->mEntities.push_back(scene->mSkelBase); } public: - static void load(Ogre::SceneManager *sceneMgr, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) + static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0) { Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); if(nif->numRoots() < 1) @@ -829,9 +1114,9 @@ public: !NIFSkeletonLoader::createSkeleton(name, group, node).isNull()) { // Create a base skeleton entity if this NIF needs one - createSkelBase(name, group, sceneMgr, node, objectlist); + createSkelBase(name, group, sceneNode->getCreator(), node, scene); } - createObjects(name, group, sceneMgr, node, objectlist, flags, 0, 0); + createObjects(name, group, sceneNode, node, scene, flags, 0, 0); } static void loadKf(Ogre::Skeleton *skel, const std::string &name, @@ -875,6 +1160,9 @@ public: continue; } + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); @@ -894,37 +1182,37 @@ public: }; -ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group); + NIFObjectLoader::load(parentNode, scene, name, group); - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(!entity->isAttached()) parentNode->attachObject(entity); } - return objectlist; + return scene; } -ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, +ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group); + NIFObjectLoader::load(parentNode, scene, name, group); bool isskinned = false; - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *ent = objectlist.mEntities[i]; - if(objectlist.mSkelBase != ent && ent->hasSkeleton()) + Ogre::Entity *ent = scene->mEntities[i]; + if(scene->mSkelBase != ent && ent->hasSkeleton()) { isskinned = true; break; @@ -937,29 +1225,36 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena if(isskinned) { + // Apparently both are allowed. Sigh. + // This could also mean that filters are supposed to work on the actual node + // hierarchy, rather than just trishapes, and the 'tri ' should be omitted? std::string filter = "@shape=tri "+bonename; + std::string filter2 = "@shape="+bonename; Misc::StringUtils::toLower(filter); - for(size_t i = 0;i < objectlist.mEntities.size();i++) + Misc::StringUtils::toLower(filter2); + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(entity->hasSkeleton()) { - if(entity == objectlist.mSkelBase || - entity->getMesh()->getName().find(filter) != std::string::npos) + if(entity == scene->mSkelBase || + entity->getMesh()->getName().find(filter) != std::string::npos + || entity->getMesh()->getName().find(filter2) != std::string::npos) parentNode->attachObject(entity); } else { - if(entity->getMesh()->getName().find(filter) == std::string::npos) + if(entity->getMesh()->getName().find(filter) == std::string::npos + || entity->getMesh()->getName().find(filter2) == std::string::npos) entity->detachFromParent(); } } } else { - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(!entity->isAttached()) { Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity); @@ -968,21 +1263,21 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } } - return objectlist; + return scene; } -ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group, 0xC0000000); + NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000); - if(objectlist.mSkelBase) - parentNode->attachObject(objectlist.mSkelBase); + if(scene->mSkelBase) + parentNode->attachObject(scene->mSkelBase); - return objectlist; + return scene; } diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index edad13a9a..badb6ccd3 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -25,6 +25,7 @@ #define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP #include +#include #include #include @@ -37,36 +38,68 @@ namespace NifOgre { +/** + * @brief Clones materials as necessary to not make controllers affect other objects (that share the original material). + */ +class MaterialControllerManager +{ +public: + ~MaterialControllerManager(); + + /// @attention if \a movable is an Entity, it needs to have *one* SubEntity + Ogre::MaterialPtr getWritableMaterial (Ogre::MovableObject* movable); + +private: + std::map mClonedMaterials; +}; + typedef std::multimap TextKeyMap; static const char sTextKeyExtraDataID[] = "TextKeyExtraData"; -struct ObjectList { +struct ObjectScene { Ogre::Entity *mSkelBase; std::vector mEntities; std::vector mParticles; std::vector mLights; + // Nodes that should always face the camera when rendering + std::vector mBillboardNodes; + + Ogre::SceneManager* mSceneMgr; + + // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. + float mMaxControllerLength; + std::map mTextKeys; + MaterialControllerManager mMaterialControllerMgr; + std::vector > mControllers; - ObjectList() : mSkelBase(0) + ObjectScene(Ogre::SceneManager* sceneMgr) : mSkelBase(0), mMaxControllerLength(0), mSceneMgr(sceneMgr) { } + + ~ObjectScene(); + + // Rotate nodes in mBillboardNodes so they face the given camera + void rotateBillboardNodes(Ogre::Camera* camera); }; +typedef Ogre::SharedPtr ObjectScenePtr; + class Loader { public: - static ObjectList createObjects(Ogre::Entity *parent, const std::string &bonename, + static ObjectScenePtr createObjects(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); - static ObjectList createObjects(Ogre::SceneNode *parentNode, + static ObjectScenePtr createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); - static ObjectList createObjectBase(Ogre::SceneNode *parentNode, + static ObjectScenePtr createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); diff --git a/libs/openengine/ogre/particles.cpp b/components/nifogre/particles.cpp similarity index 88% rename from libs/openengine/ogre/particles.cpp rename to components/nifogre/particles.cpp index 707bd75e0..a1433a669 100644 --- a/libs/openengine/ogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -5,11 +5,22 @@ #include #include #include +#include +#include +#include +#include +#include +#include /* FIXME: "Nif" isn't really an appropriate emitter name. */ class NifEmitter : public Ogre::ParticleEmitter { public: + Ogre::Bone* mEmitterBone; + Ogre::Bone* mParticleBone; + + Ogre::ParticleSystem* getPartSys() { return mParent; } + /** Command object for the emitter width (see Ogre::ParamCommand).*/ class CmdWidth : public Ogre::ParamCommand { @@ -120,6 +131,9 @@ public: NifEmitter(Ogre::ParticleSystem *psys) : Ogre::ParticleEmitter(psys) { + mEmitterBone = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()); + Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); + mParticleBone = static_cast(tag->getParent()); initDefaults("Nif"); } @@ -142,22 +156,40 @@ public: yOff = Ogre::Math::SymmetricRandom() * mYRange; zOff = Ogre::Math::SymmetricRandom() * mZRange; - particle->position = mPosition + xOff + yOff + zOff; +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3& position = particle->mPosition; + Ogre::Vector3& direction = particle->mDirection; + Ogre::ColourValue& colour = particle->mColour; + Ogre::Real& totalTimeToLive = particle->mTotalTimeToLive; + Ogre::Real& timeToLive = particle->mTimeToLive; +#else + Ogre::Vector3& position = particle->position; + Ogre::Vector3& direction = particle->direction; + Ogre::ColourValue& colour = particle->colour; + Ogre::Real& totalTimeToLive = particle->totalTimeToLive; + Ogre::Real& timeToLive = particle->timeToLive; +#endif + + position = xOff + yOff + zOff + + mParticleBone->_getDerivedOrientation().Inverse() * (mEmitterBone->_getDerivedPosition() + - mParticleBone->_getDerivedPosition()); // Generate complex data by reference - genEmissionColour(particle->colour); + genEmissionColour(colour); // NOTE: We do not use mDirection/mAngle for the initial direction. Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom(); Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom(); - particle->direction = (Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * + direction = (mParticleBone->_getDerivedOrientation().Inverse() + * mEmitterBone->_getDerivedOrientation() * + Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) * Ogre::Vector3::UNIT_Z; - genEmissionVelocity(particle->direction); + genEmissionVelocity(direction); // Generate simpler data - particle->timeToLive = particle->totalTimeToLive = genEmissionTTL(); + timeToLive = totalTimeToLive = genEmissionTTL(); } /** Overloaded to update the trans. matrix */ @@ -337,9 +369,9 @@ NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd; Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys) { - Ogre::ParticleEmitter *emit = OGRE_NEW NifEmitter(psys); - mEmitters.push_back(emit); - return emit; + Ogre::ParticleEmitter *emitter = OGRE_NEW NifEmitter(psys); + mEmitters.push_back(emitter); + return emitter; } @@ -404,9 +436,13 @@ public: /** See Ogre::ParticleAffector. */ void _initParticle(Ogre::Particle *particle) { +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + const Ogre::Real life_time = particle->mTotalTimeToLive; + Ogre::Real particle_time = particle->mTimeToLive; +#else const Ogre::Real life_time = particle->totalTimeToLive; Ogre::Real particle_time = particle->timeToLive; - +#endif Ogre::Real width = mParent->getDefaultWidth(); Ogre::Real height = mParent->getDefaultHeight(); if(life_time-particle_time < mGrowTime) @@ -431,9 +467,13 @@ public: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); - const Ogre::Real life_time = p->totalTimeToLive; - Ogre::Real particle_time = p->timeToLive; - +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + const Ogre::Real life_time = p->mTotalTimeToLive; + Ogre::Real particle_time = p->mTimeToLive; +#else + const Ogre::Real life_time = p->totalTimeToLive; + Ogre::Real particle_time = p->timeToLive; +#endif Ogre::Real width = mParent->getDefaultWidth(); Ogre::Real height = mParent->getDefaultHeight(); if(life_time-particle_time < mGrowTime) @@ -492,6 +532,11 @@ class GravityAffector : public Ogre::ParticleAffector }; public: + Ogre::Bone* mEmitterBone; + Ogre::Bone* mParticleBone; + + Ogre::ParticleSystem* getPartSys() { return mParent; } + /** Command object for force (see Ogre::ParamCommand).*/ class CmdForce : public Ogre::ParamCommand { @@ -586,6 +631,10 @@ public: , mPosition(0.0f) , mDirection(0.0f) { + mEmitterBone = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()); + Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); + mParticleBone = static_cast(tag->getParent()); + mType = "Gravity"; // Init parameters @@ -656,7 +705,11 @@ protected: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + p->mDirection += vec; +#else p->direction += vec; +#endif } } @@ -667,8 +720,18 @@ protected: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); - const Ogre::Vector3 vec = (p->position - mPosition).normalisedCopy() * force; +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3 position = p->mPosition; +#else + Ogre::Vector3 position = p->position; +#endif + + Ogre::Vector3 vec = (mPosition - position).normalisedCopy() * force; +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + p->mDirection += vec; +#else p->direction += vec; +#endif } } diff --git a/libs/openengine/ogre/particles.hpp b/components/nifogre/particles.hpp similarity index 100% rename from libs/openengine/ogre/particles.hpp rename to components/nifogre/particles.hpp diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index 04bffdeab..c0482cf5e 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -30,6 +30,7 @@ void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, node->recType == Nif::RC_RootCollisionNode || /* handled in nifbullet (hopefully) */ node->recType == Nif::RC_NiTriShape || /* Handled in the mesh loader */ node->recType == Nif::RC_NiBSAnimationNode || /* Handled in the object loader */ + node->recType == Nif::RC_NiBillboardNode || /* Handled in the object loader */ node->recType == Nif::RC_NiBSParticleNode || node->recType == Nif::RC_NiCamera || node->recType == Nif::RC_NiAutoNormalParticles || @@ -85,14 +86,23 @@ bool NIFSkeletonLoader::needSkeleton(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, there + * There are no bones used for skinning, there are no keyframe controllers, there * are no nodes named "AttachLight", and the tree consists of NiNode, * NiTriShape, and RootCollisionNode types only. */ if(node->boneTrafo) return true; - if(!node->controller.empty() || node->name == "AttachLight") + if(!node->controller.empty()) + { + Nif::ControllerPtr ctrl = node->controller; + do { + if(ctrl->recType == Nif::RC_NiKeyframeController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) + return true; + } while(!(ctrl=ctrl->next).empty()); + } + + if (node->name == "AttachLight") return true; if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode) diff --git a/components/nifogre/tests/.gitignore b/components/nifogre/tests/.gitignore deleted file mode 100644 index 1a5569983..000000000 --- a/components/nifogre/tests/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.png -meshlist.txt -ogre.cfg -*_test -chris* diff --git a/components/nifogre/tests/Makefile b/components/nifogre/tests/Makefile deleted file mode 100644 index a7c50d100..000000000 --- a/components/nifogre/tests/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -GCC=g++ - -all: ogre_manualresource_test ogre_nif_test ogre_skeleton_test - -I_OGRE=$(shell pkg-config --cflags OGRE) -L_OGRE=$(shell pkg-config --libs OGRE) - -ogre_manualresource_test: ogre_manualresource_test.cpp - $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) - -ogre_skeleton_test: ogre_skeleton_test.cpp - $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) - -ogre_nif_test: ogre_nif_test.cpp ../../nif/nif_file.cpp ../../bsa/bsa_file.cpp ../../bsa/bsa_archive.cpp ../../tools/stringops.cpp ../../mangle/vfs/servers/ogre_vfs.cpp ../ogre_nif_loader.cpp - $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) - -clean: - rm *_test - diff --git a/components/nifogre/tests/ogre_common.cpp b/components/nifogre/tests/ogre_common.cpp deleted file mode 100644 index 657913f30..000000000 --- a/components/nifogre/tests/ogre_common.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include - -using namespace std; -using namespace Ogre; - -Root *root; -RenderWindow *window; -SceneManager *mgr; - -int shot = 0; - -// Lets you quit by closing the window -struct QuitListener : FrameListener -{ - bool frameStarted(const FrameEvent& evt) - { -#ifdef SCREENSHOT - if(shot == 1) window->writeContentsToFile("nif.png"); - if(shot < 2) shot++; -#endif - - if(window->isClosed()) - return false; - return true; - } -} qlistener; - -// This has to be packaged in a struct because C++ sucks -struct C -{ - static void doTest(); -}; - -int main(int argc, char**args) -{ - // Disable Ogre logging - new LogManager; - Log *log = LogManager::getSingleton().createLog(""); - log->setDebugOutputEnabled(false); - - // Set up Root. - root = new Root("plugins.cfg","ogre.cfg",""); - - if(!root->restoreConfig()) - { - cout << "WARNING: we do NOT recommend fullscreen mode!\n"; - if(!root->showConfigDialog()) - return 1; - } - - mgr = root->createSceneManager(ST_GENERIC); - - // Only render if there are arguments on the command line (we don't - // care what they are.) - bool render = (argc>=2); - - // Create a window - window = root->initialise(true, "Test"); - if(render) - { - // More initialization - Camera *cam = mgr->createCamera("cam"); - Viewport *vp = window->addViewport(cam); - cam->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight())); - cam->setFOVy(Degree(55)); - cam->setPosition(0,0,0); - cam->lookAt(0,0,10); - cam->setNearClipDistance(1); - - root->addFrameListener(&qlistener); - - // Background color - vp->setBackgroundColour(ColourValue(0.5,0.5,0.5)); - - mgr->setAmbientLight(ColourValue(1,1,1)); - } - - // Run the actual test - C::doTest(); - - // Render loop - if(render) - { - cout << "Rendering. Close the window to exit.\n"; - root->startRendering(); - } - - // Cleanup - delete root; - return 0; -} - -void doTest() -{ - cout << "hello\n"; -} diff --git a/components/nifogre/tests/ogre_manualresource_test.cpp b/components/nifogre/tests/ogre_manualresource_test.cpp deleted file mode 100644 index 75e169d54..000000000 --- a/components/nifogre/tests/ogre_manualresource_test.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - This is a test of the manual resource loader interface to Ogre, - applied to manually created meshes. It defines a simple mesh - consisting of two triangles, and creates three instances of it as - different meshes using the same loader. It is a precursor to the NIF - loading code. If the Ogre interface changes and you have to change - this test, then you will also have to change parts of the NIF - loader. - */ - -#include "ogre_mesh_common.cpp" - -void C::doTest() -{ - // Create a couple of manual meshes - makeMesh("mesh1.mm"); - makeMesh("mesh2.mm"); - makeMesh("mesh3.mm"); - - // Display the meshes - { - SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node"); - Entity *ent = mgr->createEntity("Mesh1", "mesh1.mm"); - node->attachObject(ent); - node->setPosition(3,1,8); - } - - { - SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node2"); - Entity *ent = mgr->createEntity("Mesh2", "mesh2.mm"); - node->attachObject(ent); - node->setPosition(-3,1,8); - } - { - SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node3"); - Entity *ent = mgr->createEntity("Mesh3", "mesh3.mm"); - node->attachObject(ent); - node->setPosition(0,-2,8); - } -} diff --git a/components/nifogre/tests/ogre_mesh_common.cpp b/components/nifogre/tests/ogre_mesh_common.cpp deleted file mode 100644 index 72e51e331..000000000 --- a/components/nifogre/tests/ogre_mesh_common.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "ogre_common.cpp" - -struct MyMeshLoader : ManualResourceLoader -{ - void loadResource(Resource *resource) - { - Mesh *mesh = dynamic_cast(resource); - assert(mesh); - - const String& name = mesh->getName(); - cout << "Manually loading mesh " << name << endl; - - // Create the mesh here - int numVerts = 4; - int numFaces = 2*3; - const float vertices[] = - { -1,-1,0, 1,-1,0, - 1,1,0, -1,1,0 }; - - const short faces[] = - { 0,2,1, 0,3,2 }; - - mesh->sharedVertexData = new VertexData(); - mesh->sharedVertexData->vertexCount = numVerts; - - VertexDeclaration* decl = mesh->sharedVertexData->vertexDeclaration; - - decl->addElement(0, 0, VET_FLOAT3, VES_POSITION); - - HardwareVertexBufferSharedPtr vbuf = - HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT3), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); - - // Upload the vertex data to the card - vbuf->writeData(0, vbuf->getSizeInBytes(), vertices, true); - - // Set vertex buffer binding so buffer 0 is bound to our vertex buffer - VertexBufferBinding* bind = mesh->sharedVertexData->vertexBufferBinding; - bind->setBinding(0, vbuf); - - /// Allocate index buffer of the requested number of faces - HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton(). - createIndexBuffer(HardwareIndexBuffer::IT_16BIT, - numFaces, - HardwareBuffer::HBU_STATIC_WRITE_ONLY); - - /// Upload the index data to the card - ibuf->writeData(0, ibuf->getSizeInBytes(), faces, true); - - SubMesh* sub = mesh->createSubMesh(name+"tris"); - sub->useSharedVertices = true; - - /// Set parameters of the submesh - sub->indexData->indexBuffer = ibuf; - sub->indexData->indexCount = numFaces; - sub->indexData->indexStart = 0; - - mesh->_setBounds(AxisAlignedBox(-1.1,-1.1,-1.1,1.1,1.1,1.1)); - mesh->_setBoundingSphereRadius(2); - } -}; - -MyMeshLoader mml; - -MeshPtr makeMesh(const string &name) -{ - return MeshManager::getSingleton().createManual(name, "General", &mml); -} diff --git a/components/nifogre/tests/ogre_nif_test.cpp b/components/nifogre/tests/ogre_nif_test.cpp deleted file mode 100644 index decd43df5..000000000 --- a/components/nifogre/tests/ogre_nif_test.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "../ogre_nif_loader.hpp" -#include "../../bsa/bsa_archive.hpp" - -//#define SCREENSHOT - -#include "ogre_common.cpp" - -//const char* mesh = "meshes\\a\\towershield_steel.nif"; -//const char* mesh = "meshes\\r\\bonelord.nif"; -//const char* mesh = "meshes\\m\\text_scroll_open_01.nif"; -const char* mesh = "meshes\\f\\ex_ashl_a_banner_r.nif"; - -void C::doTest() -{ - // Add Morrowind.bsa resource location - Bsa::addBSA("../../data/Morrowind.bsa"); - - // Insert the mesh - NifOgre::NIFLoader::load(mesh); - NifOgre::NIFLoader::load(mesh); - - /* - SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node"); - Entity *ent = mgr->createEntity("Mesh1", mesh); - node->attachObject(ent); - - // Works great for the scroll - node->setPosition(0,4,50); - node->pitch(Degree(20)); - node->roll(Degree(10)); - node->yaw(Degree(-10)); - - /* Bone lord - node->setPosition(0,-70,170); - node->pitch(Degree(-90)); - */ - - // Display it from two different angles - shield and banner - const int sep = 45; - SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node"); - Entity *ent = mgr->createEntity("Mesh1", mesh); - node->attachObject(ent); - node->setPosition(sep,0,130); - node = node->createChildSceneNode("node2"); - ent = mgr->createEntity("Mesh2", mesh); - node->attachObject(ent); - node->setPosition(-2*sep,0,0); - node->yaw(Degree(180)); - //*/ -} diff --git a/components/nifogre/tests/ogre_skeleton_test.cpp b/components/nifogre/tests/ogre_skeleton_test.cpp deleted file mode 100644 index df9139b95..000000000 --- a/components/nifogre/tests/ogre_skeleton_test.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "ogre_common.cpp" - -void C::doTest() -{ - SkeletonManager &skm = SkeletonManager::getSingleton(); - - SkeletonPtr skp = skm.create("MySkel", "General"); - - cout << "hello\n"; - /* - MeshPtr msh = makeMesh("mesh1"); - - // Display the mesh - { - SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node"); - Entity *ent = mgr->createEntity("Mesh1", "mesh1"); - node->attachObject(ent); - node->setPosition(0,0,4); - } - */ -} diff --git a/components/nifogre/tests/output/ogre_manualresource_test.out b/components/nifogre/tests/output/ogre_manualresource_test.out deleted file mode 100644 index 2eab2d50d..000000000 --- a/components/nifogre/tests/output/ogre_manualresource_test.out +++ /dev/null @@ -1,3 +0,0 @@ -Manually loading mesh mesh1.mm -Manually loading mesh mesh2.mm -Manually loading mesh mesh3.mm diff --git a/components/nifogre/tests/output/ogre_nif_test.out b/components/nifogre/tests/output/ogre_nif_test.out deleted file mode 100644 index e69de29bb..000000000 diff --git a/components/nifogre/tests/output/ogre_skeleton_test.out b/components/nifogre/tests/output/ogre_skeleton_test.out deleted file mode 100644 index ce0136250..000000000 --- a/components/nifogre/tests/output/ogre_skeleton_test.out +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/components/nifogre/tests/plugins.cfg b/components/nifogre/tests/plugins.cfg deleted file mode 100644 index 9133aec32..000000000 --- a/components/nifogre/tests/plugins.cfg +++ /dev/null @@ -1,12 +0,0 @@ -# Defines plugins to load - -# Define plugin folder -PluginFolder=/usr/local/lib/OGRE - -# Define plugins -Plugin=RenderSystem_GL -Plugin=Plugin_ParticleFX -Plugin=Plugin_OctreeSceneManager - - - diff --git a/components/nifogre/tests/test.sh b/components/nifogre/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/components/nifogre/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/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/nifoverrides/nifoverrides.cpp b/components/nifoverrides/nifoverrides.cpp index 191b4ac2f..972cf1b84 100644 --- a/components/nifoverrides/nifoverrides.cpp +++ b/components/nifoverrides/nifoverrides.cpp @@ -4,14 +4,51 @@ #include <../components/misc/stringops.hpp> +#include "../extern/shiny/Main/MaterialInstance.hpp" + +#include + using namespace NifOverrides; -Ogre::ConfigFile Overrides::mTransparencyOverrides = Ogre::ConfigFile(); +Overrides::TransparencyOverrideMap Overrides::mTransparencyOverrides = Overrides::TransparencyOverrideMap(); +Overrides::MaterialOverrideMap Overrides::mMaterialOverrides = Overrides::MaterialOverrideMap(); void Overrides::loadTransparencyOverrides (const std::string& file) { - mTransparencyOverrides.load(file); + Ogre::ConfigFile cf; + cf.load(file); + + Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + mTransparencyOverrides[sectionName] = + Ogre::StringConverter::parseInt(cf.getSetting("alphaRejectValue", sectionName)); + seci.getNext(); + } +} + +void Overrides::loadMaterialOverrides(const std::string &file) +{ + Ogre::ConfigFile cf; + cf.load(file); + + Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + + Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); + Ogre::ConfigFile::SettingsMultiMap::iterator i; + std::map overrides; + for (i = settings->begin(); i != settings->end(); ++i) + { + overrides[i->first] = i->second; + } + mMaterialOverrides[sectionName] = overrides; + } + } TransparencyResult Overrides::getTransparencyOverride(const std::string& texture) @@ -19,20 +56,25 @@ TransparencyResult Overrides::getTransparencyOverride(const std::string& texture TransparencyResult result; result.first = false; - std::string tex = texture; - Misc::StringUtils::toLower(tex); + TransparencyOverrideMap::iterator it = mTransparencyOverrides.find(Misc::StringUtils::lowerCase(texture)); + if (it != mTransparencyOverrides.end()) + { + result.first = true; + result.second = it->second; + } - Ogre::ConfigFile::SectionIterator seci = mTransparencyOverrides.getSectionIterator(); - while (seci.hasMoreElements()) + return result; +} + +void Overrides::getMaterialOverrides(const std::string &texture, sh::MaterialInstance* material) +{ + MaterialOverrideMap::iterator it = mMaterialOverrides.find(Misc::StringUtils::lowerCase(texture)); + if (it != mMaterialOverrides.end()) { - Ogre::String sectionName = seci.peekNextKey(); - if (sectionName == tex) + const std::map& overrides = it->second; + for (std::map::const_iterator it = overrides.begin(); it != overrides.end(); ++it) { - result.first = true; - result.second = Ogre::StringConverter::parseInt(mTransparencyOverrides.getSetting("alphaRejectValue", sectionName)); - break; + material->setProperty(it->first, sh::makeProperty(it->second)); } - seci.getNext(); } - return result; } diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp index ba2e4cc3c..edff876d4 100644 --- a/components/nifoverrides/nifoverrides.hpp +++ b/components/nifoverrides/nifoverrides.hpp @@ -3,19 +3,34 @@ #include +namespace sh +{ + class MaterialInstance; +} + namespace NifOverrides { typedef std::pair TransparencyResult; - /// \brief provide overrides for some model / texture properties that bethesda has chosen poorly + /// Allows to provide overrides for some material properties in NIF files. + /// NIFs are a bit limited in that they don't allow specifying a material externally, which is + /// painful for texture modding. + /// We also use this to patch up transparency settings in certain NIFs that bethesda has chosen poorly. class Overrides { public: - static Ogre::ConfigFile mTransparencyOverrides; + typedef std::map TransparencyOverrideMap; + static TransparencyOverrideMap mTransparencyOverrides; + + typedef std::map > MaterialOverrideMap; + static MaterialOverrideMap mMaterialOverrides; + void loadTransparencyOverrides (const std::string& file); + void loadMaterialOverrides (const std::string& file); static TransparencyResult getTransparencyOverride(const std::string& texture); + static void getMaterialOverrides (const std::string& texture, sh::MaterialInstance* instance); }; } diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp new file mode 100644 index 000000000..c8fc621c7 --- /dev/null +++ b/components/ogreinit/ogreinit.cpp @@ -0,0 +1,165 @@ +#include "ogreinit.hpp" + +#include + +#include +#include +#include + +#include + +#include "ogreplugin.hpp" + +namespace OgreInit +{ + + OgreInit::OgreInit() + : mRoot(NULL) + #ifdef ENABLE_PLUGIN_CgProgramManager + , mCgPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + , mOctreePlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + , mParticleFXPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_GL + , mGLPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + , mD3D9Plugin(NULL) + #endif + {} + + Ogre::Root* OgreInit::init(const std::string &logPath) + { + // Set up logging first + new Ogre::LogManager; + Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); + + // Disable logging to cout/cerr + log->setDebugOutputEnabled(false); + + mRoot = new Ogre::Root("", "", ""); + + #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) + loadStaticPlugins(); + #else + loadPlugins(); + #endif + + loadParticleFactories(); + + return mRoot; + } + + OgreInit::~OgreInit() + { + delete mRoot; + + std::vector::iterator ei; + for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei) + OGRE_DELETE (*ei); + mEmitterFactories.clear(); + + std::vector::iterator ai; + for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai) + OGRE_DELETE (*ai); + mAffectorFactories.clear(); + + #ifdef ENABLE_PLUGIN_GL + delete mGLPlugin; + mGLPlugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + delete mD3D9Plugin; + mD3D9Plugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_CgProgramManager + delete mCgPlugin; + mCgPlugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + delete mOctreePlugin; + mOctreePlugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + delete mParticleFXPlugin; + mParticleFXPlugin = NULL; + #endif + } + + void OgreInit::loadStaticPlugins() + { + #ifdef ENABLE_PLUGIN_GL + mGLPlugin = new Ogre::GLPlugin(); + mRoot->installPlugin(mGLPlugin); + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + mD3D9Plugin = new Ogre::D3D9Plugin(); + mRoot->installPlugin(mD3D9Plugin); + #endif + #ifdef ENABLE_PLUGIN_CgProgramManager + mCgPlugin = new Ogre::CgPlugin(); + mRoot->installPlugin(mCgPlugin); + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + mOctreePlugin = new Ogre::OctreePlugin(); + mRoot->installPlugin(mOctreePlugin); + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + mParticleFXPlugin = new Ogre::ParticleFXPlugin(); + mRoot->installPlugin(mParticleFXPlugin); + #endif + } + + void OgreInit::loadPlugins() + { + std::string pluginDir; + const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); + if (pluginEnv) + pluginDir = pluginEnv; + else + { + #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + pluginDir = ".\\"; + #endif + #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + pluginDir = OGRE_PLUGIN_DIR; + #endif + #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + pluginDir = OGRE_PLUGIN_DIR_REL; + #endif + } + + boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); + + pluginDir = absPluginPath.string(); + + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); + Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); + Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot); + } + + void OgreInit::loadParticleFactories() + { + Ogre::ParticleEmitterFactory *emitter; + emitter = OGRE_NEW NifEmitterFactory(); + Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter); + mEmitterFactories.push_back(emitter); + + Ogre::ParticleAffectorFactory *affector; + affector = OGRE_NEW GrowFadeAffectorFactory(); + Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); + mAffectorFactories.push_back(affector); + + affector = OGRE_NEW GravityAffectorFactory(); + Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); + mAffectorFactories.push_back(affector); + } + +} diff --git a/components/ogreinit/ogreinit.hpp b/components/ogreinit/ogreinit.hpp new file mode 100644 index 000000000..b6fe4631a --- /dev/null +++ b/components/ogreinit/ogreinit.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_COMPONENTS_OGREINIT_H +#define OPENMW_COMPONENTS_OGREINIT_H + +#include +#include + +// Static plugin headers +#ifdef ENABLE_PLUGIN_CgProgramManager +# include "OgreCgPlugin.h" +#endif +#ifdef ENABLE_PLUGIN_OctreeSceneManager +# include "OgreOctreePlugin.h" +#endif +#ifdef ENABLE_PLUGIN_ParticleFX +# include "OgreParticleFXPlugin.h" +#endif +#ifdef ENABLE_PLUGIN_GL +# include "OgreGLPlugin.h" +#endif +#ifdef ENABLE_PLUGIN_Direct3D9 +# include "OgreD3D9Plugin.h" +#endif + +namespace Ogre +{ + class ParticleEmitterFactory; + class ParticleAffectorFactory; + class Root; +} + +namespace OgreInit +{ + /** + * @brief Starts Ogre::Root and loads required plugins and NIF particle factories + */ + class OgreInit + { + public: + OgreInit(); + + Ogre::Root* init(const std::string &logPath // Path to directory where to store log files + ); + + ~OgreInit(); + + private: + std::vector mEmitterFactories; + std::vector mAffectorFactories; + Ogre::Root* mRoot; + + void loadStaticPlugins(); + void loadPlugins(); + void loadParticleFactories(); + + + #ifdef ENABLE_PLUGIN_CgProgramManager + Ogre::CgPlugin* mCgPlugin; + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + Ogre::OctreePlugin* mOctreePlugin; + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + Ogre::ParticleFXPlugin* mParticleFXPlugin; + #endif + #ifdef ENABLE_PLUGIN_GL + Ogre::GLPlugin* mGLPlugin; + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + Ogre::D3D9Plugin* mD3D9Plugin; + #endif + + }; +} + +#endif diff --git a/components/files/ogreplugin.cpp b/components/ogreinit/ogreplugin.cpp similarity index 100% rename from components/files/ogreplugin.cpp rename to components/ogreinit/ogreplugin.cpp diff --git a/components/files/ogreplugin.hpp b/components/ogreinit/ogreplugin.hpp similarity index 100% rename from components/files/ogreplugin.hpp rename to components/ogreinit/ogreplugin.hpp diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index e9858eb94..b7c7d59a9 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -1,5 +1,5 @@ -#ifndef _COMPONENTS_SETTINGS_H -#define _COMPONENTS_SETTINGS_H +#ifndef COMPONENTS_SETTINGS_H +#define COMPONENTS_SETTINGS_H #include diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index ce2118cdb..a5c629088 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -18,11 +18,13 @@ namespace Terrain mVertexData = OGRE_NEW Ogre::VertexData; mVertexData->vertexStart = 0; + unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices(); + // Set the total number of vertices - size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1); + size_t numVertsOneSide = mNode->getSize() * (verts-1); numVertsOneSide /= 1 << lodLevel; numVertsOneSide += 1; - assert((int)numVertsOneSide == ESM::Land::LAND_SIZE); + assert(numVertsOneSide == verts); mVertexData->vertexCount = numVertsOneSide * numVertsOneSide; // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index ebf6046ff..8e78d2216 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include namespace @@ -36,6 +38,8 @@ namespace Terrain : mShaders(shaders) , mShadows(false) , mSplitShadows(false) + , mNormalMapping(true) + , mParallaxMapping(true) { } @@ -85,7 +89,7 @@ namespace Terrain { assert(mLayerList.size() == mBlendmapList.size()+1); std::vector::iterator blend = mBlendmapList.begin(); - for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) + for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) { Ogre::Pass* pass = technique->createPass(); pass->setLightingEnabled(false); @@ -117,7 +121,7 @@ namespace Terrain } // Add the actual layer texture on top of the alpha map. - tus = pass->createTextureUnitState("textures\\" + *layer); + tus = pass->createTextureUnitState(layer->mDiffuseMap); if (!first) tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, Ogre::LBS_TEXTURE, @@ -156,6 +160,10 @@ namespace Terrain p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); + p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(0))); sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); @@ -210,6 +218,10 @@ namespace Terrain } } ++neededTextureUnits; // layer texture + + // Check if this layer has a normal map + if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap) + ++neededTextureUnits; // normal map if (neededTextureUnits <= remainingTextureUnits) { // We can fit another! @@ -238,6 +250,8 @@ namespace Terrain p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(false))); // blend maps // the index of the first blend map used in this pass @@ -254,17 +268,43 @@ namespace Terrain } // layer maps + bool anyNormalMaps = false; + bool anyParallax = false; + size_t normalMaps = 0; for (int i = 0; i < numLayersInThisPass; ++i) { + const LayerInfo& layer = mLayerList[layerOffset+i]; + // diffuse map sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i]))); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap))); + + // normal map (optional) + bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap; + bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax; + bool useSpecular = layer.mSpecular; + if (useNormalMap) + { + anyNormalMaps = true; + anyParallax = anyParallax || useParallax; + sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i)); + normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap))); + } + p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useNormalMap))); + p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useParallax))); + p->mShaderProperties.setProperty ("use_specular_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useSpecular))); + boost::hash_combine(normalMaps, useNormalMap); + boost::hash_combine(normalMaps, useNormalMap && layer.mParallax); + boost::hash_combine(normalMaps, useSpecular); if (i+layerOffset > 0) { int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); + sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); } else { @@ -274,6 +314,14 @@ namespace Terrain sh::makeProperty (new sh::StringValue(""))); } } + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(anyNormalMaps))); + p->mShaderProperties.setProperty ("parallax_enabled", + sh::makeProperty (new sh::BooleanValue(anyParallax))); + // Since the permutation handler can't handle dynamic property names, + // combine normal map settings for all layers into one value + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(normalMaps))); // shadow if (shadows) @@ -290,6 +338,8 @@ namespace Terrain // Make sure the pass index is fed to the permutation handler, because blendmap components may be different p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset))); + assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits); + layerOffset += numLayersInThisPass; } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 330ed3d14..e7e067899 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -3,6 +3,8 @@ #include +#include "storage.hpp" + namespace Terrain { @@ -15,13 +17,15 @@ namespace Terrain /// so if this parameter is true, then the supplied blend maps are expected to be packed. MaterialGenerator (bool shaders); - void setLayerList (const std::vector& layerList) { mLayerList = layerList; } + void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } void enableShadows(bool shadows) { mShadows = shadows; } + void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } + void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. @@ -43,12 +47,14 @@ namespace Terrain private: Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap); - std::vector mLayerList; + std::vector mLayerList; std::vector mBlendmapList; std::string mCompositeMap; bool mShaders; bool mShadows; bool mSplitShadows; + bool mNormalMapping; + bool mParallaxMapping; }; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index ef2c61013..82ccc7c89 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -140,17 +140,19 @@ namespace } QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) - : mSize(size) - , mCenter(center) - , mParent(parent) - , mDirection(dir) + : mMaterialGenerator(NULL) + , mIsActive(false) , mIsDummy(false) + , mSize(size) + , mLodLevel(Log2(mSize)) + , mBounds(Ogre::AxisAlignedBox::BOX_NULL) + , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) + , mDirection(dir) + , mCenter(center) , mSceneNode(NULL) + , mParent(parent) , mTerrain(terrain) , mChunk(NULL) - , mMaterialGenerator(NULL) - , mBounds(Ogre::AxisAlignedBox::BOX_NULL) - , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) { mBounds.setNull(); for (int i=0; i<4; ++i) @@ -166,9 +168,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const if (mParent) pos = mParent->getCenter(); pos = mCenter - pos; - mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0)); - - mLodLevel = Log2(mSize); + float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); + mSceneNode->setPosition(Ogre::Vector3(pos.x*cellWorldSize, pos.y*cellWorldSize, 0)); mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); } @@ -203,6 +204,7 @@ void QuadTreeNode::initNeighbours() void QuadTreeNode::initAabb() { + float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); if (hasChildren()) { for (int i=0; i<4; ++i) @@ -210,11 +212,11 @@ void QuadTreeNode::initAabb() mChildren[i]->initAabb(); mBounds.merge(mChildren[i]->getBoundingBox()); } - mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z), - Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z)); + mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, mBounds.getMinimum().z), + Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, mBounds.getMaximum().z)); } - mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0), - mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); + mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0), + mBounds.getMaximum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0)); } void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) @@ -371,7 +373,7 @@ void QuadTreeNode::destroyChunks(bool children) for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) Ogre::TextureManager::getSingleton().remove((*it)->getName()); mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); + mMaterialGenerator->setLayerList(std::vector()); mMaterialGenerator->setCompositeMap(""); } @@ -414,7 +416,7 @@ void QuadTreeNode::ensureLayerInfo() return; std::vector blendmaps; - std::vector layerList; + std::vector layerList; mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); mMaterialGenerator->setLayerList(layerList); @@ -427,11 +429,10 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { - // TODO - why is this completely black? // TODO - store this default material somewhere instead of creating one for each empty cell MaterialGenerator matGen(mTerrain->getShadersEnabled()); - std::vector layer; - layer.push_back("_land_default.dds"); + std::vector layer; + layer.push_back(mTerrain->getStorage()->getDefaultLayer()); matGen.setLayerList(layer); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr())); return; diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 9f6fb5d24..ea299c096 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -108,7 +108,7 @@ namespace Terrain void destroyChunks(bool children); /// Get the effective LOD level if this node was rendered in one chunk - /// with ESM::Land::LAND_SIZE^2 vertices + /// with Storage::getCellVertices^2 vertices size_t getNativeLodLevel() { return mLodLevel; } /// Get the effective current LOD level used by the chunk rendering this node diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index f00677e97..e69de29bb 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -1,470 +0,0 @@ -#include "storage.hpp" - -#include -#include -#include -#include -#include - -#include - -namespace Terrain -{ - - struct VertexElement - { - Ogre::Vector3 pos; - Ogre::Vector3 normal; - Ogre::ColourValue colour; - }; - - bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) - { - assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); - - /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int cellX = origin.x; - int cellY = origin.y; - - const ESM::Land* land = getLand(cellX, cellY); - if (!land) - return false; - - min = std::numeric_limits().max(); - max = -std::numeric_limits().max(); - for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; - } - } - return true; - } - - void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mHasData) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - } - - void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) - { - Ogre::Vector3 n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row); - fixNormal(n2, cellX, cellY, col-1, row); - fixNormal(n3, cellX, cellY, col, row+1); - fixNormal(n4, cellX, cellY, col, row-1); - normal = (n1+n2+n3+n4); - normal.normalise(); - } - - void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mLandData->mUsingColours) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - } - - void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer) - { - // LOD level n means every 2^n-th vertex is kept - size_t increment = 1 << lodLevel; - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int startX = origin.x; - int startY = origin.y; - - size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; - - std::vector colors; - colors.resize(numVerts*numVerts*4); - std::vector positions; - positions.resize(numVerts*numVerts*3); - std::vector normals; - normals.resize(numVerts*numVerts*3); - - Ogre::Vector3 normal; - Ogre::ColourValue color; - - float vertY; - float vertX; - - float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) - { - float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) - { - ESM::Land* land = getLand(cellX, cellY); - if (land && !land->mHasData) - land = NULL; - bool hasColors = land && land->mLandData->mUsingColours; - - int rowStart = 0; - int colStart = 0; - // Skip the first row / column unless we're at a chunk edge, - // since this row / column is already contained in a previous cell - if (colStart == 0 && vertY_ != 0) - colStart += increment; - if (rowStart == 0 && vertX_ != 0) - rowStart += increment; - - vertY = vertY_; - for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - else - positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; - - if (land) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - - // Normals apparently don't connect seamlessly between cells - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixNormal(normal, cellX, cellY, col, row); - - // some corner normals appear to be complete garbage (z < 0) - if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) - averageNormal(normal, cellX, cellY, col, row); - - assert(normal.z > 0); - - normals[vertX*numVerts*3 + vertY*3] = normal.x; - normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; - normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; - - if (hasColors) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - // Unlike normals, colors mostly connect seamlessly between cells, but not always... - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixColour(color, cellX, cellY, col, row); - - color.a = 1; - Ogre::uint32 rsColor; - Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); - - ++vertX; - } - ++vertY; - } - vertX_ = vertX; - } - vertY_ = vertY; - - assert(vertX_ == numVerts); // Ensure we covered whole area - } - assert(vertY_ == numVerts); // Ensure we covered whole area - - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true); - } - - Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, - int x, int y) - { - // For the first/last row/column, we need to get the texture from the neighbour cell - // to get consistent blending at the borders - --x; - if (x < 0) - { - --cellX; - x += ESM::Land::LAND_TEXTURE_SIZE; - } - if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? - { - ++cellY; - y -= ESM::Land::LAND_TEXTURE_SIZE; - } - - assert(xisDataLoaded(ESM::Land::DATA_VTEX)) - land->loadData(ESM::Land::DATA_VTEX); - - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; - if (tex == 0) - return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); - } - else - return std::make_pair(0,0); - } - - std::string Storage::getTextureName(UniqueTextureId id) - { - if (id.first == 0) - return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded? - - // NB: All vtex ids are +1 compared to the ltex ids - const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); - - std::string texture = ltex->mTexture; - //TODO this is needed due to MWs messed up texture handling - texture = texture.substr(0, texture.rfind(".")) + ".dds"; - - return texture; - } - - void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - // TODO - blending isn't completely right yet; the blending radius appears to be - // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap - // and interpolate the rest of the cell by hand? :/ - - Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = origin.x; - int cellY = origin.y; - - // Save the used texture indices so we know the total number of textures - // and number of required blend maps - std::set textureIndices; - // Due to the way the blending works, the base layer will always shine through in between - // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). - // To get a consistent look, we need to make sure to use the same base layer in all cells. - // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. - textureIndices.insert(std::make_pair(0,0)); - - for (int y=0; y textureIndicesMap; - for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) - { - int size = textureIndicesMap.size(); - textureIndicesMap[*it] = size; - layerList.push_back(getTextureName(*it)); - } - - int numTextures = textureIndices.size(); - // numTextures-1 since the base layer doesn't need blending - int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); - - int channels = pack ? 4 : 1; - - // Second iteration - create and fill in the blend maps - const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - std::vector data; - data.resize(blendmapSize * blendmapSize * channels, 0); - - for (int i=0; isecond; - int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); - int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - - if (blendIndex == i) - data[y*blendmapSize*channels + x*channels + channel] = 255; - else - data[y*blendmapSize*channels + x*channels + channel] = 0; - } - } - - // All done, upload to GPU - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); - map->loadRawData(stream, blendmapSize, blendmapSize, format); - blendmaps.push_back(map); - } - } - - float Storage::getHeightAt(const Ogre::Vector3 &worldPos) - { - int cellX = std::floor(worldPos.x / 8192.f); - int cellY = std::floor(worldPos.y / 8192.f); - - ESM::Land* land = getLand(cellX, cellY); - if (!land) - return -2048; - - // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition - - // Normalized position in the cell - float nX = (worldPos.x - (cellX * 8192))/8192.f; - float nY = (worldPos.y - (cellY * 8192))/8192.f; - - // get left / bottom points (rounded down) - float factor = ESM::Land::LAND_SIZE - 1.0f; - float invFactor = 1.0f / factor; - - int startX = static_cast(nX * factor); - int startY = static_cast(nY * factor); - int endX = startX + 1; - int endY = startY + 1; - - assert(endX < ESM::Land::LAND_SIZE); - assert(endY < ESM::Land::LAND_SIZE); - - // now get points in terrain space (effectively rounding them to boundaries) - float startXTS = startX * invFactor; - float startYTS = startY * invFactor; - float endXTS = endX * invFactor; - float endYTS = endY * invFactor; - - // get parametric from start coord to next point - float xParam = (nX - startXTS) * factor; - float yParam = (nY - startYTS) * factor; - - /* For even / odd tri strip rows, triangles are this shape: - even odd - 3---2 3---2 - | / | | \ | - 0---1 0---1 - */ - - // Build all 4 positions in normalized cell space, using point-sampled height - Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); - Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); - Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); - Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); - // define this plane in terrain space - Ogre::Plane plane; - // (At the moment, all rows have the same triangle alignment) - if (true) - { - // odd row - bool secondTri = ((1.0 - yParam) > xParam); - if (secondTri) - plane.redefine(v0, v1, v3); - else - plane.redefine(v1, v2, v3); - } - else - { - // even row - bool secondTri = (yParam > xParam); - if (secondTri) - plane.redefine(v0, v2, v3); - else - plane.redefine(v0, v1, v2); - } - - // Solve plane equation for z - return (-plane.normal.x * nX - -plane.normal.y * nY - - plane.d) / plane.normal.z * 8192; - - } - - float Storage::getVertexHeight(const ESM::Land *land, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; - } - - -} diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b82f6bbb6..021e01c7e 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -11,14 +11,19 @@ namespace Terrain { + struct LayerInfo + { + std::string mDiffuseMap; + std::string mNormalMap; + bool mParallax; // Height info in normal map alpha channel? + bool mSpecular; // Specular info in diffuse map alpha channel? + }; + /// We keep storage of terrain data abstract here since we need different implementations for game and editor class Storage { public: virtual ~Storage() {} - private: - virtual ESM::Land* getLand (int cellX, int cellY) = 0; - virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: /// Get bounds of the whole terrain in cell units @@ -32,7 +37,7 @@ namespace Terrain /// @param min min height will be stored here /// @param max max height will be stored here /// @return true if there was data available for this terrain chunk - bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0; /// Fill vertex buffers for a terrain chunk. /// @param lodLevel LOD level, 0 = most detailed @@ -41,10 +46,10 @@ namespace Terrain /// @param vertexBuffer buffer to write vertices /// @param normalBuffer buffer to write vertex normals /// @param colourBuffer buffer to write vertex colours - void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Ogre::HardwareVertexBufferSharedPtr vertexBuffer, Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer); + Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might @@ -56,27 +61,19 @@ namespace Terrain /// can utilize packing, FFP can't. /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here - void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, std::vector& blendmaps, - std::vector& layerList); - - float getHeightAt (const Ogre::Vector3& worldPos); + std::vector& layerList) = 0; - private: - void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); - void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0; - float getVertexHeight (const ESM::Land* land, int x, int y); + virtual LayerInfo getDefaultLayer() = 0; - // Since plugins can define new texture palettes, we need to know the plugin index too - // in order to retrieve the correct texture name. - // pair - typedef std::pair UniqueTextureId; + /// Get the transformation factor for mapping cell units to world units. + virtual float getCellWorldSize() = 0; - UniqueTextureId getVtexIndexAt(int cellX, int cellY, - int x, int y); - std::string getTextureName (UniqueTextureId id); + /// Get the number of vertices on one side for each cell. Should be (power of two)+1 + virtual int getCellVertices() = 0; }; } diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 711ebbc8f..f4070393d 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include "storage.hpp" @@ -114,9 +113,10 @@ namespace Terrain // We arrived at a leaf float minZ,maxZ; Ogre::Vector2 center = node->getCenter(); + float cellWorldSize = getStorage()->getCellWorldSize(); if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) - node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ), - Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ))); + node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), + Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ))); else node->markAsDummy(); // no data available for this node, skip it return; @@ -169,8 +169,9 @@ namespace Terrain return Ogre::AxisAlignedBox::BOX_NULL; QuadTreeNode* node = findNode(center, mRootNode); Ogre::AxisAlignedBox box = node->getBoundingBox(); - box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192, - box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192); + float cellWorldSize = getStorage()->getCellWorldSize(); + box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize, + box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize); return box; } @@ -208,6 +209,8 @@ namespace Terrain Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices) { + unsigned int verts = mStorage->getCellVertices(); + if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) { numIndices = mIndexBufferMap[flags]->getNumIndexes(); @@ -224,11 +227,11 @@ namespace Terrain bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); size_t increment = 1 << lodLevel; - assert((int)increment < ESM::Land::LAND_SIZE); + assert(increment < verts); std::vector indices; - indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment); + indices.reserve((verts-1)*(verts-1)*2*3 / increment); - size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1; + size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; // If any edge needs stitching we'll skip all edges at this point, // mainly because stitching one edge would have an effect on corners and on the adjacent edges if (anyDeltas) @@ -242,13 +245,13 @@ namespace Terrain { for (size_t col = colStart; col < colEnd; col += increment) { - indices.push_back(ESM::Land::LAND_SIZE*col+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); - indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row+increment); + indices.push_back(verts*col+row+increment); - indices.push_back(ESM::Land::LAND_SIZE*col+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row); + indices.push_back(verts*(col+increment)+row+increment); } } @@ -261,96 +264,96 @@ namespace Terrain // South size_t row = 0; size_t outerStep = 1 << (lodDeltas[South] + lodLevel); - for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) + for (size_t col = 0; col < verts-1; col += outerStep) { - indices.push_back(ESM::Land::LAND_SIZE*col+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + indices.push_back(verts*col+row); + indices.push_back(verts*(col+outerStep)+row); // Make sure not to touch the right edge - if (col+outerStep == ESM::Land::LAND_SIZE-1) - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep); + if (col+outerStep == verts-1) + indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep); + indices.push_back(verts*(col+outerStep)+row+innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges - if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) + if (col+i == 0 || col+i == verts-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*(col)+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep); - indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep); + indices.push_back(verts*(col)+row); + indices.push_back(verts*(col+i+innerStep)+row+innerStep); + indices.push_back(verts*(col+i)+row+innerStep); } } // North - row = ESM::Land::LAND_SIZE-1; + row = verts-1; outerStep = 1 << (lodDeltas[North] + lodLevel); - for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) + for (size_t col = 0; col < verts-1; col += outerStep) { - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); - indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(verts*(col+outerStep)+row); + indices.push_back(verts*col+row); // Make sure not to touch the left edge if (col == 0) - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep); + indices.push_back(verts*(col+innerStep)+row-innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep); + indices.push_back(verts*col+row-innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges - if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) + if (col+i == 0 || col+i == verts-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep); - indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep); - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + indices.push_back(verts*(col+i)+row-innerStep); + indices.push_back(verts*(col+i+innerStep)+row-innerStep); + indices.push_back(verts*(col+outerStep)+row); } } // West size_t col = 0; outerStep = 1 << (lodDeltas[West] + lodLevel); - for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) + for (size_t row = 0; row < verts-1; row += outerStep) { - indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); - indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*col+row); // Make sure not to touch the top edge - if (row+outerStep == ESM::Land::LAND_SIZE-1) - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep); + if (row+outerStep == verts-1) + indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep); + indices.push_back(verts*(col+innerStep)+row+outerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges - if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) + if (row+i == 0 || row+i == verts-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*col+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i); - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep); + indices.push_back(verts*col+row); + indices.push_back(verts*(col+innerStep)+row+i); + indices.push_back(verts*(col+innerStep)+row+i+innerStep); } } // East - col = ESM::Land::LAND_SIZE-1; + col = verts-1; outerStep = 1 << (lodDeltas[East] + lodLevel); - for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) + for (size_t row = 0; row < verts-1; row += outerStep) { - indices.push_back(ESM::Land::LAND_SIZE*col+row); - indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + indices.push_back(verts*col+row); + indices.push_back(verts*col+row+outerStep); // Make sure not to touch the bottom edge if (row == 0) - indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep); + indices.push_back(verts*(col-innerStep)+row+innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row); + indices.push_back(verts*(col-innerStep)+row); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges - if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) + if (row+i == 0 || row+i == verts-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); - indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep); - indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i); + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*(col-innerStep)+row+i+innerStep); + indices.push_back(verts*(col-innerStep)+row+i); } } } diff --git a/apps/openmw/config.hpp.cmake b/components/version/version.hpp.cmake similarity index 52% rename from apps/openmw/config.hpp.cmake rename to components/version/version.hpp.cmake index 848fbe0eb..4cdfa32f0 100644 --- a/apps/openmw/config.hpp.cmake +++ b/components/version/version.hpp.cmake @@ -1,9 +1,13 @@ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef VERSION_HPP +#define VERSION_HPP #define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@ #define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@ #define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@ #define OPENMW_VERSION "@OPENMW_VERSION@" -#endif +#define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@" +#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@" + +#endif // VERSION_HPP + diff --git a/credits.txt b/credits.txt index bd0c6ca74..561931cde 100644 --- a/credits.txt +++ b/credits.txt @@ -24,6 +24,7 @@ Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) darkf +Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) Douglas Mencken (dougmencken) Edmondo Tommasina (edmondo) diff --git a/extern/oics/ICSChannel.h b/extern/oics/ICSChannel.h index f98f0d94d..5ec6cd575 100644 --- a/extern/oics/ICSChannel.h +++ b/extern/oics/ICSChannel.h @@ -24,8 +24,8 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------- */ -#ifndef _Channel_H_ -#define _Channel_H_ +#ifndef OICS_Channel_H_ +#define OICS_Channel_H_ #include "ICSPrerequisites.h" @@ -119,4 +119,4 @@ namespace ICS } -#endif \ No newline at end of file +#endif diff --git a/extern/oics/ICSControl.h b/extern/oics/ICSControl.h index 7939c86b9..ebf75a3fe 100644 --- a/extern/oics/ICSControl.h +++ b/extern/oics/ICSControl.h @@ -24,8 +24,8 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------- */ -#ifndef _Control_H_ -#define _Control_H_ +#ifndef OICS_Control_H_ +#define OICS_Control_H_ #include "ICSPrerequisites.h" diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index f42f9c0b5..907cba5fc 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -24,8 +24,8 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------- */ -#ifndef _InputControlSystem_H_ -#define _InputControlSystem_H_ +#ifndef OICS_InputControlSystem_H_ +#define OICS_InputControlSystem_H_ #include "ICSPrerequisites.h" diff --git a/extern/oics/tinyxml.h b/extern/oics/tinyxml.h index e69913b59..50ad417fe 100644 --- a/extern/oics/tinyxml.h +++ b/extern/oics/tinyxml.h @@ -349,7 +349,7 @@ protected: { //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), // and the null terminator isn't needed - for( int i=0; p[i] && i<*length; ++i ) { + for( int i=0; i<*length && p[i]; ++i ) { _value[i] = p[i]; } return p + (*length); diff --git a/extern/sdl4ogre/OISCompat.h b/extern/sdl4ogre/OISCompat.h index 3cffa143d..a0acc5837 100644 --- a/extern/sdl4ogre/OISCompat.h +++ b/extern/sdl4ogre/OISCompat.h @@ -1,5 +1,5 @@ -#ifndef _OIS_SDL_COMPAT_H -#define _OIS_SDL_COMPAT_H +#ifndef OIS_SDL_COMPAT_H +#define OIS_SDL_COMPAT_H #include #include diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp index f45c5cdc2..3036b236b 100644 --- a/extern/sdl4ogre/cursormanager.hpp +++ b/extern/sdl4ogre/cursormanager.hpp @@ -1,5 +1,5 @@ -#ifndef _SDL4OGRE_CURSOR_MANAGER_H -#define _SDL4OGRE_CURSOR_MANAGER_H +#ifndef SDL4OGRE_CURSOR_MANAGER_H +#define SDL4OGRE_CURSOR_MANAGER_H #include #include @@ -22,9 +22,6 @@ public: /// \brief Follow up a cursorChanged() call with enough info to create an cursor. virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) = 0; - /// \brief Tell the manager when the cursor visibility changed - virtual void cursorVisibilityChange(bool visible) = 0; - /// \brief sets whether to actively manage cursors or not virtual void setEnabled(bool enabled) = 0; }; diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h index e6e8434cb..48adb4545 100644 --- a/extern/sdl4ogre/events.h +++ b/extern/sdl4ogre/events.h @@ -70,6 +70,8 @@ public: /** @remarks The window got / lost input focus */ virtual void windowFocusChange( bool have_focus ) {} + virtual void windowClosed () {} + virtual void windowResized (int x, int y) {} }; diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp index d14a9ffa0..65fb7f98b 100644 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ b/extern/sdl4ogre/sdlcursormanager.cpp @@ -10,7 +10,6 @@ namespace SFO SDLCursorManager::SDLCursorManager() : mEnabled(false), - mCursorVisible(false), mInitialized(false) { } @@ -70,27 +69,7 @@ namespace SFO void SDLCursorManager::_setGUICursor(const std::string &name) { - if(mEnabled && mCursorVisible) - { - SDL_SetCursor(mCursorMap.find(name)->second); - _setCursorVisible(mCursorVisible); - } - } - - void SDLCursorManager::_setCursorVisible(bool visible) - { - if(!mEnabled) - return; - - SDL_ShowCursor(visible ? SDL_TRUE : SDL_FALSE); - } - - void SDLCursorManager::cursorVisibilityChange(bool visible) - { - mCursorVisible = visible; - - _setGUICursor(mCurrentCursor); - _setCursorVisible(visible); + SDL_SetCursor(mCursorMap.find(name)->second); } void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp index 8940220d4..7e3e59b4a 100644 --- a/extern/sdl4ogre/sdlcursormanager.hpp +++ b/extern/sdl4ogre/sdlcursormanager.hpp @@ -1,5 +1,5 @@ -#ifndef _SDL4OGRE_CURSORMANAGER_H -#define _SDL4OGRE_CURSORMANAGER_H +#ifndef SDL4OGRE_CURSORMANAGER_H +#define SDL4OGRE_CURSORMANAGER_H #include @@ -19,14 +19,12 @@ namespace SFO virtual bool cursorChanged(const std::string &name); virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); - virtual void cursorVisibilityChange(bool visible); private: void _createCursorFromResource(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); - void _setCursorVisible(bool visible); typedef std::map CursorMap; CursorMap mCursorMap; @@ -34,7 +32,6 @@ namespace SFO std::string mCurrentCursor; bool mEnabled; bool mInitialized; - bool mCursorVisible; }; } diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index df74bba3b..d48e43c01 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -9,7 +9,7 @@ namespace SFO { /// \brief General purpose wrapper for OGRE applications around SDL's event /// queue, mostly used for handling input-related events. - InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow) : + InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow, bool grab) : mSDLWindow(window), mOgreWindow(ogreWindow), mWarpCompensate(false), @@ -23,7 +23,12 @@ namespace SFO mJoyListener(NULL), mKeyboardListener(NULL), mMouseListener(NULL), - mWindowListener(NULL) + mWindowListener(NULL), + mWindowHasFocus(true), + mWantGrab(false), + mWantRelative(false), + mWantMouseVisible(false), + mAllowGrab(grab) { _setupOISKeys(); } @@ -51,13 +56,16 @@ namespace SFO switch(evt.type) { case SDL_MOUSEMOTION: - //ignore this if it happened due to a warp + // Ignore this if it happened due to a warp if(!_handleWarpMotion(evt.motion)) { - mMouseListener->mouseMoved(_packageMouseMotion(evt)); + // If in relative mode, don't trigger events unless window has focus + if (!mWantRelative || mWindowHasFocus) + mMouseListener->mouseMoved(_packageMouseMotion(evt)); - //try to keep the mouse inside the window - _wrapMousePointer(evt.motion); + // Try to keep the mouse inside the window + if (mWindowHasFocus) + _wrapMousePointer(evt.motion); } break; case SDL_MOUSEWHEEL: @@ -103,7 +111,8 @@ namespace SFO handleWindowEvent(evt); break; case SDL_QUIT: - Ogre::Root::getSingleton().queueEndRendering(); + if (mWindowListener) + mWindowListener->windowClosed(); break; default: std::cerr << "Unhandled SDL event of type " << evt.type << std::endl; @@ -117,16 +126,16 @@ namespace SFO switch (evt.window.event) { case SDL_WINDOWEVENT_ENTER: mMouseInWindow = true; + updateMouseSettings(); break; case SDL_WINDOWEVENT_LEAVE: mMouseInWindow = false; - SDL_SetWindowGrab(mSDLWindow, SDL_FALSE); - SDL_SetRelativeMouseMode(SDL_FALSE); + updateMouseSettings(); break; case SDL_WINDOWEVENT_SIZE_CHANGED: int w,h; SDL_GetWindowSize(mSDLWindow, &w, &h); - // TODO: Fix Ogre to handle this more consistently + // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX mOgreWindow->resize(w, h); #else @@ -137,7 +146,7 @@ namespace SFO break; case SDL_WINDOWEVENT_RESIZED: - // TODO: Fix Ogre to handle this more consistently + // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX mOgreWindow->resize(evt.window.data1, evt.window.data2); #else @@ -148,10 +157,15 @@ namespace SFO break; case SDL_WINDOWEVENT_FOCUS_GAINED: + mWindowHasFocus = true; + updateMouseSettings(); if (mWindowListener) mWindowListener->windowFocusChange(true); + break; case SDL_WINDOWEVENT_FOCUS_LOST: + mWindowHasFocus = false; + updateMouseSettings(); if (mWindowListener) mWindowListener->windowFocusChange(false); break; @@ -192,25 +206,43 @@ namespace SFO /// \brief Locks the pointer to the window void InputWrapper::setGrabPointer(bool grab) { - mGrabPointer = grab && mMouseInWindow; - SDL_SetWindowGrab(mSDLWindow, grab && mMouseInWindow ? SDL_TRUE : SDL_FALSE); + mWantGrab = grab; + updateMouseSettings(); } /// \brief Set the mouse to relative positioning. Doesn't move the cursor /// and disables mouse acceleration. void InputWrapper::setMouseRelative(bool relative) { - if(mMouseRelative == relative && mMouseInWindow) + mWantRelative = relative; + updateMouseSettings(); + } + + void InputWrapper::setMouseVisible(bool visible) + { + mWantMouseVisible = visible; + updateMouseSettings(); + } + + void InputWrapper::updateMouseSettings() + { + mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus; + SDL_SetWindowGrab(mSDLWindow, mGrabPointer && mAllowGrab ? SDL_TRUE : SDL_FALSE); + + SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus); + + bool relative = mWantRelative && mMouseInWindow && mWindowHasFocus; + if(mMouseRelative == relative) return; - mMouseRelative = relative && mMouseInWindow; + mMouseRelative = relative; mWrapPointer = false; //eep, wrap the pointer manually if the input driver doesn't support //relative positioning natively - int success = SDL_SetRelativeMouseMode(relative && mMouseInWindow ? SDL_TRUE : SDL_FALSE); - if(relative && mMouseInWindow && success != 0) + int success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE); + if(relative && success != 0) mWrapPointer = true; //now remove all mouse events using the old setting from the queue diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index 1bd8947a0..f08e3eff6 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -1,5 +1,5 @@ -#ifndef _SDL4OGRE_SDLINPUTWRAPPER_H -#define _SDL4OGRE_SDLINPUTWRAPPER_H +#ifndef SDL4OGRE_SDLINPUTWRAPPER_H +#define SDL4OGRE_SDLINPUTWRAPPER_H #include @@ -16,7 +16,7 @@ namespace SFO class InputWrapper { public: - InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow); + InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow, bool grab); ~InputWrapper(); void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; } @@ -28,6 +28,7 @@ namespace SFO bool isModifierHeld(SDL_Keymod mod); bool isKeyDown(SDL_Scancode key); + void setMouseVisible (bool visible); void setMouseRelative(bool relative); bool getMouseRelative() { return mMouseRelative; } void setGrabPointer(bool grab); @@ -36,6 +37,8 @@ namespace SFO void warpMouse(int x, int y); + void updateMouseSettings(); + private: void handleWindowEvent(const SDL_Event& evt); @@ -57,14 +60,20 @@ namespace SFO Uint16 mWarpX; Uint16 mWarpY; bool mWarpCompensate; - bool mMouseRelative; bool mWrapPointer; + + bool mAllowGrab; + bool mWantMouseVisible; + bool mWantGrab; + bool mWantRelative; bool mGrabPointer; + bool mMouseRelative; Sint32 mMouseZ; Sint32 mMouseX; Sint32 mMouseY; + bool mWindowHasFocus; bool mMouseInWindow; SDL_Window* mSDLWindow; diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 97984609e..15c859958 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -259,8 +259,8 @@ namespace sh Platform* mPlatform; MaterialInstance* findInstance (const std::string& name); + private: MaterialInstance* searchInstance (const std::string& name); - /// @return was anything removed? bool removeCache (const std::string& pattern); diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index 3725d5f35..9f309fbcd 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -64,8 +64,11 @@ namespace sh bool OgrePlatform::supportsShaderSerialization () { - // Not very reliable in OpenGL mode (requires extension), and somehow doesn't work on linux even if the extension is present + #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + return true; + #else return Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos; + #endif } bool OgrePlatform::supportsMaterialQueuedListener () @@ -110,10 +113,15 @@ namespace sh void OgrePlatform::serializeShaders (const std::string& file) { - std::fstream output; - output.open(file.c_str(), std::ios::out | std::ios::binary); - Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); - Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); + #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + if (Ogre::GpuProgramManager::getSingleton().isCacheDirty()) + #endif + { + std::fstream output; + output.open(file.c_str(), std::ios::out | std::ios::binary); + Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); + Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); + } } void OgrePlatform::deserializeShaders (const std::string& file) @@ -143,7 +151,7 @@ namespace sh else if (typeid(*value) == typeid(IntValue)) type = Ogre::GCT_INT1; else - assert(0); + throw std::runtime_error("unexpected type"); params->addConstantDefinition(name, type); mSharedParameters[name] = params; } diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp index 54dda3523..f215f4ab7 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp @@ -1,5 +1,8 @@ #include "OgreTextureUnitState.hpp" +#include +#include + #include "OgrePass.hpp" #include "OgrePlatform.hpp" #include "OgreMaterialSerializer.hpp" @@ -28,6 +31,32 @@ namespace sh setTextureName (retrieveValue(value, context).get()); return true; } + else if (name == "anim_texture2") + { + std::string val = retrieveValue(value, context).get(); + std::vector tokens; + boost::split(tokens, val, boost::is_any_of(" ")); + assert(tokens.size() == 3); + std::string texture = tokens[0]; + int frames = boost::lexical_cast(tokens[1]); + float duration = boost::lexical_cast(tokens[2]); + + std::vector frameTextures; + for (int i=0; isetAnimatedTextureName(&frameTextures[0], frames, duration); + return true; + } else if (name == "create_in_ffp") return true; // handled elsewhere diff --git a/files/mac/opencs.icns b/files/mac/opencs.icns new file mode 100644 index 000000000..98812f871 Binary files /dev/null and b/files/mac/opencs.icns differ diff --git a/files/materials/core.h b/files/materials/core.h index 6f8179c08..a912e2356 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -91,7 +91,7 @@ precision mediump float; #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) - #define shMatrixMult(m, v) (m * v) + #define shMatrixMult(m, v) ((m) * (v)) #define shOutputPosition gl_Position diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 8f8734d62..2281226b0 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -1,17 +1,24 @@ material openmw_objects_base { diffuse 1.0 1.0 1.0 1.0 - specular 0 0 0 0 + specular 0 0 0 0 1 ambient 1.0 1.0 1.0 emissive 0.0 0.0 0.0 vertmode 0 diffuseMap black.png normalMap emissiveMap + specMap + darkMap use_emissive_map false use_detail_map false + use_diffuse_map false + use_dark_map false emissiveMapUVSet 0 detailMapUVSet 0 + diffuseMapUVSet 0 + darkMapUVSet 0 + use_parallax false scene_blend default depth_write default @@ -19,6 +26,8 @@ material openmw_objects_base alpha_rejection default transparent_sorting default polygon_mode default + env_map false + env_map_color 1 1 1 pass { @@ -31,8 +40,16 @@ material openmw_objects_base normalMap $normalMap emissiveMapUVSet $emissiveMapUVSet detailMapUVSet $detailMapUVSet + diffuseMapUVSet $diffuseMapUVSet + darkMapUVSet $darkMapUVSet emissiveMap $emissiveMap detailMap $detailMap + diffuseMap $diffuseMap + specMap $specMap + darkMap $darkMap + env_map $env_map + env_map_color $env_map_color + use_parallax $use_parallax } diffuse $diffuse @@ -49,8 +66,8 @@ material openmw_objects_base texture_unit diffuseMap { direct_texture $diffuseMap - create_in_ffp true - tex_coord_set $emissiveMapUVSet + create_in_ffp $use_diffuse_map + tex_coord_set $diffuseMapUVSet } texture_unit normalMap @@ -60,12 +77,13 @@ material openmw_objects_base num_mipmaps 4 } - texture_unit emissiveMap + texture_unit darkMap { - create_in_ffp $use_emissive_map - colour_op add - direct_texture $emissiveMap - tex_coord_set $emissiveMapUVSet + create_in_ffp $use_dark_map + colour_op_ex modulate src_current src_texture + alpha_op_ex modulate src_current src_texture + direct_texture $darkMap + tex_coord_set $darkMapUVSet } texture_unit detailMap @@ -75,6 +93,27 @@ material openmw_objects_base direct_texture $detailMap tex_coord_set $detailMapUVSet } + + texture_unit emissiveMap + { + create_in_ffp $use_emissive_map + colour_op add + direct_texture $emissiveMap + tex_coord_set $emissiveMapUVSet + } + + texture_unit envMap + { + create_in_ffp $env_map + env_map spherical + anim_texture2 textures\magicitem\caust.dds 32 2 + colour_op add + } + + texture_unit specMap + { + direct_texture $specMap + } texture_unit shadowMap0 { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 36f92bfd9..ed75babdd 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -14,12 +14,21 @@ #define NEED_DEPTH #endif +#define SPECULAR 1 + #define NORMAL_MAP @shPropertyHasValue(normalMap) #define EMISSIVE_MAP @shPropertyHasValue(emissiveMap) #define DETAIL_MAP @shPropertyHasValue(detailMap) +#define DIFFUSE_MAP @shPropertyHasValue(diffuseMap) +#define DARK_MAP @shPropertyHasValue(darkMap) +#define SPEC_MAP @shPropertyHasValue(specMap) && SPECULAR + +#define PARALLAX @shPropertyBool(use_parallax) +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 // right now we support 2 UV sets max. implementing them is tedious, and we're probably not going to need more -#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet)) +#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet) || @shPropertyString(diffuseMapUVSet) || @shPropertyString(darkMapUVSet)) // if normal mapping is enabled, we force pixel lighting #define VERTEX_LIGHTING (!@shPropertyHasValue(normalMap)) @@ -30,6 +39,10 @@ #define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) +#define ENV_MAP @shPropertyBool(env_map) + +#define NEED_NORMAL (!VERTEX_LIGHTING || ENV_MAP) || SPECULAR + #ifdef SH_VERTEX_SHADER // ------------------------------------- VERTEX --------------------------------------- @@ -61,15 +74,12 @@ shOutput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING +#if NEED_NORMAL shOutput(float3, normalPassthrough) #endif -#ifdef NEED_DEPTH - shOutput(float, depthPassthrough) -#endif - - shOutput(float3, objSpacePositionPassthrough) + // Depth in w + shOutput(float4, objSpacePositionPassthrough) #if VERTEXCOLOR_MODE != 0 shColourInput(float4) @@ -79,18 +89,19 @@ shOutput(float4, colourPassthrough) #endif +#if ENV_MAP || VERTEX_LIGHTING + shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) +#endif + #if VERTEX_LIGHTING shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_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) - shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) #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 @@ -125,10 +136,23 @@ UV.zw = uv1; #endif +#if ENV_MAP || VERTEX_LIGHTING + float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); +#endif + +#if ENV_MAP + float3 viewVec = normalize( shMatrixMult(worldView, shInputPosition).xyz); + + float3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + UV.z = r.x/m + 0.5; + UV.w = r.y/m + 0.5; +#endif + #if NORMAL_MAP tangentPassthrough = tangent.xyz; #endif -#if !VERTEX_LIGHTING +#if NEED_NORMAL normalPassthrough = normal.xyz; #endif #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING @@ -151,14 +175,14 @@ float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); - depthPassthrough = shMatrixMult(fixedWVP, shInputPosition).z; + objSpacePositionPassthrough.w = shMatrixMult(fixedWVP, shInputPosition).z; #else - depthPassthrough = shOutputPosition.z; + objSpacePositionPassthrough.w = shOutputPosition.z; #endif #endif - objSpacePositionPassthrough = shInputPosition.xyz; + objSpacePositionPassthrough.xyz = shInputPosition.xyz; #if SHADOWS lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @@ -173,7 +197,6 @@ #if VERTEX_LIGHTING float3 viewPos = shMatrixMult(worldView, shInputPosition).xyz; - float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); float3 lightDir; float d; @@ -212,9 +235,7 @@ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; #endif -#if VERTEXCOLOR_MODE != 2 lightResult.a *= materialDiffuse.a; -#endif #endif } @@ -228,34 +249,55 @@ #endif SH_BEGIN_PROGRAM +#if DIFFUSE_MAP shSampler2D(diffuseMap) +#endif #if NORMAL_MAP shSampler2D(normalMap) #endif -#if EMISSIVE_MAP - shSampler2D(emissiveMap) +#if DARK_MAP + shSampler2D(darkMap) #endif #if DETAIL_MAP shSampler2D(detailMap) #endif +#if EMISSIVE_MAP + shSampler2D(emissiveMap) +#endif + +#if ENV_MAP + shSampler2D(envMap) + shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) +#endif + +#if SPEC_MAP + shSampler2D(specMap) +#endif + +#if ENV_MAP || SPECULAR || PARALLAX + shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif +#if SPECULAR + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPosObjSpace0) @shAutoConstant(lightPosObjSpace0, light_position_object_space, 0) + shUniform(float, matShininess) @shAutoConstant(matShininess, surface_shininess) + shUniform(float3, matSpec) @shAutoConstant(matSpec, surface_specular_colour) +#endif + shInput(float4, UV) #if NORMAL_MAP shInput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING +#if NEED_NORMAL shInput(float3, normalPassthrough) #endif -#ifdef NEED_DEPTH - shInput(float, depthPassthrough) -#endif - - shInput(float3, objSpacePositionPassthrough) + shInput(float4, objSpacePositionPassthrough) #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING shInput(float4, colourPassthrough) @@ -298,7 +340,6 @@ shInput(float4, lightResult) shInput(float3, directionalResult) #else - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_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)) @@ -307,9 +348,7 @@ #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 @@ -317,18 +356,17 @@ SH_START_PROGRAM { - shOutputColour(0) = shSample(diffuseMap, UV.xy); + float4 newUV = UV; -#if DETAIL_MAP -#if @shPropertyString(detailMapUVSet) - shOutputColour(0) *= shSample(detailMap, UV.zw)*2; -#else - shOutputColour(0) *= shSample(detailMap, UV.xy)*2; +#ifdef NEED_DEPTH + float depthPassthrough = objSpacePositionPassthrough.w; #endif + +#if NEED_NORMAL + float3 normal = normalPassthrough; #endif #if NORMAL_MAP - float3 normal = normalPassthrough; float3 binormal = cross(tangentPassthrough.xyz, normal.xyz); float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz); @@ -336,13 +374,51 @@ tbn = transpose(tbn); #endif - float3 TSnormal = shSample(normalMap, UV.xy).xyz * 2 - 1; + float4 normalTex = shSample(normalMap, UV.xy); + + normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2 - 1 )); +#endif + +#if ENV_MAP || SPECULAR || PARALLAX + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); +#endif + +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); + + newUV += (TSeyeDir.xyxy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS )).xyxy; +#endif + +#if DIFFUSE_MAP + #if @shPropertyString(diffuseMapUVSet) + float4 diffuse = shSample(diffuseMap, newUV.zw); + #else + float4 diffuse = shSample(diffuseMap, newUV.xy); + #endif +#else + float4 diffuse = float4(1,1,1,1); +#endif + +#if DETAIL_MAP +#if @shPropertyString(detailMapUVSet) + diffuse *= shSample(detailMap, newUV.zw)*2; +#else + diffuse *= shSample(detailMap, newUV.xy)*2; +#endif +#endif - normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); +#if DARK_MAP +#if @shPropertyString(darkMapUVSet) + diffuse *= shSample(darkMap, newUV.zw); +#else + diffuse *= shSample(darkMap, newUV.xy); +#endif #endif + shOutputColour(0) = diffuse; + #if !VERTEX_LIGHTING - float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough,1)).xyz; + float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); float3 lightDir; @@ -381,9 +457,7 @@ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; #endif -#if VERTEXCOLOR_MODE != 2 lightResult.a *= materialDiffuse.a; -#endif #endif // shadows only for the first (directional) light @@ -407,7 +481,7 @@ #if (UNDERWATER) || (FOG) - float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; + float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough.xyz,1)).xyz; #endif #if UNDERWATER @@ -420,6 +494,44 @@ shOutputColour(0) *= lightResult; #endif +#if EMISSIVE_MAP + #if @shPropertyString(emissiveMapUVSet) + shOutputColour(0).xyz += shSample(emissiveMap, newUV.zw).xyz; + #else + shOutputColour(0).xyz += shSample(emissiveMap, newUV.xy).xyz; + #endif +#endif + +#if ENV_MAP + // Everything looks better with fresnel + float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); + float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); + + shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; +#endif + +#if SPECULAR + float3 light0Dir = normalize(lightPosObjSpace0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float shininess = matShininess; +#if SPEC_MAP + float4 specTex = shSample(specMap, UV.xy); + shininess *= (specTex.a); +#endif + + float3 specular = pow(max(dot(normal, halfVec), 0), shininess) * lightSpec0 * matSpec; +#if SPEC_MAP + specular *= specTex.xyz; +#else + specular *= diffuse.a; +#endif + + shOutputColour(0).xyz += specular * shadow; +#endif + #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); @@ -430,14 +542,6 @@ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif -#endif - -#if EMISSIVE_MAP - #if @shPropertyString(emissiveMapUVSet) - shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; - #else - shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; - #endif #endif // prevent negative colour output (for example with negative lights) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 861841a84..1436de0c3 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -27,6 +27,16 @@ #define COMPOSITE_MAP @shPropertyBool(display_composite_map) +#define NORMAL_MAP @shPropertyBool(normal_map_enabled) +#define PARALLAX @shPropertyBool(parallax_enabled) + +#define VERTEX_LIGHTING (!NORMAL_MAP) + +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + +// This is just for the permutation handler +#define NORMAL_MAPS @shPropertyString(normal_maps) #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -37,8 +47,13 @@ @shAllocatePassthrough(3, worldPos) #if LIGHTING +@shAllocatePassthrough(3, normalPassthrough) +#if VERTEX_LIGHTING @shAllocatePassthrough(3, lightResult) @shAllocatePassthrough(3, directionalResult) +#else +@shAllocatePassthrough(3, colourPassthrough) +#endif #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) @@ -63,18 +78,18 @@ #endif shVertexInput(float2, uv0) - shVertexInput(float2, uv1) // lodDelta, lodThreshold #if LIGHTING shNormalInput(float4) shColourInput(float4) - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) +#if VERTEX_LIGHTING 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) - +#endif + #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) #endif @@ -122,6 +137,13 @@ @shPassthroughAssign(worldPos, worldPos.xyz); +#if LIGHTING + @shPassthroughAssign(normalPassthrough, normal.xyz); +#endif +#if LIGHTING && !VERTEX_LIGHTING + @shPassthroughAssign(colourPassthrough, colour.xyz); +#endif + #if LIGHTING #if SHADOWS @@ -139,6 +161,7 @@ #endif +#if VERTEX_LIGHTING // Lighting float3 lightDir; float d; @@ -164,6 +187,7 @@ @shPassthroughAssign(lightResult, lightResult); @shPassthroughAssign(directionalResult, directionalResult); +#endif #endif } @@ -189,6 +213,9 @@ @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) +#if @shPropertyBool(use_normal_map_@shIterator) + shSampler2D(normalMap@shIterator) +#endif @shEndForeach #endif @@ -201,6 +228,15 @@ @shPassthroughFragmentInputs #if LIGHTING + +#if !VERTEX_LIGHTING +shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_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) +shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) +#endif + #if SHADOWS shSampler2D(shadowMap0) shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) @@ -220,13 +256,21 @@ #if (UNDERWATER) || (FOG) shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) - shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) #endif #if UNDERWATER shUniform(float, waterLevel) @shSharedParameter(waterLevel) #endif + +// For specular +#if LIGHTING + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPos0) @shAutoConstant(lightPos0, light_position, 0) +#endif + +shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + SH_START_PROGRAM { @@ -237,12 +281,32 @@ float2 UV = @shPassthroughReceive(UV); float3 worldPos = @shPassthroughReceive(worldPos); - - + +#if LIGHTING + float3 normal = @shPassthroughReceive(normalPassthrough); +#endif + +#if LIGHTING && !VERTEX_LIGHTING + +#if NORMAL_MAP + // derive the tangent space basis + float3 tangent = float3(1,0, 0); + + float3 binormal = normalize(cross(tangent, normal)); + tangent = normalize(cross(normal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + + // derive final matrix + float3x3 tbn = float3x3(tangent, binormal, normal); + #if SH_GLSL + tbn = transpose(tbn); + #endif +#endif + +#endif + #if UNDERWATER float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif - #if !IS_FIRST_PASS // Opacity the previous passes should have, i.e. 1 - (opacity of this pass) @@ -252,6 +316,8 @@ float previousAlpha = 1.f; shOutputColour(0) = float4(1,1,1,1); +float3 TSnormal = float3(0,0,1); + #if COMPOSITE_MAP shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; #else @@ -266,39 +332,88 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; @shEndForeach - float3 albedo = float3(0,0,0); + float4 albedo = float4(0,0,0,1); - float2 layerUV = UV * 16; + float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents + float2 thisLayerUV; + float4 normalTex; + float4 diffuseTex; + + float3 eyeDir = normalize(cameraPos.xyz - worldPos); +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); +#endif @shForeach(@shPropertyString(num_layers)) + thisLayerUV = layerUV; +#if @shPropertyBool(use_normal_map_@shIterator) + normalTex = shSample(normalMap@shIterator, thisLayerUV); +#if @shIterator == 0 && IS_FIRST_PASS + TSnormal = normalize(normalTex.xyz * 2 - 1); +#else + TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator)); +#endif +#endif +#if @shPropertyBool(use_parallax_@shIterator) + thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); +#endif -#if IS_FIRST_PASS - #if @shIterator == 0 - // first layer of first pass is the base layer and doesn't need a blend map - albedo = shSample(diffuseMap0, layerUV).rgb; - #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); - #endif + diffuseTex = shSample(diffuseMap@shIterator, layerUV); +#if !@shPropertyBool(use_specular_@shIterator) + diffuseTex.a = 0; +#endif + +#if @shIterator == 0 +albedo = diffuseTex; #else - #if @shIterator == 0 - albedo = shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator); - #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); - #endif +albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_component_@shIterator)); +#endif + +#if !IS_FIRST_PASS previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator); #endif + + @shEndForeach - shOutputColour(0).rgb *= albedo; + shOutputColour(0).rgb *= albedo.xyz; #endif #if LIGHTING + +#if VERTEX_LIGHTING // Lighting float3 lightResult = @shPassthroughReceive(lightResult); float3 directionalResult = @shPassthroughReceive(directionalResult); - +#else + +#if NORMAL_MAP + normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); +#endif + + float3 colour = @shPassthroughReceive(colourPassthrough); + float3 lightDir; + float d; + float3 lightResult = float3(0,0,0); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (worldPos * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + lightResult.xyz += lightDiffuse[@shIterator].xyz + * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normal.xyz, lightDir), 0); +#if @shIterator == 0 + float3 directionalResult = lightResult.xyz; +#endif + @shEndForeach + lightResult.xyz += lightAmbient.xyz; + lightResult.xyz *= colour.xyz; + directionalResult.xyz *= colour.xyz; +#endif + // shadows only for the first (directional) light #if SHADOWS float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); @@ -325,6 +440,17 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); #endif +#if LIGHTING && !COMPOSITE_MAP + // Specular + float3 light0Dir = normalize(lightPos0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0; + shOutputColour(0).xyz += specular * (albedo.a) * shadow; +#endif + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); diff --git a/files/materials/water.mat b/files/materials/water.mat index 1e5f8c8e0..ade55f326 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -37,7 +37,7 @@ material Water texture_unit normalMap { - texture water_nm.png 5 + texture water_nm.png } texture_unit rippleNormalMap diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 1ec1e08cb..ef223a617 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -8,8 +8,6 @@ set(MYGUI_FILES black.png core.skin core.xml - EBGaramond-Regular.ttf - Obliviontt.zip openmw_alchemy_window.layout openmw_book.layout openmw_box.skin.xml @@ -81,6 +79,8 @@ set(MYGUI_FILES openmw_merchantrepair.layout openmw_repair.layout openmw_companion_window.layout + openmw_savegame_dialog.layout + openmw_recharge_dialog.layout smallbars.png DejaVuLGCSansMono.ttf markers.png diff --git a/files/mygui/EBGaramond-Regular.ttf b/files/mygui/EBGaramond-Regular.ttf deleted file mode 100644 index 3f6f6c191..000000000 Binary files a/files/mygui/EBGaramond-Regular.ttf and /dev/null differ diff --git a/files/mygui/Obliviontt.zip b/files/mygui/Obliviontt.zip deleted file mode 100644 index af4f809fd..000000000 Binary files a/files/mygui/Obliviontt.zip and /dev/null differ diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index b368a6407..d13a5a02d 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -1,20 +1,20 @@ - + - + - + - + - + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 3c0348b66..aae3c7035 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -1,20 +1,20 @@ - + - + - + - + - + @@ -22,12 +22,12 @@ - + - + @@ -35,37 +35,37 @@ - - + + - + - - - - - + + + + + - + - - - - - + + + + + - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index 92382640b..890e2aac1 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -1,13 +1,14 @@ - + - + + - + @@ -22,12 +23,12 @@ - + - + @@ -35,37 +36,37 @@ - - + + - - + + - - - - - + + + + + - - + + - - - - - + + + + + - + diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout index 5d18f4bff..db55e8754 100644 --- a/files/mygui/openmw_chargen_review.layout +++ b/files/mygui/openmw_chargen_review.layout @@ -1,10 +1,10 @@ - + - + @@ -17,27 +17,27 @@ - - - - + + + + - - + + - + - + @@ -46,57 +46,57 @@ - - + + - + - + - + - + - + - + - + @@ -106,12 +106,12 @@ - + - + diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index bfda40c68..0c9a97d04 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -7,8 +7,12 @@ - + + + + + diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml index 219cce39a..470451a0e 100644 --- a/files/mygui/openmw_console.skin.xml +++ b/files/mygui/openmw_console.skin.xml @@ -2,19 +2,6 @@ - - - - - - - - - - - - - diff --git a/files/mygui/openmw_container_window.layout b/files/mygui/openmw_container_window.layout index 06cc04ebe..87651b0f2 100644 --- a/files/mygui/openmw_container_window.layout +++ b/files/mygui/openmw_container_window.layout @@ -3,6 +3,7 @@ + diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml index 726bfb281..e4037561d 100644 --- a/files/mygui/openmw_font.xml +++ b/files/mygui/openmw_font.xml @@ -1,31 +1,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index e39777dd0..72d337e45 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -45,6 +45,7 @@ + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index ecccd995b..09e5ed9c7 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -2,7 +2,7 @@ - + @@ -12,11 +12,10 @@ - - - + + - + diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout index 003eb1c6a..5fc286298 100644 --- a/files/mygui/openmw_itemselection_dialog.layout +++ b/files/mygui/openmw_itemselection_dialog.layout @@ -1,13 +1,13 @@ - + - + - + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 02c11c354..7972527ac 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -126,6 +126,19 @@ + + + + + + + + + + + + + diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index 232f31b75..6e0efce7e 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_messagebox.layout b/files/mygui/openmw_messagebox.layout index dfdb57648..b2d29271b 100644 --- a/files/mygui/openmw_messagebox.layout +++ b/files/mygui/openmw_messagebox.layout @@ -1,11 +1,11 @@ - - - + + + + + diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index 35114ffeb..f5418e3f8 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -17,6 +17,11 @@ + + + + + @@ -65,6 +70,10 @@ + + + + diff --git a/files/mygui/openmw_recharge_dialog.layout b/files/mygui/openmw_recharge_dialog.layout new file mode 100644 index 000000000..49e735764 --- /dev/null +++ b/files/mygui/openmw_recharge_dialog.layout @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 2c3908a1b..3901ab825 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -301,4 +301,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout new file mode 100644 index 000000000..ceb1a8428 --- /dev/null +++ b/files/mygui/openmw_savegame_dialog.layout @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index ebfaf678a..61103963d 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -65,6 +65,12 @@ + + + + + + @@ -216,7 +222,11 @@ - + + + + + @@ -308,9 +318,9 @@ - + - + @@ -336,9 +346,14 @@ - + + + + + + - + diff --git a/files/mygui/openmw_spell_buying_window.layout b/files/mygui/openmw_spell_buying_window.layout index 1510372dd..b24a476c4 100644 --- a/files/mygui/openmw_spell_buying_window.layout +++ b/files/mygui/openmw_spell_buying_window.layout @@ -11,7 +11,7 @@ - + diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index ab924da6d..ec655ace8 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index 5ae3f96ca..6cdd4c02a 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -2,7 +2,7 @@ - + @@ -100,7 +100,9 @@ - + + + @@ -114,7 +116,7 @@ - + @@ -128,7 +130,7 @@ - + @@ -142,7 +144,7 @@ - + @@ -156,7 +158,7 @@ - + @@ -170,7 +172,7 @@ - + @@ -184,7 +186,7 @@ - + @@ -198,7 +200,7 @@ - + @@ -212,6 +214,7 @@ + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index 6a1dea60b..15287bc74 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -17,14 +17,6 @@ - - - - - - - - diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index 624c133f2..3f4fec59f 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -184,11 +184,8 @@ - - - diff --git a/files/mygui/openmw_travel_window.layout b/files/mygui/openmw_travel_window.layout index db3fa24a0..683d47fe7 100644 --- a/files/mygui/openmw_travel_window.layout +++ b/files/mygui/openmw_travel_window.layout @@ -11,7 +11,7 @@ - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/files/opencs.desktop b/files/opencs.desktop index f6aad5b09..638de6ebf 100644 --- a/files/opencs.desktop +++ b/files/opencs.desktop @@ -3,6 +3,7 @@ Type=Application Name=OpenMW Content Editor GenericName=Content Editor Comment=A replacement for the Morrowind Construction Set. +Keywords=Morrowind;Construction Set;Creation Kit Editor;Set;Kit; TryExec=opencs Exec=opencs Icon=opencs diff --git a/files/opencs/defaultfilters b/files/opencs/defaultfilters new file mode 100644 index 000000000..0ac3c8db4 Binary files /dev/null and b/files/opencs/defaultfilters differ diff --git a/files/openmw.desktop b/files/openmw.desktop index 118cd3bbe..4a3a76f52 100644 --- a/files/openmw.desktop +++ b/files/openmw.desktop @@ -3,6 +3,7 @@ Type=Application Name=OpenMW Launcher GenericName=Role Playing Game Comment=An engine replacement for The Elder Scrolls III: Morrowind +Keywords=Morrowind;Reimplementation Mods;esm;bsa; TryExec=omwlauncher Exec=omwlauncher Icon=openmw diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f191430df..4fb7097f8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,4 +1,4 @@ -# WARNING: Editing this file might have no effect, as these +# WARNING: Editing this file might have no effect, as these # settings are overwritten by your user settings file. [Video] @@ -13,7 +13,7 @@ screen = 0 # Valid values: # OpenGL Rendering Subsystem # Direct3D9 Rendering Subsystem -render system = +render system = # Valid values: # none @@ -53,8 +53,7 @@ texture filtering = anisotropic anisotropy = 4 # Number of texture mipmaps to generate -# This setting is currently ignored due to mipmap generation problems on Intel/AMD -#num mipmaps = 5 +num mipmaps = 8 shader mode = @@ -156,6 +155,8 @@ voice volume = 1.0 [Input] +grab cursor = true + invert y axis = false camera sensitivity = 1.0 @@ -169,3 +170,72 @@ ui y multiplier = 1.0 [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false + +[Saves] +character = + +[Windows] +inventory x = 0 +inventory y = 0.4275 +inventory w = 0.6225 +inventory h = 0.5725 + +inventory container x = 0 +inventory container y = 0.4275 +inventory container w = 0.6225 +inventory container h = 0.5725 + +inventory barter x = 0 +inventory barter y = 0.4275 +inventory barter w = 0.6225 +inventory barter h = 0.5725 + +inventory companion x = 0 +inventory companion y = 0.4275 +inventory companion w = 0.6225 +inventory companion h = 0.5725 + +container x = 0.25 +container y = 0 +container w = 0.75 +container h = 0.375 + +companion x = 0.25 +companion y = 0 +companion w = 0.75 +companion h = 0.375 + +map x = 0.625 +map y = 0 +map w = 0.375 +map h = 0.5725 + +barter x = 0.25 +barter y = 0 +barter w = 0.75 +barter h = 0.375 + +alchemy x = 0.25 +alchemy y = 0.25 +alchemy w = 0.5 +alchemy h = 0.5 + +stats x = 0 +stats y = 0 +stats w = 0.375 +stats h = 0.4275 + +spells x = 0.3775 +spells y = 0.4275 +spells w = 0.375 +spells h = 0.5725 + +console x = 0 +console y = 0 +console w = 1 +console h = 0.5 + +dialogue h = 0.810 +dialogue w = 0.810 +dialogue x = 0.095 +dialogue y = 0.095 diff --git a/files/ui/contentselector.ui b/files/ui/contentselector.ui new file mode 100644 index 000000000..b9b5ba5a0 --- /dev/null +++ b/files/ui/contentselector.ui @@ -0,0 +1,111 @@ + + + ContentSelector + + + + 0 + 0 + 518 + 436 + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + + 0 + + + + + Content + + + + 3 + + + + + false + + + + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + true + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideLeft + + + false + + + false + + + true + + + false + + + + + + + + + + + ContentSelectorView::ComboBox + QComboBox +

components/contentselector/view/combobox.hpp
+ + + + + diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 041a9576d..eb5ebc61d 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -7,108 +7,97 @@ 0 0 518 - 304 + 108 + + + 0 + 0 + + + + Qt::DefaultContextMenu + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter: - - - - - - - + - - - - 0 - 0 - + + + Qt::NoFocus - - Qt::Horizontal + + Profile - + false - - + + + 6 + + + 3 + + + 6 + + + 0 + + + 6 + + + + + true + + + + 0 + 0 + + + + Select a profiile + + + + + + + New Profile + + + &New Profile + + + true + + + + + + + Delete Profile + + + Delete Profile + + + Ctrl+D + + + true + + + + - - - - - - Current Profile: - - - - - - - true - - - - 0 - 0 - - - - - - - - New Profile - - - &New Profile - - - true - - - - - - - Delete Profile - - - Delete Profile - - - Ctrl+D - - - true - - - - - @@ -127,6 +116,9 @@ + + false + @@ -140,6 +132,9 @@ + + true + Check Selection @@ -151,15 +146,10 @@ - - LineEdit - QLineEdit -
components/fileorderlist/utils/lineedit.hpp
-
ProfilesComboBox QComboBox -
components/fileorderlist/utils/profilescombobox.hpp
+
apps/launcher/utils/profilescombobox.hpp
diff --git a/files/ui/filedialog.ui b/files/ui/filedialog.ui new file mode 100644 index 000000000..b3af166da --- /dev/null +++ b/files/ui/filedialog.ui @@ -0,0 +1,73 @@ + + + FileDialog + + + + 0 + 0 + 518 + 109 + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + + + + + + + Qt::NoFocus + + + Project Name + + + false + + + + 6 + + + 3 + + + 6 + + + 0 + + + 6 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui index a1dfb172b..5f2be05a2 100644 --- a/files/ui/mainwindow.ui +++ b/files/ui/mainwindow.ui @@ -2,6 +2,14 @@ MainWindow + + + 0 + 0 + 575 + 535 + + 575 @@ -56,11 +64,35 @@ - - - QDialogButtonBox::Close - - + + + + + OpenMW version + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Close + + + + diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp index b0fa07fd6..de9ea6f57 100644 --- a/libs/openengine/bullet/BtOgre.cpp +++ b/libs/openengine/bullet/BtOgre.cpp @@ -819,6 +819,8 @@ namespace BtOgre { */ DynamicRenderable::DynamicRenderable() + : mVertexBufferCapacity(0) + , mIndexBufferCapacity(0) { } diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h index b20a3ff98..9572b8a7b 100644 --- a/libs/openengine/bullet/BtOgreExtras.h +++ b/libs/openengine/bullet/BtOgreExtras.h @@ -13,8 +13,8 @@ * ===================================================================================== */ -#ifndef _BtOgreShapes_H_ -#define _BtOgreShapes_H_ +#ifndef BtOgreShapes_H_ +#define BtOgreShapes_H_ #include "btBulletDynamicsCommon.h" #include "OgreSimpleRenderable.h" diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h index 4ce2f181e..dde606a4f 100644 --- a/libs/openengine/bullet/BtOgreGP.h +++ b/libs/openengine/bullet/BtOgreGP.h @@ -14,8 +14,8 @@ * ===================================================================================== */ -#ifndef _BtOgrePG_H_ -#define _BtOgrePG_H_ +#ifndef BtOgrePG_H_ +#define BtOgrePG_H_ #include "btBulletDynamicsCommon.h" #include "BtOgreExtras.h" diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h index 9ff069a8f..2e42fe1f9 100644 --- a/libs/openengine/bullet/BtOgrePG.h +++ b/libs/openengine/bullet/BtOgrePG.h @@ -14,8 +14,8 @@ * ===================================================================================== */ -#ifndef _BtOgreGP_H_ -#define _BtOgreGP_H_ +#ifndef BtOgreGP_H_ +#define BtOgreGP_H_ #include "btBulletDynamicsCommon.h" #include "OgreSceneNode.h" diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h index 98cda859d..0e5c65226 100644 --- a/libs/openengine/bullet/BulletShapeLoader.h +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -1,5 +1,5 @@ -#ifndef _BULLET_SHAPE_LOADER_H_ -#define _BULLET_SHAPE_LOADER_H_ +#ifndef OPENMW_BULLET_SHAPE_LOADER_H_ +#define OPENMW_BULLET_SHAPE_LOADER_H_ #include #include diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp deleted file mode 100644 index c20415884..000000000 --- a/libs/openengine/bullet/CMotionState.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "CMotionState.h" -#include "physic.hpp" - -#include -#include -#include - -namespace OEngine { -namespace Physic -{ - - CMotionState::CMotionState(PhysicEngine* eng,std::string name) - : isPC(false) - , isNPC(true) - { - pEng = eng; - tr.setIdentity(); - pName = name; - } - - void CMotionState::getWorldTransform(btTransform &worldTrans) const - { - worldTrans = tr; - } - - void CMotionState::setWorldTransform(const btTransform &worldTrans) - { - tr = worldTrans; - - PhysicEvent evt; - evt.isNPC = isNPC; - evt.isPC = isPC; - evt.newTransform = tr; - evt.RigidBodyName = pName; - - if(isPC) - { - pEng->PEventList.push_back(evt); - } - else - { - pEng->NPEventList.push_back(evt); - } - } - -}} diff --git a/libs/openengine/bullet/CMotionState.h b/libs/openengine/bullet/CMotionState.h deleted file mode 100644 index 3508ab4ef..000000000 --- a/libs/openengine/bullet/CMotionState.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef OENGINE_CMOTIONSTATE_H -#define OENGINE_CMOTIONSTATE_H - -#include -#include - -namespace OEngine { -namespace Physic -{ - class PhysicEngine; - - /** - * A CMotionState is associated with a single RigidBody. - * When the RigidBody is moved by bullet, bullet will call the function setWorldTransform. - * for more info, see the bullet Wiki at btMotionState. - */ - class CMotionState:public btMotionState - { - public: - - CMotionState(PhysicEngine* eng,std::string name); - - /** - * Return the position of the RigidBody. - */ - virtual void getWorldTransform(btTransform &worldTrans) const; - - /** - * Function called by bullet when the RigidBody is moved. - * It add an event to the EventList of the PhysicEngine class. - */ - virtual void setWorldTransform(const btTransform &worldTrans); - - protected: - PhysicEngine* pEng; - btTransform tr; - bool isNPC; - bool isPC; - - std::string pName; - }; - - struct PhysicEvent - { - bool isNPC; - bool isPC; - btTransform newTransform; - std::string RigidBodyName; - }; - -}} -#endif diff --git a/libs/openengine/bullet/btKinematicCharacterController.cpp b/libs/openengine/bullet/btKinematicCharacterController.cpp deleted file mode 100644 index fc4f3278f..000000000 --- a/libs/openengine/bullet/btKinematicCharacterController.cpp +++ /dev/null @@ -1,643 +0,0 @@ -/* -Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com - -This software is provided 'as-is', without any express or implied warranty. -In no event will the authors be held liable for any damages arising from the use of this software. -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it freely, -subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -#include "LinearMath/btIDebugDraw.h" -#include "BulletCollision/CollisionDispatch/btGhostObject.h" -#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" -#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" -#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" -#include "BulletCollision/CollisionDispatch/btCollisionWorld.h" -#include "LinearMath/btDefaultMotionState.h" -#include "btKinematicCharacterController.h" - -///@todo Interact with dynamic objects, -///Ride kinematicly animated platforms properly -///Support ducking -class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback -{ -public: - btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) - { - m_me[0] = me; - count = 1; - } - - btKinematicClosestNotMeRayResultCallback (btCollisionObject* me[], int count_) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) - { - count = count_; - - for(int i = 0; i < count; i++) - m_me[i] = me[i]; - } - - virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) - { - for(int i = 0; i < count; i++) - if (rayResult.m_collisionObject == m_me[i]) - return 1.0; - - return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace); - } -protected: - btCollisionObject* m_me[10]; - int count; -}; - -class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback -{ -public: - btKinematicClosestNotMeConvexResultCallback( btCollisionObject* me, const btVector3& up, btScalar minSlopeDot ) - : btCollisionWorld::ClosestConvexResultCallback( btVector3( 0.0, 0.0, 0.0 ), btVector3( 0.0, 0.0, 0.0 ) ), - m_me( me ), m_up( up ), m_minSlopeDot( minSlopeDot ) - { - } - - virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) - { - if( convexResult.m_hitCollisionObject == m_me ) - return btScalar( 1 ); - - btVector3 hitNormalWorld; - if( normalInWorldSpace ) - { - hitNormalWorld = convexResult.m_hitNormalLocal; - } - else - { - ///need to transform normal into worldspace - hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; - } - - // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... - - btScalar dotUp = m_up.dot(hitNormalWorld); - if( dotUp < m_minSlopeDot ) - return btScalar( 1 ); - - return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace); - } - -protected: - btCollisionObject* m_me; - const btVector3 m_up; - btScalar m_minSlopeDot; -}; - - - -btKinematicCharacterController::btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject_, - btPairCachingGhostObject* internalGhostObject_, - btScalar stepHeight, - btScalar constantScale, - btScalar gravity, - btScalar fallVelocity, - btScalar jumpVelocity, - btScalar recoveringFactor ) -{ - m_upAxis = btKinematicCharacterController::Y_AXIS; - - m_walkDirection.setValue( btScalar( 0 ), btScalar( 0 ), btScalar( 0 ) ); - - m_useGhostObjectSweepTest = true; - - externalGhostObject = externalGhostObject_; - internalGhostObject = internalGhostObject_; - - m_recoveringFactor = recoveringFactor; - - m_stepHeight = stepHeight; - - m_useWalkDirection = true; // use walk direction by default, legacy behavior - m_velocityTimeInterval = btScalar( 0 ); - m_verticalVelocity = btScalar( 0 ); - m_verticalOffset = btScalar( 0 ); - - m_gravity = constantScale * gravity; - m_fallSpeed = constantScale * fallVelocity; // Terminal velocity of a sky diver in m/s. - - m_jumpSpeed = constantScale * jumpVelocity; // ? - m_wasJumping = false; - - setMaxSlope( btRadians( 45.0 ) ); - - mCollision = true; -} - - -btKinematicCharacterController::~btKinematicCharacterController () -{ -} - -void btKinematicCharacterController::setVerticalVelocity(float z) -{ - m_verticalVelocity = z; -} - -bool btKinematicCharacterController::recoverFromPenetration( btCollisionWorld* collisionWorld ) -{ - bool penetration = false; - - if(!mCollision) return penetration; - - collisionWorld->getDispatcher()->dispatchAllCollisionPairs( internalGhostObject->getOverlappingPairCache(), - collisionWorld->getDispatchInfo(), - collisionWorld->getDispatcher() ); - - btVector3 currentPosition = internalGhostObject->getWorldTransform().getOrigin(); - - btScalar maxPen = btScalar( 0 ); - - for( int i = 0; i < internalGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++ ) - { - m_manifoldArray.resize(0); - - btBroadphasePair* collisionPair = &internalGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; - - if( collisionPair->m_algorithm ) - collisionPair->m_algorithm->getAllContactManifolds( m_manifoldArray ); - - - for( int j = 0; j < m_manifoldArray.size(); j++ ) - { - btPersistentManifold* manifold = m_manifoldArray[j]; - - btScalar directionSign = manifold->getBody0() == internalGhostObject ? btScalar( -1.0 ) : btScalar( 1.0 ); - - for( int p = 0; p < manifold->getNumContacts(); p++ ) - { - const btManifoldPoint&pt = manifold->getContactPoint( p ); - if( (manifold->getBody1() == externalGhostObject && manifold->getBody0() == internalGhostObject) - ||(manifold->getBody0() == externalGhostObject && manifold->getBody1() == internalGhostObject) ) - { - } - else - { - btScalar dist = pt.getDistance(); - - if( dist < 0.0 ) - { - if( dist < maxPen ) - maxPen = dist; - - // NOTE : btScalar affects the stairs but the parkinson... - // 0.0 , the capsule can break the walls... - currentPosition += pt.m_normalWorldOnB * directionSign * dist * m_recoveringFactor; - - penetration = true; - } - } - } - - // ??? - //manifold->clearManifold(); - } - } - - btTransform transform = internalGhostObject->getWorldTransform(); - - transform.setOrigin( currentPosition ); - - internalGhostObject->setWorldTransform( transform ); - externalGhostObject->setWorldTransform( transform ); - - return penetration; -} - - -btVector3 btKinematicCharacterController::stepUp( btCollisionWorld* world, const btVector3& currentPosition, btScalar& currentStepOffset ) -{ - btVector3 targetPosition = currentPosition + getUpAxisDirections()[ m_upAxis ] * ( m_stepHeight + ( m_verticalOffset > btScalar( 0.0 ) ? m_verticalOffset : 0.0 ) ); - - //if the no collisions mode is on, no need to go any further - if(!mCollision) - { - currentStepOffset = m_stepHeight; - return targetPosition; - } - - // Retrieve the collision shape - // - btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); - btAssert( collisionShape->isConvex() ); - - btConvexShape* convexShape = ( btConvexShape* )collisionShape; - - // FIXME: Handle penetration properly - // - btTransform start; - start.setIdentity(); - start.setOrigin( currentPosition + getUpAxisDirections()[ m_upAxis ] * ( convexShape->getMargin() ) ); - - btTransform end; - end.setIdentity(); - end.setOrigin( targetPosition ); - - btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, -getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); - callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; - callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; - - // Sweep test - // - if( m_useGhostObjectSweepTest ) - externalGhostObject->convexSweepTest( convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration ); - - else - world->convexSweepTest( convexShape, start, end, callback ); - - if( callback.hasHit() ) - { - // Only modify the position if the hit was a slope and not a wall or ceiling. - // - if( callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > btScalar( 0.0 ) ) - { - // We moved up only a fraction of the step height - // - currentStepOffset = m_stepHeight * callback.m_closestHitFraction; - - return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); - } - - m_verticalVelocity = btScalar( 0.0 ); - m_verticalOffset = btScalar( 0.0 ); - - return currentPosition; - } - else - { - currentStepOffset = m_stepHeight; - return targetPosition; - } -} - - -///Reflect the vector d around the vector r -inline btVector3 reflect( const btVector3& d, const btVector3& r ) -{ - return d - ( btScalar( 2.0 ) * d.dot( r ) ) * r; -} - - -///Project a vector u on another vector v -inline btVector3 project( const btVector3& u, const btVector3& v ) -{ - return v * u.dot( v ); -} - - -///Helper for computing the character sliding -inline btVector3 slide( const btVector3& direction, const btVector3& planeNormal ) -{ - return direction - project( direction, planeNormal ); -} - - - -btVector3 slideOnCollision( const btVector3& fromPosition, const btVector3& toPosition, const btVector3& hitNormal ) -{ - btVector3 moveDirection = toPosition - fromPosition; - btScalar moveLength = moveDirection.length(); - - if( moveLength <= btScalar( SIMD_EPSILON ) ) - return toPosition; - - moveDirection.normalize(); - - btVector3 reflectDir = reflect( moveDirection, hitNormal ); - reflectDir.normalize(); - - return fromPosition + slide( reflectDir, hitNormal ) * moveLength; -} - - -btVector3 btKinematicCharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ) -{ - // We go to ! - // - btVector3 targetPosition = currentPosition + walkMove; - - //if the no collisions mode is on, no need to go any further - if(!mCollision) return targetPosition; - - // Retrieve the collision shape - // - btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); - btAssert( collisionShape->isConvex() ); - - btConvexShape* convexShape = ( btConvexShape* )collisionShape; - - btTransform start; - start.setIdentity(); - - btTransform end; - end.setIdentity(); - - btScalar fraction = btScalar( 1.0 ); - - // This optimization scheme suffers in the corners. - // It basically jumps from a wall to another, then fails to find a new - // position (after 4 iterations here) and finally don't move at all. - // - // The stepping algorithm adds some problems with stairs. It seems - // the treads create some fake corner using capsules for collisions. - // - for( int i = 0; i < 4 && fraction > btScalar( 0.01 ); i++ ) - { - start.setOrigin( currentPosition ); - end.setOrigin( targetPosition ); - - btVector3 sweepDirNegative = currentPosition - targetPosition; - - btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, sweepDirNegative, btScalar( 0.0 ) ); - callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; - callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; - - if( m_useGhostObjectSweepTest ) - externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); - - else - collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); - - if( callback.hasHit() ) - { - // Try another target position - // - targetPosition = slideOnCollision( currentPosition, targetPosition, callback.m_hitNormalWorld ); - fraction = callback.m_closestHitFraction; - } - else - - // Move to the valid target position - // - return targetPosition; - } - - // Don't move if you can't find a valid target position... - // It prevents some flickering. - // - return currentPosition; -} - - -///Handle the gravity -btScalar btKinematicCharacterController::addFallOffset( bool wasOnGround, btScalar currentStepOffset, btScalar dt ) -{ - btScalar downVelocity = ( m_verticalVelocity < 0.0 ? -m_verticalVelocity : btScalar( 0.0 ) ) * dt; - - if( downVelocity > btScalar( 0.0 ) && downVelocity < m_stepHeight && ( wasOnGround || !m_wasJumping ) ) - downVelocity = m_stepHeight; - - return currentStepOffset + downVelocity; -} - - -btVector3 btKinematicCharacterController::stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ) -{ - btVector3 stepDrop = getUpAxisDirections()[ m_upAxis ] * currentStepOffset; - - // Be sure we are falling from the last m_currentPosition - // It prevents some flickering - // - btVector3 targetPosition = currentPosition - stepDrop; - - //if the no collisions mode is on, no need to go any further - if(!mCollision) return targetPosition; - - btTransform start; - start.setIdentity(); - start.setOrigin( currentPosition ); - - btTransform end; - end.setIdentity(); - end.setOrigin( targetPosition ); - - btKinematicClosestNotMeConvexResultCallback callback( internalGhostObject, getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); - callback.m_collisionFilterGroup = internalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; - callback.m_collisionFilterMask = internalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; - - // Retrieve the collision shape - // - btCollisionShape* collisionShape = internalGhostObject->getCollisionShape(); - btAssert( collisionShape->isConvex() ); - btConvexShape* convexShape = ( btConvexShape* )collisionShape; - - if( m_useGhostObjectSweepTest ) - externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); - - else - collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); - - if( callback.hasHit() ) - { - m_verticalVelocity = btScalar( 0.0 ); - m_verticalOffset = btScalar( 0.0 ); - m_wasJumping = false; - - // We dropped a fraction of the height -> hit floor - // - return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); - } - else - - // We dropped the full height - // - return targetPosition; -} - - - -void btKinematicCharacterController::setWalkDirection( const btVector3& walkDirection ) -{ - m_useWalkDirection = true; - m_walkDirection = walkDirection; -} - - -void btKinematicCharacterController::setVelocityForTimeInterval( const btVector3& velocity, btScalar timeInterval ) -{ - m_useWalkDirection = false; - m_walkDirection = velocity; - m_velocityTimeInterval = timeInterval; -} - - -void btKinematicCharacterController::reset() -{ -} - - -void btKinematicCharacterController::warp( const btVector3& origin ) -{ - btTransform transform; - transform.setIdentity(); - transform.setOrigin( -origin ); - - externalGhostObject->setWorldTransform( transform ); - internalGhostObject->setWorldTransform( transform ); -} - - -void btKinematicCharacterController::preStep( btCollisionWorld* collisionWorld ) -{ - BT_PROFILE( "preStep" ); - - for( int i = 0; i < 4 && recoverFromPenetration ( collisionWorld ); i++ ); -} - - -void btKinematicCharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt ) -{ - BT_PROFILE( "playerStep" ); - - if( !m_useWalkDirection && m_velocityTimeInterval <= btScalar( 0.0 ) ) - return; - - bool wasOnGround = onGround(); - - // Handle the gravity - // - m_verticalVelocity -= m_gravity * dt; - - if( m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed ) - m_verticalVelocity = m_jumpSpeed; - - if( m_verticalVelocity < 0.0 && btFabs( m_verticalVelocity ) > btFabs( m_fallSpeed ) ) - m_verticalVelocity = -btFabs( m_fallSpeed ); - - m_verticalOffset = m_verticalVelocity * dt; - - // This forced stepping up can cause problems when the character - // walks (jump in fact...) under too low ceilings. - // - btVector3 currentPosition = externalGhostObject->getWorldTransform().getOrigin(); - btScalar currentStepOffset; - - currentPosition = stepUp( collisionWorld, currentPosition, currentStepOffset ); - - // Move in the air and slide against the walls ignoring the stair steps. - // - if( m_useWalkDirection ) - currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, m_walkDirection ); - - else - { - btScalar dtMoving = ( dt < m_velocityTimeInterval ) ? dt : m_velocityTimeInterval; - m_velocityTimeInterval -= dt; - - // How far will we move while we are moving ? - // - btVector3 moveDirection = m_walkDirection * dtMoving; - - currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, moveDirection ); - } - - // Finally find the ground. - // - currentStepOffset = addFallOffset( wasOnGround, currentStepOffset, dt ); - - currentPosition = stepDown( collisionWorld, currentPosition, currentStepOffset ); - - // Apply the new position to the collision objects. - // - btTransform tranform; - tranform = externalGhostObject->getWorldTransform(); - tranform.setOrigin( currentPosition ); - - externalGhostObject->setWorldTransform( tranform ); - internalGhostObject->setWorldTransform( tranform ); -} - - -void btKinematicCharacterController::setFallSpeed( btScalar fallSpeed ) -{ - m_fallSpeed = fallSpeed; -} - - -void btKinematicCharacterController::setJumpSpeed( btScalar jumpSpeed ) -{ - m_jumpSpeed = jumpSpeed; -} - - -void btKinematicCharacterController::setMaxJumpHeight( btScalar maxJumpHeight ) -{ - m_maxJumpHeight = maxJumpHeight; -} - - -bool btKinematicCharacterController::canJump() const -{ - return onGround(); -} - - -void btKinematicCharacterController::jump() -{ - if( !canJump() ) - return; - - m_verticalVelocity = m_jumpSpeed; - m_wasJumping = true; -} - - -void btKinematicCharacterController::setGravity( btScalar gravity ) -{ - m_gravity = gravity; -} - - -btScalar btKinematicCharacterController::getGravity() const -{ - return m_gravity; -} - - -void btKinematicCharacterController::setMaxSlope( btScalar slopeRadians ) -{ - m_maxSlopeRadians = slopeRadians; - m_maxSlopeCosine = btCos( slopeRadians ); -} - - -btScalar btKinematicCharacterController::getMaxSlope() const -{ - return m_maxSlopeRadians; -} - - -bool btKinematicCharacterController::onGround() const -{ - return btFabs( m_verticalVelocity ) < btScalar( SIMD_EPSILON ) && - btFabs( m_verticalOffset ) < btScalar( SIMD_EPSILON ); -} - - -btVector3* btKinematicCharacterController::getUpAxisDirections() -{ - static btVector3 sUpAxisDirection[] = - { - btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 0.0 ) ), - btVector3( btScalar( 0.0 ), btScalar( 1.0 ), btScalar( 0.0 ) ), - btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 1.0 ) ) - }; - - return sUpAxisDirection; -} - - -void btKinematicCharacterController::debugDraw( btIDebugDraw* debugDrawer ) -{ -} diff --git a/libs/openengine/bullet/btKinematicCharacterController.h b/libs/openengine/bullet/btKinematicCharacterController.h deleted file mode 100644 index d24cd9722..000000000 --- a/libs/openengine/bullet/btKinematicCharacterController.h +++ /dev/null @@ -1,168 +0,0 @@ -/* -Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com - -This software is provided 'as-is', without any express or implied warranty. -In no event will the authors be held liable for any damages arising from the use of this software. -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it freely, -subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -#ifndef KINEMATIC_CHARACTER_CONTROLLER_H -#define KINEMATIC_CHARACTER_CONTROLLER_H - -#include "LinearMath/btVector3.h" -#include "LinearMath/btQuickprof.h" - -#include "BulletDynamics/Character/btCharacterControllerInterface.h" - -#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" - - -class btCollisionShape; -class btRigidBody; -class btCollisionWorld; -class btCollisionDispatcher; -class btPairCachingGhostObject; - -///btKinematicCharacterController is an object that supports a sliding motion in a world. -///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations. -///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user. -class btKinematicCharacterController : public btCharacterControllerInterface -{ -public: - enum UpAxis - { - X_AXIS = 0, - Y_AXIS = 1, - Z_AXIS = 2 - }; - -private: - btPairCachingGhostObject* externalGhostObject; // use this for querying collisions for sliding and move - btPairCachingGhostObject* internalGhostObject; // and this for recoreving from penetrations - - btScalar m_verticalVelocity; - btScalar m_verticalOffset; - btScalar m_fallSpeed; - btScalar m_jumpSpeed; - btScalar m_maxJumpHeight; - btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) - btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) - btScalar m_gravity; - btScalar m_recoveringFactor; - - btScalar m_stepHeight; - - ///this is the desired walk direction, set by the user - btVector3 m_walkDirection; - - ///keep track of the contact manifolds - btManifoldArray m_manifoldArray; - - ///Gravity attributes - bool m_wasJumping; - - bool m_useGhostObjectSweepTest; - bool m_useWalkDirection; - btScalar m_velocityTimeInterval; - - UpAxis m_upAxis; - - static btVector3* getUpAxisDirections(); - - bool recoverFromPenetration ( btCollisionWorld* collisionWorld ); - - btVector3 stepUp( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar& currentStepOffset ); - btVector3 stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ); - btScalar addFallOffset( bool wasJumping, btScalar currentStepOffset, btScalar dt ); - btVector3 stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ); - -public: - /// externalGhostObject is used for querying the collisions for sliding along the wall, - /// and internalGhostObject is used for querying the collisions for recovering from large penetrations. - /// These parameters can point on the same object. - /// Using a smaller internalGhostObject can help for removing some flickering but create some - /// stopping artefacts when sliding along stairs or small walls. - /// Don't forget to scale gravity and fallSpeed if you scale the world. - btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject, - btPairCachingGhostObject* internalGhostObject, - btScalar stepHeight, - btScalar constantScale = btScalar( 1.0 ), - btScalar gravity = btScalar( 9.8 ), - btScalar fallVelocity = btScalar( 55.0 ), - btScalar jumpVelocity = btScalar( 9.8 ), - btScalar recoveringFactor = btScalar( 0.2 ) ); - - ~btKinematicCharacterController (); - - void setVerticalVelocity(float z); - - ///btActionInterface interface - virtual void updateAction( btCollisionWorld* collisionWorld, btScalar deltaTime ) - { - preStep( collisionWorld ); - playerStep( collisionWorld, deltaTime ); - } - - ///btActionInterface interface - void debugDraw( btIDebugDraw* debugDrawer ); - - void setUpAxis( UpAxis axis ) - { - m_upAxis = axis; - } - - /// This should probably be called setPositionIncrementPerSimulatorStep. - /// This is neither a direction nor a velocity, but the amount to - /// increment the position each simulation iteration, regardless - /// of dt. - /// This call will reset any velocity set by setVelocityForTimeInterval(). - virtual void setWalkDirection(const btVector3& walkDirection); - - /// Caller provides a velocity with which the character should move for - /// the given time period. After the time period, velocity is reset - /// to zero. - /// This call will reset any walk direction set by setWalkDirection(). - /// Negative time intervals will result in no motion. - virtual void setVelocityForTimeInterval(const btVector3& velocity, - btScalar timeInterval); - - void reset(); - void warp( const btVector3& origin ); - - void preStep( btCollisionWorld* collisionWorld ); - void playerStep( btCollisionWorld* collisionWorld, btScalar dt ); - - void setFallSpeed( btScalar fallSpeed ); - void setJumpSpeed( btScalar jumpSpeed ); - void setMaxJumpHeight( btScalar maxJumpHeight ); - bool canJump() const; - - void jump(); - - void setGravity( btScalar gravity ); - btScalar getGravity() const; - - /// The max slope determines the maximum angle that the controller can walk up. - /// The slope angle is measured in radians. - void setMaxSlope( btScalar slopeRadians ); - btScalar getMaxSlope() const; - - void setUseGhostSweepTest( bool useGhostObjectSweepTest ) - { - m_useGhostObjectSweepTest = useGhostObjectSweepTest; - } - - bool onGround() const; - - //if set to false, there will be no collision. - bool mCollision; -}; - -#endif // KINEMATIC_CHARACTER_CONTROLLER_H diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index e33edda18..481b99bad 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -3,9 +3,7 @@ #include #include #include -#include "CMotionState.h" #include "OgreRoot.h" -#include "btKinematicCharacterController.h" #include "BtOgrePG.h" #include "BtOgreGP.h" #include "BtOgreExtras.h" @@ -318,9 +316,7 @@ namespace Physic btVector3 scl(triSize, triSize, 1); hfShape->setLocalScaling(scl); - CMotionState* newMotionState = new CMotionState(this,name); - - btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,hfShape); + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,0,hfShape); RigidBody* body = new RigidBody(CI,name); body->getWorldTransform().setOrigin(btVector3( (x+0.5)*triSize*(sqrtVerts-1), (y+0.5)*triSize*(sqrtVerts-1), (maxh+minh)/2.f)); @@ -401,12 +397,9 @@ namespace Physic else shape->mRaycastingShape->setLocalScaling( btVector3(scale,scale,scale)); - //create the motionState - CMotionState* newMotionState = new CMotionState(this,name); - //create the real body btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,newMotionState, raycasting ? shape->mRaycastingShape : shape->mCollisionShape); + (0,0, raycasting ? shape->mRaycastingShape : shape->mCollisionShape); RigidBody* body = new RigidBody(CI,name); body->mPlaceable = placeable; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index f28f95ccb..6cd7244b8 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -38,7 +38,6 @@ namespace MWWorld namespace OEngine { namespace Physic { - class CMotionState; struct PhysicEvent; class PhysicEngine; class RigidBody; @@ -157,17 +156,7 @@ namespace Physic private: void disableCollisionBody(); void enableCollisionBody(); -public: -//HACK: in Visual Studio 2010 and presumably above, this structures alignment -// must be 16, but the built in operator new & delete don't properly -// perform this alignment. -#if _MSC_VER >= 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; OEngine::Physic::RigidBody* mRaycastingBody; @@ -329,12 +318,6 @@ public: const btVector3 &origin, btCollisionObject *object); - //event list of non player object - std::list NPEventList; - - //event list affecting the player - std::list PEventList; - //Bullet Stuff btOverlappingPairCache* pairCache; btBroadphaseInterface* broadphase; diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index 9040dfb90..26a3fdab8 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -150,10 +150,10 @@ namespace GUI MyGUI::IntSize size = button->getTextSize(); button->setSize(size.width + 24, button->getSize().height); } + MyGUI::Widget* mMainWidget; protected: - MyGUI::Widget* mMainWidget; std::string mPrefix; std::string mLayoutName; MyGUI::VectorWidgetPtr mListWindowRoot; diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 912781240..c86697497 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -1,138 +1,43 @@ #include "renderer.hpp" #include "fader.hpp" -#include "particles.hpp" #include -#include "OgreRoot.h" -#include "OgreRenderWindow.h" -#include "OgreLogManager.h" -#include "OgreLog.h" -#include "OgreTextureManager.h" -#include "OgreTexture.h" -#include "OgreHardwarePixelBuffer.h" -#include -#include "OgreParticleAffectorFactory.h" - -#include - -#include +#include +#include +#include +#include +#include +#include #include +#include + #include -#include #include using namespace Ogre; using namespace OEngine::Render; -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - -CustomRoot::CustomRoot(const Ogre::String& pluginFileName, - const Ogre::String& configFileName, - const Ogre::String& logFileName) -: Ogre::Root(pluginFileName, configFileName, logFileName) -{} - -bool CustomRoot::isQueuedEnd() const -{ - return mQueuedEnd; -} - -#endif - void OgreRenderer::cleanup() { delete mFader; mFader = NULL; - delete mRoot; - mRoot = NULL; + if (mWindow) + Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + mWindow = NULL; + + delete mOgreInit; + mOgreInit = NULL; // If we don't do this, the desktop resolution is not restored on exit SDL_SetWindowFullscreen(mSDLWindow, 0); SDL_DestroyWindow(mSDLWindow); mSDLWindow = NULL; - - unloadPlugins(); -} - -void OgreRenderer::start() -{ -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // we need this custom main loop because otherwise Ogre's Carbon message pump will - // steal input events even from our Cocoa window - // There's no way to disable Ogre's message pump other that comment pump code in Ogre's source - do { - if (!mRoot->renderOneFrame()) { - break; - } - - } while (!mRoot->isQueuedEnd()); -#else - mRoot->startRendering(); -#endif -} - -void OgreRenderer::loadPlugins() -{ - #ifdef ENABLE_PLUGIN_GL - mGLPlugin = new Ogre::GLPlugin(); - mRoot->installPlugin(mGLPlugin); - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - mD3D9Plugin = new Ogre::D3D9Plugin(); - mRoot->installPlugin(mD3D9Plugin); - #endif - #ifdef ENABLE_PLUGIN_CgProgramManager - mCgPlugin = new Ogre::CgPlugin(); - mRoot->installPlugin(mCgPlugin); - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - mOctreePlugin = new Ogre::OctreePlugin(); - mRoot->installPlugin(mOctreePlugin); - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - mParticleFXPlugin = new Ogre::ParticleFXPlugin(); - mRoot->installPlugin(mParticleFXPlugin); - #endif -} - -void OgreRenderer::unloadPlugins() -{ - std::vector::iterator ei; - for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei) - OGRE_DELETE (*ei); - mEmitterFactories.clear(); - - std::vector::iterator ai; - for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai) - OGRE_DELETE (*ai); - mAffectorFactories.clear(); - - #ifdef ENABLE_PLUGIN_GL - delete mGLPlugin; - mGLPlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - delete mD3D9Plugin; - mD3D9Plugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_CgProgramManager - delete mCgPlugin; - mCgPlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - delete mOctreePlugin; - mOctreePlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - delete mParticleFXPlugin; - mParticleFXPlugin = NULL; - #endif } void OgreRenderer::update(float dt) @@ -152,75 +57,11 @@ float OgreRenderer::getFPS() void OgreRenderer::configure(const std::string &logPath, const std::string& renderSystem, - const std::string& rttMode, - bool _logging) + const std::string& rttMode + ) { - // Set up logging first - new LogManager; - Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); - logging = _logging; - - if(logging) - // Full log detail - log->setLogDetail(LL_BOREME); - else - // Disable logging - log->setDebugOutputEnabled(false); - -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - mRoot = new CustomRoot("", "", ""); -#else - mRoot = new Root("", "", ""); -#endif - - #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) - loadPlugins(); - #endif - - std::string pluginDir; - const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); - if (pluginEnv) - pluginDir = pluginEnv; - else - { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - pluginDir = ".\\"; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - pluginDir = OGRE_PLUGIN_DIR; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR_REL; -#endif - } - - boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); - - pluginDir = absPluginPath.string(); - - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); - Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); - Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot); - - - Ogre::ParticleEmitterFactory *emitter; - emitter = OGRE_NEW NifEmitterFactory(); - Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter); - mEmitterFactories.push_back(emitter); - - - Ogre::ParticleAffectorFactory *affector; - affector = OGRE_NEW GrowFadeAffectorFactory(); - Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); - mAffectorFactories.push_back(affector); - - affector = OGRE_NEW GravityAffectorFactory(); - Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); - mAffectorFactories.push_back(affector); - + mOgreInit = new OgreInit::OgreInit(); + mRoot = mOgreInit->init(logPath + "/ogre.log"); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 89edc567d..767e7cf99 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -7,38 +7,15 @@ #include -// Static plugin headers -#ifdef ENABLE_PLUGIN_CgProgramManager -# include "OgreCgPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_OctreeSceneManager -# include "OgreOctreePlugin.h" -#endif -#ifdef ENABLE_PLUGIN_ParticleFX -# include "OgreParticleFXPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_GL -# include "OgreGLPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 -# include "OgreD3D9Plugin.h" -#endif - -#include "OgreTexture.h" -#include +#include -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include -#endif struct SDL_Window; struct SDL_Surface; namespace Ogre { -#if OGRE_PLATFORM != OGRE_PLATFORM_APPLE class Root; -#endif class RenderWindow; class SceneManager; class Camera; @@ -47,6 +24,11 @@ namespace Ogre class ParticleAffectorFactory; } +namespace OgreInit +{ + class OgreInit; +} + namespace OEngine { namespace Render @@ -61,17 +43,6 @@ namespace OEngine std::string icon; }; -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - class CustomRoot : public Ogre::Root { - public: - bool isQueuedEnd() const; - - CustomRoot(const Ogre::String& pluginFileName = "plugins.cfg", - const Ogre::String& configFileName = "ogre.cfg", - const Ogre::String& logFileName = "Ogre.log"); - }; -#endif - class Fader; class WindowSizeListener @@ -82,35 +53,16 @@ namespace OEngine class OgreRenderer { -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - CustomRoot *mRoot; -#else Ogre::Root *mRoot; -#endif Ogre::RenderWindow *mWindow; SDL_Window *mSDLWindow; Ogre::SceneManager *mScene; Ogre::Camera *mCamera; Ogre::Viewport *mView; - #ifdef ENABLE_PLUGIN_CgProgramManager - Ogre::CgPlugin* mCgPlugin; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - Ogre::OctreePlugin* mOctreePlugin; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - Ogre::ParticleFXPlugin* mParticleFXPlugin; - #endif - #ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; - #endif + + OgreInit::OgreInit* mOgreInit; + Fader* mFader; - std::vector mEmitterFactories; - std::vector mAffectorFactories; - bool logging; WindowSizeListener* mWindowListener; @@ -122,24 +74,9 @@ namespace OEngine , mScene(NULL) , mCamera(NULL) , mView(NULL) - , mWindowListener(NULL) - #ifdef ENABLE_PLUGIN_CgProgramManager - , mCgPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - , mOctreePlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - , mParticleFXPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_GL - , mGLPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - , mD3D9Plugin(NULL) - #endif + , mOgreInit(NULL) , mFader(NULL) - , logging(false) + , mWindowListener(NULL) { } @@ -150,8 +87,7 @@ namespace OEngine void configure( const std::string &logPath, // Path to directory where to store log files const std::string &renderSystem, - const std::string &rttMode, - bool _logging); // Enable or disable logging + const std::string &rttMode); // Enable or disable logging /// Create a window with the given title void createWindow(const std::string &title, const WindowSettings& settings); @@ -167,13 +103,6 @@ namespace OEngine /// Kill the renderer. void cleanup(); - /// Start the main rendering loop - void start(); - - void loadPlugins(); - - void unloadPlugins(); - void update(float dt); /// Write a screenshot to file diff --git a/manual/opencs/.gitignore b/manual/opencs/.gitignore new file mode 100644 index 000000000..ce5852a37 --- /dev/null +++ b/manual/opencs/.gitignore @@ -0,0 +1,6 @@ +*.backup +*.aux +*.log +*.toc +*.pdf +*.out diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex new file mode 100644 index 000000000..2c1377ec1 --- /dev/null +++ b/manual/opencs/creating_file.tex @@ -0,0 +1,25 @@ +\section{OpenCS starting dialog} +\subsection{Introduction} +The great day has come. Today, you shall open \OCS{} application. And when you do this, you shall see our starting dialog window that holds three buttons +that can bring both pain and happiness. So just do this, please. + +\subsection{Basics} +Back to the manual? Great! As you can see, the starting window holds just three buttons. Since you are already familiar with our files system, they come +to you with no surprise.\\ + +First, there is a \textbf{Create A New Game} button. Clearly, you should press it when you want to create a game file. Than, what \textbf{Create A New Addon} button do? +Yes! You are right! This button will create any addon content file (and new project file associated with it)! Wonderful! And what the last remaining button do? \textbf{Edit A Content File}? Well, it comes with no surprise that this should be used when you need to alter existing content file, either a game or addon.\\ + +\paragraph{Selecting Files For New Addon} +As We wrote earlier, both \OMW{} and \OCS{} are operating with dependency idea in mind. As You remember you should only depend on files you are actually using. But how?\\ +It is simple. When you click either \textbf{Create new Addon} you will be asked to choose those with a new dialog window. The window is using vertical layout, first you should consider the the top element, the one that allows you to select a game file with drop down menu. Since we are operating on the assumption that there is only one game file loaded at the time, you can depend only on one game file. Next, choose addons that you want to use in your addon with checkboxes.\\ + +The last thing to do is to name your your addon and click create. + +\paragraph{Selecting File for Editing} +Clicking \textbf{Edit A Content File} will show somewhat similar window. Here you should select your Game file with drop down menu. If you want to edit this game file, simply click \textbf{OK} button. If you want to alter addon depending on that file, mark it with checkbox and than click \textbf{Ok} button. + +\subsection{Advanced} +If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Those are general OpenCS settings. We will cover this is separate section.\\ + +And that would be it. There is no point spending more time here. We should go forward now. \ No newline at end of file diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex new file mode 100644 index 000000000..b483dac5a --- /dev/null +++ b/manual/opencs/files_and_directories.tex @@ -0,0 +1,120 @@ +\section{Files and Directories} +\subsection{Introduction} +This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts, +and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on \OCS. + +\subsection{Used terms} %TODO + +\subsection{Basics} + +\paragraph{Directories} +OpenMW and \OCS{} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration +files and few different folders. The location of the user directory is hard coded for each supported operating system. + +%TODO list paths. +In addition to this single hard coded directory, both \OMW{} and \OCS{} need a~place to seek for actual data files of the game: +textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support +multiple such paths (we call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory +where original \MW{} is either installed or unpacked. You are free to specify as many data paths as you would like, +however, there is one special data path that, as described later, is used to store newly created content files. + +\paragraph{Content files} +\BS{} \MW{} engine is using two types of files: ESM (master) and ESP (plugin). The distinction between those +is not clear, and often confusing. You would expect the ESM (master) file is used to specify one master, that is modified by the ESPs plugins, +and indeed: this is the basic idea. However, original expansions also were made as ESM files, even though they essentially could be +described as a really large plugins, and therefore rather use ESP files. There were technical reasons behind this decision -- somewhat valid +in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. \OMW{} achieves +this with our own content file types. + +We support both ESM and ESP files, but in order to make use of new features of OpenMW one should consider using new file types designed +with our engine in mind: game files and addon files together called ``content files``. + +\subparagraph{OpenMW content files} +Game and Addon files are concept somewhat similar to the old ESM/ESP, only in the way it should be from the very beginning. Nothing easier +to describe. If you want to make new game using \OMW{} as engine (so called ``total conversion'') you should create a game file. +If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should +consider is if your project is about changing other game, or creating a new one. Simple as that. + +Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files. + +%TODO describe what content files contains. and what not. +\subparagraph{\MW{} content files} +Using our content files is recommended solution for projects that are intended to used with \OMW{} engine. However some players +wish to use original \MW{} engine, even with it large flaws and lacking features\footnote{If this is actually wrong, we are very +successful project. Yay!}. Also, since 2002 thousands of ESP/ESM files were created, some with really outstanding content. +Because of this \OCS{} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead +using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very +last section of this manual. %not finished TODO add the said section. Most likely when more features are present. + +The actual creation of new files is described in the next chapter. Here we are gonna focus only on details that you need to know +in order to create your first \OCS{} file while full understanding your needs. For now let's jut remember that content files +are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier). + +\subparagraph{Dependencies} +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can not work otherwise. +Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That is right: +we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island +for a game) or other addon files (house on the said island). It is a good idea to be dependent only on files that are really changed +in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. Again, please remember that +this section of the manual does not cover creating the content files -- it is only theoretical introduction to the subject. For now just +keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file. + +Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original +and dirty {ESP/ESM} system) at the time and therefore no game file can depend on other game file, and since game file makes the base +for addon files -- it can not depend on addon files. + +%\subparagraph{Loading order} %TODO +\paragraph{Project files} +Project files act as containers for data not used by the \OMW{} game engine itself, but still useful for OpenCS. The shining example +of this data category are without doubt record filters (described in the later section of the manual you are reading currently). +As a mod author you probably do not need and/or want to distribute project files at all, they are meant to be used only by you. + +As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work +on new content file and project file was not found, it will be created. +Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file +with appended extensions. For instance swords.omwaddon file is associated with swords.omwaddon.project file. + +%TODO where are they stored. +Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly +created project files, and a place where \OCS{} looks for already existing files. + +\paragraph{Resources files} +%textures, sounds, whatever +Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: +models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is ESP, ESM or new \OMW{} +file type do not contain any of those, it is clear that they have to be deliver with a different file. It is also clear that this, +let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than +a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. +Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going +to do just that. Later, you will learn how to make use of those files in your content. + +\subparagraph{Audio} +OpenMW is using {FFmpeg} for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. +Below is only small portion of supported file types. + +\begin{description} + \item mp3 ({MPEG}-1 {Part 3 Layer 3}) popular audio file format and \textit{de facto} standard for storing audio. Used by the \MW{} game. + \item ogg open source, multimedia container file using high quality vorbis audio codec. Recommended. +\end{description} + +\subparagraph{Video} +As in the case of audio files, we are using {FFmepg} to decode video files. The list of supported files is long, we will cover +only the most significant. + +\begin{description} + \item bik videos used by original \MW{} game. + \item mp4 multimedia container which use more advanced codecs ({MPEG-4 Parts 2,3,10}) with a better audio and video compression rate, + but also requiring more {CPU} intensive decoding -- this makes it probably less suited for storing sounds in computer games, but good for videos. + \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, + but since game logic is not running during cut scenes we can recommend it for use with \OMW. + \item ogv alternative, open source container using theora codec for video and vorbis for audio. +\end{description} + +\subparagraph{Textures and images} +Original \MW{} game uses {DDS} and {TGA} files for all kind of two dimensional images and textures alike. In addition, engine supported BMP +files for some reason ({BMP} is a terrible format for a video game). We also support extended set of image files -- including {JPEG} and {PNG}. +JPEG and PNG files can be useful in some cases, for instance JPEG file is a valid option for skybox texture and PNG can useful for masks. +However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with {DirectX} rendering backend. You probabbly still want +to use {DDS} files for textures. + +%\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex new file mode 100644 index 000000000..36d97e0f5 --- /dev/null +++ b/manual/opencs/filters.tex @@ -0,0 +1,205 @@ +\section{Record filters} +\subsection{Introduction} +Filters are the key element of \OCS{} use cases by allowing rapid and easy access to the searched records presented in all tables. +Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in +the this section of the manual are perfectly clear to you. + +Do not be afraid though, filters are fairly intuitive and easy to use. + +\subsubsection{Used Terms} + +\begin{description} + \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according + to the some criteria. In case of \OCS: records are being filtered according to the criteria of user choice. Criteria are written + down in language with simple syntax. + \item[Criteria] describes condition under with any any record is being select by the filter. + \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: + written with correct syntax. + \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates + either to the true or false for every column record at the time. + \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions + together in order to create filter that will check for criteria placed in two (again: or more) columns (logical \textit{or}, \textit{and}). + \item[unary] is any expression that expects one other expression. The example is \textit{not} expression. In fact \textit{not} is the only useful + unary expression in \OCS{} record filters. + \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. +\end{description} + +\subsubsection{Basics} +In fact you do not need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity +with \OCS{} is inside basics section. + +\subsubsection{Interface} +Above each table there is a field that is used to enter filter: either predefined by the \OMW{} developers or made by you, the user. +You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application +menu view, and click filters. You should see set of default filters, made by the \OMW{} team in the table with the following columns: filter, +description and modified. + +\begin{description} + \item[ID] contains the name of the filter. + \item[Modified] just like in all other tables you have seen so far modified indicates if a filter was added, modified or removed. + \item[Filter] column containing expression of the filter. + \item[Description] contains the short description of the filter function. Do not expect any surprises there. +\end{description} + +So let's learn how to actually use those to speed up your work. +\subsubsection{Using predefined filters} +Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables +table and type in the filters field the following: \mono{project::weapons}. As soon as you complete the text, table will magicly alter +and will show only the weapons. As you could noticed \mono{project::weapons} is nothing else than a~ID of one of the predefined filters. That is it: +in order to use the filter inside the table you simply type it is name inside the filter field. + +To make life easier filter IDs follow simple convention. + +\begin{itemize} + \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance \mono{project::weapons} filter + contains the word weapons (did you noticed?). Plural form is always used. + \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance \mono{project::weaponssilver} will + filter only silver weapons (new mechanic introduced by the \BM{}, silver weapons deal double damage against werewolfs) and + \mono{project::weaponsmagical} will filter only magical weapons (able to hurt ghosts and other supernatural creatures). + \item There are few exceptions from the above rule. For instance there is a \mono{project::added}, \mono{project::removed}, + \mono{project::modyfied}, \mono{project::base}. + You would probably except something more like \mono{project::statusadded} but in this case typing this few extra characters would only + help to break your keyboard faster. +\end{itemize} + +We strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. + +\subsection{Advanced} +Back to the manual? Great. + +If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. +Finally, you will have to write this with correct syntax. As a result table will show only desired rows. + +Advance subsection covers everything that you need to know in order to create any filter you may want to %TODO the filter part is actually wrong +\subsubsection{Namespaces} +Did you noticed that every default filter has \mono{project::} prefix? It is a \textit{namespace}, a~term borrowed from the \CPP{} language. +In case of \OCS{} namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. +But what does it mean in case of filters? Well, short explanation is actually simple. +\begin{description} + \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart \OCS{} and filter + is still there. + \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit \OCS{} (close session) + the filter will be gone. Forever! Until then it can be found inside the filters table. +\end{description} +In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session) +anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that +you do not need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with +exclamation mark: ``!''. + +Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start +with nullary expressions that will allow you to create a basic filter. + +\subsubsection{Nullary expressions} +All nullary expressions are used in similar manner. First off: you have to write it is name (for instance: \mono{string}) and secondly: +condition that will be checked inside brackets (for instance \mono{string(something, something)}). If conditions of your expression will be meet +by a record (technical speaking: expression will evaluate to true) the record will show up in the table. + +It is clear that you need to know what are you checking, that is: what column of the table contains information that you are interested +in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column +you want to see, while the second one sets desired value inside of the cell. To separate column argument from the value argument use comma. + +\paragraph{String -- string(``column'', ``value'')} +String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} +just a word for anything composed of characters. In case of \OCS{} this is in fact true for every value inside the column that is not composed +of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression\footnote{There is no +Boolean (``true'' or ``false'') value in the \OCS. You should use string for those.}. String evaluates to true, +when record contains in the specified column exactly the same value as specified. + +Since majority of the columns contain string values, string is among the most often used expressions. Examples: +\begin{itemize} + \item \mono{string(``Record Type'', ``Weapon'')} -- will evaluate to true for all records containing \mono{Weapon} in the \mono{Record Type} column cell. + This group contains every weapon (including arrows and bolts) found in the game. + \item \mono{string(``Portable'', ``true'')} -- will evaluate to true for all records containing word true inside \mono{Portable} column cell. + This group contains every portable light sources (lanterns, torches etc.). +\end{itemize} +This is probably enough to create around 90 string filters you will eventually need. However, this expression is even more powerful +-- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched +by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: +\mono{string("armor type", ".* gauntlet"))} because \mono{.*} in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. +There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? + +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, we are under impression +that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned +\mono{.*} is needed and therefore the following description of regexps can be skipped by vast majority of readers. + +Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: +when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular +expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, +you will clearly need way to determinate what letters you want to match (word is composed by letters). + +Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. +You surely should know about \mono{\textasciicircum} anchor and \mono{\textdollar}. Putting \mono{\textasciicircum} will tell to \OCS{} +to look on the beginning of string, while \mono{\textdollar} is used to mark the end of it. For instance, pattern +\mono{\textasciicircum{}Pink.* elephant.\textdollar} will match any sentence beginning with the word \mono{Pink} and ending with +\mono{ elephant.}. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because \mono{.*} is used. + +You have already seen the power of the simple \mono{.*}. But what if you want to chose between only two (or more) letters? Well, this is when +\mono{[|]} comes in handy. If you write something like: \mono{\textasciicircum[a|k].*} you are simply telling \OCS{} to filter anything that +starts with either \mono{a} or \mono{k}. Using \mono{\textasciicircum[a|k|l].*} will work in the same manner, but it will also cover +strings starting with \mono{l}. + +What if you want to match more than just one latter? Just use \mono{(|)}. it is pretty similar to the above one letter as you see, but it is +used to fit more than just one character. For instance: \mono{\textasciicircum(Pink|Green).* (elephant|crocodile).\textdollar} will be +true for all sentences starting with \mono{Pink} or \mono{Green} and ending with either \mono{elephant.} or \mono{crocodile.}. + +Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on +Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use \OCS{} effectively to be sure. + +\paragraph{Value -- value(``value'', (``open'', ``close''))} +While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like +``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria +inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. +As you would imagine the range can be specified as including a border value, or excluding. We are using two types of brackets for this: +\begin{itemize} + \item To include value use [] brackets. For value equal 5, expression \mono{value(something, [5, 10])} will evaluate to true. + \item To exclude value use () brackets. For value equal 5, expression \mono{value(something, (5, 10))} will evaluate to false. + \item Mixing brackets is completely legal. For value equal 10, expression \mono{value(something, [5, 10)} will evaluate to true. + The same expression will evaluate to false for value equal 10. +\end{itemize} + +\paragraph{``true'' and ``false''} +Nullary \textit{true} and \textit{false} do not accept any arguments, and always evaluates to true (in case of \textit{true}) +and false (in case of \textit{false}) no matter what. The main usage of this expressions is the give users ability to quickly +disable some part of the filter that makes heavy use of the logical expressions. + +\subsubsection{Logical expressions} +This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the \OCS{} is logical +\textit{not}, while the remaining binary expressions are: \textit{or}, \textit{and}. This clearly makes them (from the user point of view) +belonging to the same group of logical expressions. + +\paragraph{not -- not expression()} +Sometimes you may be in need of reversing the output of the expression. This is where \textit{not} comes in handy. Adding \textit{not} before +expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets +are not needed: \textit{not} will revert only the first expression following it. + +To show this on know example, let's consider the \mono{string("armor type", ".* gauntlet"))} filter. As we mentioned earlier this will return true +for every gauntlet found in game. In order to show everything, but gauntlets we simply do \mono{not string("armor type", ".* gauntlet"))}. +This is probably not the most useful filter on earth, but this is not a surprise: real value of \textit{not} expression shines when combined with +\textit{or}, \textit{and} filters. + +\paragraph{or -- or(expression1(), expression2())} +\textit{Or} is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma. + +\textit{Or} expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following +\mono{or(string(``record type'', npc), string(``record type'', creature))} and will show both npcs and creatures. + +\paragraph{and -- and(expression1(), expression2())} +\textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ``or'' you can use two or more arguments, +separated by a comma. +As we mentioned earlier in the \textit{not} filter, combining \textit{not} with \textit{and} can be very useful. For instance to show all armor types, +excluding gauntlets you can write the following: \mono{and (not string(``armor type'', ``.* gauntlet''), string(``Record Type'', ``Armor''))}. + +\subsubsection{Creating and saving filter} +In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. +A horizontal widget group at the bottom of the table should show up. From there you should select a namespace responsible for scope of +the filter (described earlier) and desired ID of the filter. After pressing OK button new entry will show up in the filters table. This filter +does nothing at the moment, since it still lacks expressions. In order to add your formula simply double click the filter cell of the new entry +and write it down there. +Done! You are free to use your filter. + +\subsubsection{Replacing the default filters set} +OpenCS allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, +add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (do not forget to remove .omwaddon.project extension) +and place it inside your configuration directory. + +The file acts as template for all new project files from now. If you wish to go back to the old default set, simply rename or remove the custom file. diff --git a/manual/opencs/img/water.png b/manual/opencs/img/water.png new file mode 100644 index 000000000..885c2d9a7 Binary files /dev/null and b/manual/opencs/img/water.png differ diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex new file mode 100644 index 000000000..1d8aebe40 --- /dev/null +++ b/manual/opencs/main.tex @@ -0,0 +1,34 @@ +\documentclass[american]{article} +\usepackage[T1]{fontenc} +\usepackage{babel} +\usepackage{txfonts} % Public Times New Roman text & math font +\usepackage[pdftex]{graphicx} +\usepackage[final,colorlinks,pdftex,pdfpagelabels=true]{hyperref} +\author{OpenMW Team} + +\def\pdfBorderAttrs{/Border [0 0 0] } % No border arround Links + +\def\CPP{{C\kern-.05em\raise.23ex\hbox{+\kern-.05em+}}} +\hypersetup{% + pdfauthor={Copyright \textcopyright{} OpenMW Team }, + pdftitle={OpenCS user manual} +} +\def\mono{\texttt} +\def\MW{\textit{Morrowind\texttrademark{}}} +\def\TB{\textit{Tribunal}} +\def\BM{\textit{Bloodmon}} +\def\BS{Bethesda Softworks} +\def\OMW{\hbox{OpenMW}} +\def\OCS{\hbox{OpenCS}} + +\begin{document} + +\title{OpenCS User Manual} +\maketitle +\tableofcontents{} +\input{files_and_directories} +\input{creating_file} +\input{windows} +\input{tables} +\input{filters} +\end{document} diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex new file mode 100644 index 000000000..e7cc06735 --- /dev/null +++ b/manual/opencs/tables.tex @@ -0,0 +1,103 @@ +\section{Tables} + +\subsection{Introduction} +If you have launched \OCS{} already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. +You'd be spot on: \OCS{} is built around using tables. This does not mean it works just like Microsoft Excel or Libre Office Calc, though. +Due to the vast amounts of information involved with \MW, tables just made the most sense. You have to be able to spot information quickly +and be able to change them on the fly. +Let's browse through the various screens and see what all these tables show. + +\subsection{Used Terms} + +\subsubsection{Glossary} + +\begin{description} + \item[Record:] An entry in \OCS{} representing an item, location, sound, NPC or anything else. + + \item[Reference, Referenceable:] When an item is placed in the world, it does not create a new record each time. For example, the game world might + contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record. + In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders + to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will + only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually. +\end{description} + +\subsubsection{Recurring Terms} +Some columns are recurring throughout \OCS. They show up in (nearly) every table in \OCS. + +\begin{description} +\item[ID] Each item, location, sound, etc. gets the same unique identifier in both \OCS{} and \MW. This is usually a very self-explanatory name. +For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, +you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind +and \OCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. +\item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. +\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with +optionally the Bloodmoon and Tribunal expansions. +\item[Added] means that this record was not in the base game and has been added by a~modder. +\item[Modified] means that the record is part of the base game, but has been changed in some way. +\item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences +in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it does not mean the bedroll in the basement +of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced +by something that still exists otherwise you will get crashes in the worst case scenario. +\end{description} + +\subsection{World Screens} +The contents of the game world can be changed by choosing one of the options in the appropriate menu at the top of the screen. + +\subsubsection{Regions} +This describes the general areas of Vvardenfell. Each of these areas has different rules about things such as encounters and weather. + +\begin{description} + \item[Name:] This is how the game will show your location in-game. + \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in + World > Region Map. If you do not have an application with a colour picker, you can use your favourite search engine to find a colour picker online. + \item[Sleep Encounter:] These are the rules for what kind of enemies you might encounter when you sleep outside in the wild. +\end{description} + +\subsubsection{Cells} +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot going on simultaneously. But if you are in Balmora, +why would the computer need to keep track the exact locations of NPCs walking through the corridors in a Vivec canton? All that work would +be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell, +the game will load everything that is going on in that cell so you can interact with it. + +In the original \MW{} this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; +you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in \OCS{} provides you with a list of cells +in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world). + +\begin{description} + \item[Sleep Forbidden:] Can the player sleep on the floor? In most cities it is forbidden to sleep outside. Sleeping in the wild carries its + own risks of attack, though, and this entry lets you decide if a player should be allowed to sleep on the floor in this cell or not. + + \item[Interior Water:] Should water be rendered in this interior cell? The game world consists of an endless ocean at height 0. Then the landscape + is added. If part of the landscape goes below height 0, the player will see water. (See illustration.) + + Setting the cell's Interior Water to true tells the game that this cell is both an interior cell (inside a building, for example, rather than + in the open air) but that there still needs to be water at height 0. This is useful for dungeons or mines that have water in them. + + Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in + the outside world are exterior cells and should thus \textit{always} be set to false! + + \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \TB{} expansion took place in a city on + the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is + in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see + the city's very own map, as if he was walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell, + but it would need a sky as if it was an exterior cell. That is what this is. This is why the vast majority of the cells you will find in this screen + will have this option set to false: It is only meant for these "fake exteriors". + + \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area. + It is also possible for a cell not to belong to any region. + +\end{description} + +\subsubsection{Referenceables} +This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type +a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, +does not. All Record Types contain at least a~model. How else would the player see them? Usually they also have a Name, which is what you see +when you hover your reticle over the object. + +Let's go through all Record Types and discuss what you can tell \OCS{} about them. + +\begin{description} + \item[Activator:] This is an item that, when activated, starts a script or even just shows a~tooltip. +\end{description} \ No newline at end of file diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex new file mode 100644 index 000000000..97bb75e79 --- /dev/null +++ b/manual/opencs/windows.tex @@ -0,0 +1,54 @@ +\section{Windows} +\subsection{Introduction} +This section describes the multiple windows interface of the \OCS{} editor. This design principle was chosen in order +to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced +windows management features, like for instance: multiple desktops found commonly on many open source desktop environments. +However, it is enough to have a single large screen to see the advantages of this concept. + +OpenCS windows interface is easy to describe and understand. In fact we decided to minimize use of many windows concepts +applied commonly in various applications. For instance dialog windows are really hard to find in the \OCS. You are free to try, +though. + +Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly +focused on practical ways of organizing work with the \OCS. + +\subsection{Basics} +After starting \OCS{} and choosing content files to use a editor window should show up. It probably does not look surprising: +there is a menubar at the top, and there is a~large empty area. That is it: a brand new \OCS{} window contains only menubar +and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}. +You are free to do so, just try to explore the menubar. + +You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's +just focus on the windows itself. + +\paragraph{Creating new windows} +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, +it is also blank, and you are free to add any of the \OCS{} panels. + +\paragraph{Closing opened window} +is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. +Closing last \OCS{} window will also terminate application session. + +\paragraph{Multi-everything} +is the main foundation of \OCS{} interface. You are free to create as many windows as you want to, free to populate it with +any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and +you are wonder if you are able to have one hundred \OCS{} windows showing panels of the same type, well most likely you are +able to do so. + +The principle behind this design decision is easy to see for \BS{} made editor, but maybe not so clear for users who are +just about to begin their wonderful journey of modding. + +\subsection{Advanced} +So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often +have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense +to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. +All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change +active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work +with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one. + +OpenCS is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one +flexible approach in all cases. + +There is no point in digging deeper in the windows of \OCS. Let's explore panels, starting with tables. + +%We should write some tips and tricks here. \ No newline at end of file diff --git a/readme.txt b/readme.txt index 7865f8dba..6b388dc72 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ 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.26.0 +Version: 0.27.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -23,8 +23,8 @@ Ubuntu (and most others) Download the .deb file and install it in the usual way. Arch Linux -There's an OpenMW package available in the AUR Repository: -http://aur.archlinux.org/packages.php?ID=21419 +There's an OpenMW package available in the [community] Repository: +https://www.archlinux.org/packages/?sort=&q=openmw OS X: Open DMG file, copy OpenMW folder anywhere, for example in /Applications @@ -36,52 +36,87 @@ https://wiki.openmw.org/index.php?title=Development_Environment_Setup THE DATA PATH -The data path tells OpenMW where to find your Morrowind files. From 0.12.0 on OpenMW should be able to +The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install). -If that does not work for you, please check if you have any leftover openmw.cfg files from versions earlier than 0.12.0. These can interfere with the configuration process, so try to remove then. - -If you are running OpenMW without installing it, you still need to manually adjust the data path. Create a text file named openmw.cfg in the location of the binary and enter the following line: - -data=path to your data directory - -(where you replace "path to your data directory" with the actual location of your data directory) - - COMMAND LINE OPTIONS Syntax: openmw Allowed options: - --help print help message - --version print version information and quit - --data arg (=data) set data directories (later directories have higher priority) - --data-local arg set local data directory (highest priority) - --resources arg (=resources) set resources directory - --start arg (=Beshara) set initial cell - --master arg master file(s) - --plugin arg plugin file(s) - --anim-verbose [=arg(=1)] (=0) output animation indices files - --debug [=arg(=1)] (=0) debug mode - --nosound [=arg(=1)] (=0) disable all sounds - --script-verbose [=arg(=1)] (=0) verbose script output - --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scripts) at startup - --script-console [=arg(=1)] (=0) enable console-only script functionality - --script-run arg select a file containing a list of console commands that is executed on startup - --new-game [=arg(=1)] (=0) activate char gen/new game mechanics - --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding) - --encoding arg (=win1252) Character encoding used in OpenMW game messages: - - win1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages - - win1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages - - win1252 - Western European (Latin) alphabet, used by default - - --fallback arg fallback values + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories + have higher priority) + --data-local arg set local data directory (highest + priority) + --fallback-archive arg (=fallback-archive) + set fallback BSA archives (later + archives have higher priority) + --resources arg (=resources) set resources directory + --start arg (=Beshara) set initial cell + --content arg content file(s): esm/esp, or + omwgame/omwaddon + --anim-verbose [=arg(=1)] (=0) output animation indices files + --no-sound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue + scripts) at startup + --script-console [=arg(=1)] (=0) enable console-only script + functionality + --script-run arg select a file containing a list of + console commands that is executed on + startup + --new-game [=arg(=1)] (=0) activate char gen/new game mechanics + --fs-strict [=arg(=1)] (=0) strict file system handling (no case + folding) + --encoding arg (=win1252) Character encoding used in OpenMW game + messages: + + win1250 - Central and Eastern European + such as Polish, Czech, Slovak, + Hungarian, Slovene, Bosnian, Croatian, + Serbian (Latin script), Romanian and + Albanian languages + + win1251 - Cyrillic alphabet such as + Russian, Bulgarian, Serbian Cyrillic + and other languages + + win1252 - Western European (Latin) + alphabet, used by default + --fallback arg fallback values + --no-grab Don't grab mouse cursor + --activate-dist arg (=-1) activation distance override CHANGELOG +0.27.0 + +Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp +Bug #794: incorrect display of decimal numbers +Bug #840: First-person sneaking camera height +Bug #887: Ambient sounds playing while paused +Bug #902: Problems with Polish character encoding +Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key +Bug #910: Some CDs not working correctly with Unshield installer +Bug #917: Quick character creation plugin does not work +Bug #918: Fatigue does not refill +Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) +Feature #57: Acrobatics Skill +Feature #462: Editor: Start Dialogue +Feature #546: Modify ESX selector to handle new content file scheme +Feature #588: Editor: Adjust name/path of edited content files +Feature #644: Editor: Save +Feature #710: Editor: Configure script compiler context +Feature #790: God Mode +Feature #881: Editor: Allow only one instance of OpenCS +Feature #889: Editor: Record filtering +Feature #895: Extinguish torches +Feature #898: Breath meter enhancements +Feature #901: Editor: Default record filter +Feature #913: Merge --master and --plugin switches + 0.26.0 Bug #274: Inconsistencies in the terrain