diff --git a/.gitignore b/.gitignore index 944fb8418..88f591aae 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,6 @@ resources ## generated objects apps/openmw/config.hpp -components/version/version.hpp docs/mainpage.hpp moc_*.cxx *.cxx_parameters diff --git a/.travis.yml b/.travis.yml index d3b54b179..69879bd78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,8 @@ addons: name: "OpenMW/openmw" description: "" notification_email: scrawl@baseoftrash.de - build_command_prepend: "cmake ." - build_command: "make -j3" + build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE" + build_command: "make" branch_pattern: coverity_scan matrix: include: @@ -40,8 +40,8 @@ script: - cd ./build - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi -after_script: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi notifications: recipients: - corrmage+travis-ci@gmail.com diff --git a/AUTHORS.md b/AUTHORS.md index ce76c104e..8e9d35cf2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -29,6 +29,7 @@ Programmers Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) darkf + Dieho Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) Douglas Mencken (dougmencken) @@ -52,6 +53,7 @@ Programmers jeaye Jeffrey Haines (Jyby) Jengerer + Jiří Kuneš (kunesj) Joel Graff (graffy) John Blomberg (fstp) Jordan Ayers @@ -59,6 +61,7 @@ Programmers Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) + Koncord Lars Söderberg (Lazaroth) lazydev Leon Saunders (emoose) @@ -86,6 +89,7 @@ Programmers Nolan Poe (nopoe) Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) + pkubik Radu-Marius Popovici (rpopovici) rdimesio riothamus @@ -109,6 +113,7 @@ Programmers viadanna Vincent Heuken vocollapse + zelurker Manual ------ diff --git a/CHANGELOG.md b/CHANGELOG.md index 466c2ef25..b2e7dc25c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.36.1 +------ + + Bug #2590: Start scripts not added correctly + 0.36.0 ------ diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 2408a5822..2efb6e2bb 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -10,9 +10,10 @@ fi 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/openmw +echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock -sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev +sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 diff --git a/CI/check_tabs.sh b/CI/check_tabs.sh new file mode 100755 index 000000000..91f81e2a1 --- /dev/null +++ b/CI/check_tabs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} apps components) + +if [[ $OUTPUT ]] ; then + echo "Error: Tab characters found!" + echo $OUTPUT + exit 1 +fi diff --git a/CMakeLists.txt b/CMakeLists.txt index a133a8d6a..1e966a163 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 36) -set(OPENMW_VERSION_RELEASE 0) +set(OPENMW_VERSION_RELEASE 1) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") @@ -56,6 +56,8 @@ configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_ option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) +option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE) +option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) @@ -70,6 +72,7 @@ option(BUILD_OPENCS "build OpenMW Construction Set" ON) option(BUILD_WIZARD "build Installation Wizard" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) +option(BUILD_NIFTEST "build nif file tester" OFF) option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) # OS X deployment @@ -199,9 +202,67 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil osgFX) +find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgQt osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) +if(OSG_STATIC) + macro(use_static_osg_plugin_library PLUGIN_NAME) + set(PLUGIN_NAME_DBG ${PLUGIN_NAME}d ${PLUGIN_NAME}D ${PLUGIN_NAME}_d ${PLUGIN_NAME}_D ${PLUGIN_NAME}_debug ${PLUGIN_NAME}) + + # For now, users wishing to do a static build will need to pass the path to where the plugins reside + # More clever logic would need to deduce the path, probably installed under /lib/osgPlugins- + find_library(${PLUGIN_NAME}_LIBRARY_REL NAMES ${PLUGIN_NAME} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) + find_library(${PLUGIN_NAME}_LIBRARY_DBG NAMES ${PLUGIN_NAME_DBG} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) + make_library_set(${PLUGIN_NAME}_LIBRARY) + + if("${${PLUGIN_NAME}_LIBRARY}" STREQUAL "") + message(FATAL_ERROR "Unable to find static OpenSceneGraph plugin: ${PLUGIN_NAME}") + endif() + + set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${PLUGIN_NAME}_LIBRARY}) + endmacro() + + macro(use_static_osg_plugin_dep DEPENDENCY) + find_package(${DEPENDENCY} REQUIRED) + + set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES}) + endmacro() + + add_definitions(-DOSG_LIBRARY_STATIC) + + set(PLUGIN_LIST + osgdb_png # depends on libpng, zlib + osgdb_tga + osgdb_dds + osgdb_jpeg # depends on libjpeg + ) + + foreach(PLUGIN ${PLUGIN_LIST}) + use_static_osg_plugin_library(${PLUGIN}) + endforeach() + + # OSG static plugins need to linked against their respective dependencies + set(PLUGIN_DEPS_LIST + PNG # needed by osgdb_png + ZLIB # needed by osgdb_png + JPEG # needed by osgdb_jpeg + ) + + foreach(DEPENDENCY ${PLUGIN_DEPS_LIST}) + use_static_osg_plugin_dep(${DEPENDENCY}) + endforeach() +endif() + +if(QT_STATIC) + if(WIN32) + if(DESIRED_QT_VERSION MATCHES 4) + # QtCore needs WSAAsyncSelect from Ws2_32.lib + set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib) + message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}") + endif() + endif() +endif() + find_package(MyGUI REQUIRED) if (${MYGUI_VERSION} VERSION_LESS "3.2.1") message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info") @@ -228,7 +289,7 @@ if(MYGUI_STATIC) endif (MYGUI_STATIC) if (APPLE) - configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist + configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw-Info.plist.in "${APP_BUNDLE_DIR}/Contents/Info.plist") configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns @@ -245,6 +306,11 @@ add_subdirectory(files/) if (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") + + if (OPENMW_OSX_DEPLOYMENT) + SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) + endif() else (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") @@ -273,6 +339,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml + "${OpenMW_BINARY_DIR}/openmw.appdata.xml") configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop "${OpenMW_BINARY_DIR}/openmw-cs.desktop") endif() @@ -336,6 +404,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_ESMTOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) + IF(BUILD_NIFTEST) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_NIFTEST) IF(BUILD_MWINIIMPORTER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) @@ -358,6 +429,7 @@ IF(NOT WIN32 AND NOT APPLE) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/appdata" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") @@ -366,6 +438,7 @@ IF(NOT WIN32 AND NOT APPLE) # Install global configuration files INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) @@ -519,6 +592,10 @@ if (BUILD_WIZARD) add_subdirectory(apps/wizard) endif() +if (BUILD_NIFTEST) + add_subdirectory(apps/niftest) +endif(BUILD_NIFTEST) + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) @@ -666,13 +743,65 @@ if (APPLE) install(CODE " set(BU_CHMOD_BUNDLE_ITEMS ON) - include(BundleUtilities) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) + include(BundleUtilitiesWithRPath) " COMPONENT Runtime) - #For now, search unresolved dependencies only in default system paths, so if you put unresolveable (i.e. with @executable_path in id name) lib or framework somewhere else, it would fail - set(DIRS "") + set(ABSOLUTE_PLUGINS "") + set(USED_OSG_PLUGINS + osgdb_tga + osgdb_dds + osgdb_imageio + ) - include(CPack) + foreach (PLUGIN_NAME ${USED_OSG_PLUGINS}) + set(PLUGIN_ABS "${OSG_PLUGIN_LIB_SEARCH_PATH}/${PLUGIN_NAME}.so") + set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS}) + endforeach () + + get_filename_component(PLUGIN_PREFIX_DIR "${OSG_PLUGIN_LIB_SEARCH_PATH}" NAME) + + # installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX}) + # and returns list of install paths for all installed plugins + function (install_plugins_for_bundle bundle_path plugins_var) + set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/PlugIns/${PLUGIN_PREFIX_DIR}") + + set(PLUGINS "") + set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}") + + foreach (PLUGIN ${ABSOLUTE_PLUGINS}) + get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME) + get_filename_component(PLUGIN_RELATIVE_WE ${PLUGIN} NAME_WE) + + set(PLUGIN_DYLIB_IN_BUNDLE "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}") + set(PLUGINS ${PLUGINS} "${PLUGIN_DYLIB_IN_BUNDLE}") + + install(CODE " + copy_resolved_item_into_bundle(\"${PLUGIN}\" \"${PLUGIN_DYLIB_IN_BUNDLE}\") + " COMPONENT Runtime) + endforeach () + + set(${plugins_var} ${PLUGINS} PARENT_SCOPE) + endfunction (install_plugins_for_bundle) + + install_plugins_for_bundle("${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}" PLUGINS) + install_plugins_for_bundle("${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) + + set(DIRS "${CMAKE_PREFIX_PATH}/lib") + + install(CODE " + function(gp_item_default_embedded_path_override item default_embedded_path_var) + if (\${item} MATCHES ${PLUGIN_PREFIX_DIR}) + set(path \"@executable_path/../PlugIns/${PLUGIN_PREFIX_DIR}\") + set(\${default_embedded_path_var} \"\${path}\" PARENT_SCOPE) + endif() + endfunction() + + cmake_policy(SET CMP0009 OLD) + fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"${DIRS}\") + " COMPONENT Runtime) + include(CPack) endif (APPLE) # Doxygen Target -- simply run 'make doc' or 'make doc_pages' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..a9cd6a690 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Description +=========== + +Your pull request description should include (if applicable): + +* A link back to the bug report or forum discussion that prompted the change +* Summary of the changes made +* Reasoning / motivation behind the change +* What testing you have carried out to verify the change + +Other notes +=========== + +* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time. +* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. +* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). diff --git a/README.md b/README.md index f62800e1f..362d2eef4 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ OpenMW [![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) -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. +OpenMW is a recreation of 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.36.0 +OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. + +* Version: 0.36.1 * License: GPL (see docs/license/GPL3.txt for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net @@ -14,6 +15,13 @@ Morrowind by Bethesda Softworks. You need to own and install the original game f Font Licenses: * DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) +Current Status +-------------- + +The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. + +Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. + Getting Started --------------- diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 2ee6c54bb..5fe6471b0 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -493,14 +493,14 @@ void Record::print() std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { - std::cout << " Text:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Text:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Text: [skipped]" << std::endl; + std::cout << " Text: [skipped]" << std::endl; } } @@ -799,14 +799,14 @@ void Record::print() { if (mPrintPlain) { - std::cout << " Result Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mResultScript << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Result Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mResultScript << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Result Script: [skipped]" << std::endl; + std::cout << " Result Script: [skipped]" << std::endl; } } } @@ -841,19 +841,13 @@ void Record::print() std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl; - // Seems like this should done with reference counting in the - // loader to me. But I'm not really knowledgable about this - // record type yet. --Cory - bool wasLoaded = (mData.mDataLoaded != 0); - if (mData.mDataTypes) mData.loadData(mData.mDataTypes); - if (mData.mDataLoaded) + if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes)) { - std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl; + std::cout << " Height Offset: " << data->mHeightOffset << std::endl; // Lots of missing members. - std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl; - std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl; + std::cout << " Unknown1: " << data->mUnk1 << std::endl; + std::cout << " Unknown2: " << data->mUnk2 << std::endl; } - if (!wasLoaded) mData.unloadData(); } template<> @@ -1207,14 +1201,14 @@ void Record::print() if (mPrintPlain) { - std::cout << " Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mScriptText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mScriptText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Script: [skipped]" << std::endl; + std::cout << " Script: [skipped]" << std::endl; } } diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index c1b90ac2b..5e03c64db 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -53,7 +53,7 @@ namespace EsmTool } void setPrintPlain(bool plain) { - mPrintPlain = plain; + mPrintPlain = plain; } virtual void load(ESM::ESMReader &esm) = 0; diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 2ef10ee34..6267ef91e 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -143,7 +143,7 @@ namespace ESSImport osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream); if (!result.success()) { - std::cerr << "can't write global map image: " << result.message() << std::endl; + std::cerr << "can't write global map image: " << result.message() << " code " << result.status() << std::endl; return; } diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index fb8fb8c9f..4bad5e23b 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -507,60 +507,40 @@ class ConvertGAME : public Converter public: ConvertGAME() : mHasGame(false) {} - std::string toString(int weatherId) - { - switch (weatherId) - { - case 0: - return "clear"; - case 1: - return "cloudy"; - case 2: - return "foggy"; - case 3: - return "overcast"; - case 4: - return "rain"; - case 5: - return "thunderstorm"; - case 6: - return "ashstorm"; - case 7: - return "blight"; - case 8: - return "snow"; - case 9: - return "blizzard"; - case -1: - return ""; - default: - { - std::stringstream error; - error << "unknown weather id: " << weatherId; - throw std::runtime_error(error.str()); - } - } - } - virtual void read(ESM::ESMReader &esm) { mGame.load(esm); mHasGame = true; } + int validateWeatherID(int weatherID) + { + if(weatherID >= -1 && weatherID < 10) + { + return weatherID; + } + else + { + std::stringstream error; + error << "Invalid weather ID:" << weatherID << std::endl; + throw std::runtime_error(error.str()); + } + } + virtual void write(ESM::ESMWriter &esm) { if (!mHasGame) return; esm.startRecord(ESM::REC_WTHR); ESM::WeatherState weather; - weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather); - weather.mNextWeather = toString(mGame.mGMDT.mNextWeather); - weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015f*24*3600); - weather.mHour = mContext->mHour; - weather.mWindSpeed = 0.f; - weather.mTimePassed = 0.0; - weather.mFirstUpdate = false; + weather.mTimePassed = 0.0f; + weather.mFastForward = false; + weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour; + weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f); + weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather); + weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather); + weather.mQueuedWeather = -1; + // TODO: Determine how ModRegion modifiers are saved in Morrowind. weather.save(esm); esm.endRecord(ESM::REC_WTHR); } diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index 9d881515d..a5f9d6eb3 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -10,7 +10,21 @@ namespace ESSImport void ActorData::load(ESM::ESMReader &esm) { if (esm.isNextSub("ACTN")) + { + /* + Activation flags: + ActivationFlag_UseEnabled = 1 + ActivationFlag_OnActivate = 2 + ActivationFlag_OnDeath = 10h + ActivationFlag_OnKnockout = 20h + ActivationFlag_OnMurder = 40h + ActivationFlag_DoorOpening = 100h + ActivationFlag_DoorClosing = 200h + ActivationFlag_DoorJammedOpening = 400h + ActivationFlag_DoorJammedClosing = 800h + */ esm.skipHSub(); + } if (esm.isNextSub("STPR")) esm.skipHSub(); diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 32ad1816c..624241039 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -166,7 +166,9 @@ namespace ESSImport if (i >= file2.mRecords.size()) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl; + std::cout.flags(f); return; } @@ -174,7 +176,9 @@ namespace ESSImport if (rec.mName != rec2.mName) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl; + std::cout.flags(f); return; // TODO: try to recover } @@ -185,7 +189,9 @@ namespace ESSImport if (j >= rec2.mSubrecords.size()) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; + std::cout.flags(f); return; } @@ -193,8 +199,10 @@ namespace ESSImport if (sub.mName != sub2.mName) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; + std::cout.flags(f); break; // TODO: try to recover } @@ -203,6 +211,8 @@ namespace ESSImport if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end()) continue; + std::ios::fmtflags f(std::cout.flags()); + std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; @@ -235,6 +245,7 @@ namespace ESSImport std::cout << "\033[0m"; } std::cout << std::endl; + std::cout.flags(f); } } } @@ -319,7 +330,11 @@ namespace ESSImport else { if (unknownRecords.insert(n.val).second) + { + std::ios::fmtflags f(std::cerr.flags()); std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; + std::cerr.flags(f); + } esm.skipRecord(); } diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 3b010cb8f..c93dff269 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -49,6 +49,10 @@ namespace ESSImport std::map mNpcs; Context() + : mDay(0) + , mMonth(0) + , mYear(0) + , mHour(0.f) { mPlayer.mAutoMove = 0; ESM::CellId playerCellId; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 4b7c0504f..9a6f91a0f 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -57,26 +57,6 @@ Launcher::MainDialog::MainDialog(QWidget *parent) // 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); - - versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); - if (!revision.isEmpty() && !tag.isEmpty()) - { - if (revision == tag) { - versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION)); - } else { - versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); - } - - // Add the compile date and time - versionLabel->setToolTip(tr("Compiled on %1 %2").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(); } @@ -186,11 +166,34 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure; } +void Launcher::MainDialog::setVersionLabel() +{ + // Add version information to bottom of the window + Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData()); + + QString revision(QString::fromUtf8(v.mCommitHash.c_str())); + QString tag(QString::fromUtf8(v.mTagHash.c_str())); + + versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag)) + versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str()))); + else + versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); + + // Add the compile date and time + versionLabel->setToolTip(tr("Compiled on %1 %2").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))); +} + bool Launcher::MainDialog::setup() { if (!setupGameSettings()) return false; + setVersionLabel(); + mLauncherSettings.setContentList(mGameSettings); if (!setupGraphicsSettings()) @@ -309,11 +312,11 @@ bool Launcher::MainDialog::setupGameSettings() mGameSettings.readUserFile(stream); } - // Now the rest + // Now the rest - priority: user > local > global QStringList paths; - paths.append(userPath + QString("openmw.cfg")); - paths.append(QString("openmw.cfg")); paths.append(globalPath + QString("openmw.cfg")); + paths.append(QString("openmw.cfg")); + paths.append(userPath + QString("openmw.cfg")); foreach (const QString &path, paths) { qDebug() << "Loading config file:" << qPrintable(path); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index c90309990..298682d20 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -72,6 +72,8 @@ namespace Launcher bool setupGameSettings(); bool setupGraphicsSettings(); + void setVersionLabel(); + void loadSettings(); void saveSettings(); diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt new file mode 100644 index 000000000..d7f0200d2 --- /dev/null +++ b/apps/niftest/CMakeLists.txt @@ -0,0 +1,19 @@ +set(NIFTEST + niftest.cpp +) +source_group(components\\nif\\tests FILES ${NIFTEST}) + +# Main executable +add_executable(niftest + ${NIFTEST} +) + +target_link_libraries(niftest + ${Boost_FILESYSTEM_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(niftest gcov) +endif() diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp new file mode 100644 index 000000000..72393db40 --- /dev/null +++ b/apps/niftest/niftest.cpp @@ -0,0 +1,165 @@ +///Program to test .nif files both on the FileSystem and in BSA archives. + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +// Create local aliases for brevity +namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; + +///See if the file has the named extension +bool hasExtension(std::string filename, std::string extensionToFind) +{ + std::string extension = filename.substr(filename.find_last_of(".")+1); + + //Convert strings to lower case for comparison + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); + + if(extension == extensionToFind) + return true; + else + return false; +} + +///See if the file has the "nif" extension. +bool isNIF(std::string filename) +{ + return hasExtension(filename,"nif"); +} +///See if the file has the "bsa" extension. +bool isBSA(std::string filename) +{ + return hasExtension(filename,"bsa"); +} + +/// Check all the nif files in a given VFS::Archive +/// \note Takes ownership! +/// \note Can not read a bsa file inside of a bsa file. +void readVFS(VFS::Archive* anArchive,std::string archivePath = "") +{ + VFS::Manager myManager(true); + myManager.addArchive(anArchive); + myManager.buildIndex(); + + std::map files=myManager.getIndex(); + for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = it->first; + + try{ + if(isNIF(name)) + { + // std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(myManager.get(name),archivePath+name); + } + else if(isBSA(name)) + { + if(!archivePath.empty() && !isBSA(archivePath)) + { +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/"); +// std::cout << "Done with BSA File: " << name << std::endl; + } + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } + } +} + +std::vector parseOptions (int argc, char** argv) +{ + bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" + "Usages:\n" + " niftool \n" + " Scan the file or directories for nif errors.\n\n" + "Allowed options"); + desc.add_options() + ("help,h", "print help message.") + ("input-file", bpo::value< std::vector >(), "input file") + ; + + //Default option if none provided + bpo::positional_options_description p; + p.add("input-file", -1); + + bpo::variables_map variables; + try + { + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). + options(desc).positional(p).run(); + bpo::store(valid_opts, variables); + } + catch(std::exception &e) + { + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" + << desc << std::endl; + exit(1); + } + + bpo::notify(variables); + if (variables.count ("help")) + { + std::cout << desc << std::endl; + exit(1); + } + if (variables.count("input-file")) + { + return variables["input-file"].as< std::vector >(); + } + + std::cout << "No input files or directories specified!" << std::endl; + std::cout << desc << std::endl; + exit(1); +} + +int main(int argc, char **argv) +{ + std::vector files = parseOptions (argc, argv); + +// std::cout << "Reading Files" << std::endl; + for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = *it; + + try{ + if(isNIF(name)) + { + //std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name); + } + else if(isBSA(name)) + { +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(name)); + } + else if(bfs::is_directory(bfs::path(name))) + { +// std::cout << "Reading All Files in: " << name << std::endl; + readVFS(new VFS::FileSystemArchive(name),name); + } + else + { + std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl; + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } + } + return 0; +} diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 4c7801d46..f9a73a09f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -23,7 +23,7 @@ opencs_units (model/world opencs_units_noqt (model/world - universalid record commands columnbase scriptcontext cell refidcollection + universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager metadata @@ -35,13 +35,18 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck - startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck + startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck + mergestages + ) + +opencs_hdrs_noqt (model/tools + mergestate ) @@ -64,7 +69,7 @@ opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox recordbuttonbar tableeditidaction + dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) opencs_units_noqt (view/world @@ -94,7 +99,7 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview reporttable searchsubview searchbox + reportsubview reporttable searchsubview searchbox merge ) opencs_units_noqt (view/tools @@ -192,6 +197,7 @@ if(APPLE) MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs-Info.plist.in" ) set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9450aa4bf..e3ecdce05 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include @@ -21,7 +20,8 @@ CS::Editor::Editor () : mUserSettings (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), - mLock(), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) + mLock(), mMerge (mDocumentManager), + mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); @@ -40,9 +40,12 @@ CS::Editor::Editor () mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); + mMerge.setLocalData (mLocal); connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), this, SLOT (documentAdded (CSMDoc::Document *))); + connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)), + this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *))); connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), this, SLOT (lastDocumentDeleted())); @@ -50,6 +53,7 @@ CS::Editor::Editor () connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); + connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *))); connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); @@ -360,7 +364,21 @@ void CS::Editor::documentAdded (CSMDoc::Document *document) mViewManager.addView (document); } +void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document) +{ + if (mMerge.getDocument()==document) + mMerge.cancel(); +} + void CS::Editor::lastDocumentDeleted() { QApplication::quit(); } + +void CS::Editor::mergeDocument (CSMDoc::Document *document) +{ + mMerge.configure (document); + mMerge.show(); + mMerge.raise(); + mMerge.activateWindow(); +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 0464fb7eb..ac403b1ee 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -27,11 +27,18 @@ #include "view/settings/dialog.hpp" +#include "view/tools/merge.hpp" + namespace VFS { class Manager; } +namespace CSMDoc +{ + class Document; +} + namespace CS { class Editor : public QObject @@ -55,6 +62,7 @@ namespace CS boost::interprocess::file_lock mLock; boost::filesystem::ofstream mPidFile; bool mFsStrict; + CSVTools::Merge mMerge; void setupDataFiles (const Files::PathContainer& dataDirs); @@ -94,8 +102,12 @@ namespace CS void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void lastDocumentDeleted(); + void mergeDocument (CSMDoc::Document *document); + private: QString mIpcServerName; diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index fd7370a40..de2e6e83e 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp index 083726412..b1d402c69 100644 --- a/apps/opencs/model/doc/blacklist.cpp +++ b/apps/opencs/model/doc/blacklist.cpp @@ -1,4 +1,3 @@ - #include "blacklist.hpp" #include diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 215dbd304..0e2d4e7d1 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -798,9 +798,9 @@ void CSMDoc::Document::addGmsts() "sBookSkillMessage", "sBounty", "sBreath", - "sBribe", - "sBribe", - "sBribe", + "sBribe 10 Gold", + "sBribe 100 Gold", + "sBribe 1000 Gold", "sBribeFail", "sBribeSuccess", "sBuy", @@ -2250,13 +2250,13 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) : mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), - mTools (*this), + mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), mSaving (&mSavingOperation), mResDir(resDir), - mRunner (mProjectPath), mIdCompletionManager(mData) + mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2294,6 +2294,8 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); @@ -2323,7 +2325,7 @@ int CSMDoc::Document::getState() const { int state = 0; - if (!mUndoStack.isClean()) + if (!mUndoStack.isClean() || mDirty) state |= State_Modified; if (mSaving.isRunning()) @@ -2388,6 +2390,12 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C emit stateChanged (getState(), this); } +void CSMDoc::Document::runMerge (std::auto_ptr target) +{ + mTools.runMerge (target); + emit stateChanged (getState(), this); +} + void CSMDoc::Document::abortOperation (int type) { if (type==State_Saving) @@ -2409,6 +2417,9 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) void CSMDoc::Document::operationDone (int type, bool failed) { + if (type==CSMDoc::State_Saving && !failed) + mDirty = false; + emit stateChanged (getState(), this); } @@ -2485,3 +2496,8 @@ CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager() { return mIdCompletionManager; } + +void CSMDoc::Document::flagAsDirty() +{ + mDirty = true; +} diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index c9271fa54..0e8ae6d45 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -68,6 +68,7 @@ namespace CSMDoc boost::filesystem::path mResDir; Blacklist mBlacklist; Runner mRunner; + bool mDirty; CSMWorld::IdCompletionManager mIdCompletionManager; @@ -129,7 +130,9 @@ namespace CSMDoc CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); - + + void runMerge (std::auto_ptr target); + void abortOperation (int type); const CSMWorld::Data& getData() const; @@ -150,12 +153,18 @@ namespace CSMDoc CSMWorld::IdCompletionManager &getIdCompletionManager(); + void flagAsDirty(); + signals: void stateChanged (int state, CSMDoc::Document *document); void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + private slots: void modificationStateChanged (bool clean); @@ -173,4 +182,3 @@ namespace CSMDoc } #endif - diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 5a5f50159..407a608ca 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -1,4 +1,3 @@ - #include "documentmanager.hpp" #include @@ -57,10 +56,24 @@ bool CSMDoc::DocumentManager::isEmpty() void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); + Document *document = makeDocument (files, savePath, new_); + insertDocument (document); +} +CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( + const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_) +{ + return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); +} + +void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) +{ mDocuments.push_back (document); + connect (document, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SLOT (insertDocument (CSMDoc::Document*))); + emit loadRequest (document); mLoader.hasThingsToDo().wakeAll(); @@ -73,6 +86,8 @@ void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) if (iter==mDocuments.end()) throw std::runtime_error ("removing invalid document"); + emit documentAboutToBeRemoved (document); + mDocuments.erase (iter); document->deleteLater(); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index b61656d4b..4f6b2b2c9 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -56,6 +56,15 @@ namespace CSMDoc ///< \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. + /// Create a new document. The ownership of the created document is transferred to + /// the calling function. The DocumentManager does not manage it. Loading has not + /// taken place at the point when the document is returned. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. + Document *makeDocument (const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_); + void setResourceDir (const boost::filesystem::path& parResDir); void setEncoding (ToUTF8::FromType encoding); @@ -84,10 +93,16 @@ namespace CSMDoc void removeDocument (CSMDoc::Document *document); ///< Emits the lastDocumentDeleted signal, if applicable. + /// Hand over document to *this. The ownership is transferred. The DocumentManager + /// will initiate the load procedure, if necessary + void insertDocument (CSMDoc::Document *document); + signals: void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void loadRequest (CSMDoc::Document *document); void lastDocumentDeleted(); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 33725a6f9..cb3ff2cd0 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index bd6e808c8..86e96a88d 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,4 +1,3 @@ - #include "messages.hpp" CSMDoc::Message::Message() {} @@ -8,6 +7,20 @@ CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& me : mId (id), mMessage (message), mHint (hint), mSeverity (severity) {} +std::string CSMDoc::Message::toString (Severity severity) +{ + switch (severity) + { + case CSMDoc::Message::Severity_Info: return "Information"; + case CSMDoc::Message::Severity_Warning: return "Warning"; + case CSMDoc::Message::Severity_Error: return "Error"; + case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; + case CSMDoc::Message::Severity_Default: break; + } + + return ""; +} + CSMDoc::Messages::Messages (Message::Severity default_) : mDefault (default_) @@ -18,7 +31,7 @@ void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& { if (severity==Message::Severity_Default) severity = mDefault; - + mMessages.push_back (Message (id, message, hint, severity)); } diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 86f5feb15..429feae4e 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -21,18 +21,20 @@ namespace CSMDoc // reporting it correctly Severity_Default = 4 }; - + CSMWorld::UniversalId mId; std::string mMessage; std::string mHint; Severity mSeverity; Message(); - + Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity); + + static std::string toString (Severity severity); }; - + class Messages { public: diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 8b2717086..cb9b7ec29 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -1,4 +1,3 @@ - #include "operation.hpp" #include diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 703f9d44b..3c86a6e23 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -83,7 +83,9 @@ namespace CSMDoc void executeStage(); - void operationDone(); + protected slots: + + virtual void operationDone(); }; } diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index 25fc6fc26..db0d1a9a4 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -1,4 +1,3 @@ - #include "operationholder.hpp" #include "../settings/usersettings.hpp" diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 14fe0cda8..5a0bc39be 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -1,4 +1,3 @@ - #include "runner.hpp" #include diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 9f6e469b8..95a2feaf2 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,4 +1,3 @@ - #include "saving.hpp" #include "../world/data.hpp" @@ -81,22 +80,25 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteCollectionStage > (mDocument.getData().getStartScripts(), mState)); - appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); - - appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); - appendStage (new WriteRefIdCollectionStage (mDocument, mState)); appendStage (new CollectionReferencesStage (mDocument, mState)); appendStage (new WriteCellCollectionStage (mDocument, mState)); + // Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); + appendStage (new WritePathgridCollectionStage (mDocument, mState)); - appendStage (new WriteLandCollectionStage (mDocument, mState)); - appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); + // references Land Textures + appendStage (new WriteLandCollectionStage (mDocument, mState)); + // close file and clean up appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index f78c57ecd..c6d8a8cb3 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,4 +1,3 @@ - #include "savingstages.hpp" #include @@ -416,15 +415,16 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) if (land.mState==CSMWorld::RecordBase::State_Modified || land.mState==CSMWorld::RecordBase::State_ModifiedOnly) { - CSMWorld::Land record = land.get(); + const CSMWorld::Land& record = land.get(); - mState.getWriter().startRecord (record.mLand->sRecordId); + mState.getWriter().startRecord (record.sRecordId); - record.mLand->save (mState.getWriter()); - if(record.mLand->mLandData) - record.mLand->mLandData->save (mState.getWriter()); + record.save (mState.getWriter()); - mState.getWriter().endRecord (record.mLand->sRecordId); + if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) + data->save (mState.getWriter()); + + mState.getWriter().endRecord (record.sRecordId); } else if (land.mState==CSMWorld::RecordBase::State_Deleted) { @@ -455,6 +455,8 @@ void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& mess mState.getWriter().startRecord (record.sRecordId); + mState.getWriter().writeHNString("NAME", record.mId); + record.save (mState.getWriter()); mState.getWriter().endRecord (record.sRecordId); diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index e7ad551b2..10539c1b5 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -1,4 +1,3 @@ - #include "savingstate.hpp" #include "operation.hpp" diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp index 78aa14574..c8da86069 100644 --- a/apps/opencs/model/doc/stage.cpp +++ b/apps/opencs/model/doc/stage.cpp @@ -1,4 +1,3 @@ - #include "stage.hpp" CSMDoc::Stage::~Stage() {} diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 78f468101..7352b4b99 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -12,7 +12,7 @@ namespace CSMDoc State_Saving = 16, State_Verifying = 32, - State_Compiling = 64, // not implemented yet + State_Merging = 64, State_Searching = 128, State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp index 4249fc228..908662799 100644 --- a/apps/opencs/model/filter/andnode.cpp +++ b/apps/opencs/model/filter/andnode.cpp @@ -1,4 +1,3 @@ - #include "andnode.hpp" #include diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp index 35fc98e08..ee7ddc1c0 100644 --- a/apps/opencs/model/filter/booleannode.cpp +++ b/apps/opencs/model/filter/booleannode.cpp @@ -1,4 +1,3 @@ - #include "booleannode.hpp" CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp index 055a1747c..6745e165e 100644 --- a/apps/opencs/model/filter/leafnode.cpp +++ b/apps/opencs/model/filter/leafnode.cpp @@ -1,4 +1,3 @@ - #include "leafnode.hpp" std::vector CSMFilter::LeafNode::getReferencedColumns() const diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp index 98f706c87..f2e0e5cb2 100644 --- a/apps/opencs/model/filter/narynode.cpp +++ b/apps/opencs/model/filter/narynode.cpp @@ -1,4 +1,3 @@ - #include "narynode.hpp" #include diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp index 091dc4698..e25610675 100644 --- a/apps/opencs/model/filter/node.cpp +++ b/apps/opencs/model/filter/node.cpp @@ -1,4 +1,3 @@ - #include "node.hpp" CSMFilter::Node::Node() {} diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp index b5d9da7b7..ba5302bbe 100644 --- a/apps/opencs/model/filter/notnode.cpp +++ b/apps/opencs/model/filter/notnode.cpp @@ -1,4 +1,3 @@ - #include "notnode.hpp" CSMFilter::NotNode::NotNode (boost::shared_ptr child) : UnaryNode (child, "not") {} diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp index c5d15a384..41ec7b5cf 100644 --- a/apps/opencs/model/filter/ornode.cpp +++ b/apps/opencs/model/filter/ornode.cpp @@ -1,4 +1,3 @@ - #include "ornode.hpp" #include diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 51338dfc9..7936a1ae2 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp index 73c378f11..246ebae24 100644 --- a/apps/opencs/model/filter/textnode.cpp +++ b/apps/opencs/model/filter/textnode.cpp @@ -1,4 +1,3 @@ - #include "textnode.hpp" #include diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp index c40d191b6..cbdadf6fc 100644 --- a/apps/opencs/model/filter/unarynode.cpp +++ b/apps/opencs/model/filter/unarynode.cpp @@ -1,4 +1,3 @@ - #include "unarynode.hpp" CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr child, const std::string& name) diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp index 6fdb5cb02..85c5a8e27 100644 --- a/apps/opencs/model/filter/valuenode.cpp +++ b/apps/opencs/model/filter/valuenode.cpp @@ -1,4 +1,3 @@ - #include "valuenode.hpp" #include diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 1bfc6e85b..24680ada3 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -162,7 +162,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() ritd->setDeclaredValues (values); } - declareSection ("table-input", "Table Input"); + declareSection ("table-input", "ID Tables"); { QString inPlaceEdit ("Edit in Place"); QString editRecord ("Edit Record"); @@ -215,9 +215,24 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "Jump to the added or cloned record."); jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDeclaredValues (jumpValues); + + Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config", + "Manually specify affected record types for an extended delete/revert"); + extendedConfig->setDefaultValue("false"); + extendedConfig->setToolTip("Delete and revert commands have an extended form that also affects " + "associated records.\n\n" + "If this option is enabled, types of affected records are selected " + "manually before a command execution.\nOtherwise, all associated " + "records are deleted/reverted immediately."); } - declareSection ("report-input", "Report Input"); + declareSection ("dialogues", "ID Dialogues"); + { + Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); + toolbar->setDefaultValue ("true"); + } + + declareSection ("report-input", "Reports"); { QString none ("None"); QString edit ("Edit"); @@ -257,7 +272,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() shiftCtrlDoubleClick->setDefaultValue (none); shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:

" + toolTip); } - + declareSection ("search", "Search & Replace"); { Setting *before = createSetting (Type_SpinBox, "char-before", @@ -299,7 +314,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() QStringList modes; modes << "Ignore" << modeNormal << "Strict"; - + Setting *warnings = createSetting (Type_ComboBox, "warnings", "Warning Mode"); warnings->setDeclaredValues (modes); @@ -309,7 +324,16 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "

  • Normal: Report warning as a warning
  • " "
  • Strict: Promote warning to an error
  • " ""); - + + Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); + toolbar->setDefaultValue ("true"); + + Setting *delay = createSetting (Type_SpinBox, "compile-delay", + "Delay between updating of source errors"); + delay->setDefaultValue (100); + delay->setRange (0, 10000); + delay->setToolTip ("Delay in milliseconds"); + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); @@ -346,7 +370,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() cycle->setToolTip ("When using next/previous functions at the last/first item of a " "list go to the first/last item"); } - + { /****************************************************************** * There are three types of values: diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index 4e6da4631..9898352f1 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -1,4 +1,3 @@ - #include "birthsigncheck.hpp" #include diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index be57a3729..e4964d4e3 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -1,4 +1,3 @@ - #include "classcheck.hpp" #include diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 0dfdee775..621b28070 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -1,4 +1,3 @@ - #include "factioncheck.hpp" #include diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp new file mode 100644 index 000000000..5435881b3 --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -0,0 +1,133 @@ +#include "magiceffectcheck.hpp" + +#include + +#include "../world/resources.hpp" +#include "../world/data.hpp" + +namespace +{ + void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text) + { + if (!text.empty()) + { + messages.push_back(std::make_pair(id, text)); + } + } +} + +bool CSMTools::MagicEffectCheckStage::isTextureExists(const std::string &texture, bool isIcon) const +{ + const CSMWorld::Resources &textures = isIcon ? mIcons : mTextures; + bool exists = false; + + if (textures.searchId(texture) != -1) + { + exists = true; + } + else + { + std::string ddsTexture = texture; + if (Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && textures.searchId(ddsTexture) != -1) + { + exists = true; + } + } + + return exists; +} + +std::string CSMTools::MagicEffectCheckStage::checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const +{ + std::string error; + if (!id.empty()) + { + CSMWorld::RefIdData::LocalIndex index = mReferenceables.getDataSet().searchId(id); + if (index.first == -1) + { + error = "No such " + column + " '" + id + "'"; + } + else if (index.second != type.getType()) + { + error = column + " is not of type " + type.getTypeName(); + } + } + return error; +} + +std::string CSMTools::MagicEffectCheckStage::checkSound(const std::string &id, const std::string &column) const +{ + std::string error; + if (!id.empty() && mSounds.searchId(id) == -1) + { + error = "No such " + column + " '" + id + "'"; + } + return error; +} + +CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures) + : mMagicEffects(effects), + mSounds(sounds), + mReferenceables(referenceables), + mIcons(icons), + mTextures(textures) +{} + +int CSMTools::MagicEffectCheckStage::setup() +{ + return mMagicEffects.getSize(); +} + +void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) +{ + ESM::MagicEffect effect = mMagicEffects.getRecord(stage).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + + if (effect.mData.mBaseCost < 0.0f) + { + messages.push_back(std::make_pair(id, "Base Cost is negative")); + } + + if (effect.mIcon.empty()) + { + messages.push_back(std::make_pair(id, "Icon is not specified")); + } + else if (!isTextureExists(effect.mIcon, true)) + { + messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); + } + + if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false)) + { + messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); + } + + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object")); + + addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound")); + + if (effect.mDescription.empty()) + { + messages.push_back(std::make_pair(id, "Description is empty")); + } +} diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp new file mode 100644 index 000000000..0ad6760d3 --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP +#define CSM_TOOLS_MAGICEFFECTCHECK_HPP + +#include +#include + +#include "../world/idcollection.hpp" +#include "../world/refidcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMWorld +{ + class Resources; +} + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that magic effect records are internally consistent + class MagicEffectCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection &mMagicEffects; + const CSMWorld::IdCollection &mSounds; + const CSMWorld::RefIdCollection &mReferenceables; + const CSMWorld::Resources &mIcons; + const CSMWorld::Resources &mTextures; + + private: + bool isTextureExists(const std::string &texture, bool isIcon) const; + + std::string checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const; + std::string checkSound(const std::string &id, const std::string &column) const; + + public: + MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures); + + virtual int setup(); + ///< \return number of steps + virtual void perform (int stage, CSMDoc::Messages &messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index 4c97d2266..23adb9d37 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -1,4 +1,3 @@ - #include "mandatoryid.hpp" #include "../world/collectionbase.hpp" diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp new file mode 100644 index 000000000..907d742ed --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -0,0 +1,59 @@ + +#include "mergeoperation.hpp" + +#include "../doc/state.hpp" +#include "../doc/document.hpp" + +#include "mergestages.hpp" + +CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) +: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) +{ + appendStage (new StartMergeStage (mState)); + + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getClasses)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFactions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRaces)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSounds)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getScripts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRegions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBirthsigns)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSpells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopics)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getCells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFilters)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getEnchantments)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBodyParts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getDebugProfiles)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); + appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); + appendStage (new MergeRefIdsStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new ListLandTexturesMergeStage (mState)); + appendStage (new MergeLandTexturesStage (mState)); + appendStage (new MergeLandStage (mState)); + + appendStage (new FinishMergedDocumentStage (mState, encoding)); +} + +void CSMTools::MergeOperation::setTarget (std::auto_ptr document) +{ + mState.mTarget = document; +} + +void CSMTools::MergeOperation::operationDone() +{ + CSMDoc::Operation::operationDone(); + + if (mState.mCompleted) + emit mergeDone (mState.mTarget.release()); +} diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp new file mode 100644 index 000000000..bdaeb2ccd --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -0,0 +1,45 @@ +#ifndef CSM_TOOLS_MERGEOPERATION_H +#define CSM_TOOLS_MERGEOPERATION_H + +#include + +#include + +#include "../doc/operation.hpp" + +#include "mergestate.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class MergeOperation : public CSMDoc::Operation + { + Q_OBJECT + + MergeState mState; + + public: + + MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding); + + /// \attention Do not call this function while a merge is running. + void setTarget (std::auto_ptr document); + + protected slots: + + virtual void operationDone(); + + signals: + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp new file mode 100644 index 000000000..52e1e6964 --- /dev/null +++ b/apps/opencs/model/tools/mergestages.cpp @@ -0,0 +1,258 @@ + +#include "mergestages.hpp" + +#include + +#include + +#include "mergestate.hpp" + +#include "../doc/document.hpp" +#include "../world/data.hpp" + + +CSMTools::StartMergeStage::StartMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::StartMergeStage::setup() +{ + return 1; +} + +void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mCompleted = false; + mState.mTextureIndices.clear(); +} + + +CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) +: mState (state), mEncoder (encoding) +{} + +int CSMTools::FinishMergedDocumentStage::setup() +{ + return 1; +} + +void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) +{ + // We know that the content file list contains at least two entries and that the first one + // does exist on disc (otherwise it would have been impossible to initiate a merge on that + // document). + boost::filesystem::path path = mState.mSource.getContentFiles()[0]; + + ESM::ESMReader reader; + reader.setEncoder (&mEncoder); + reader.open (path.string()); + + CSMWorld::MetaData source; + source.mId = "sys::meta"; + source.load (reader); + + CSMWorld::MetaData target = mState.mTarget->getData().getMetaData(); + + target.mAuthor = source.mAuthor; + target.mDescription = source.mDescription; + + mState.mTarget->getData().setMetaData (target); + + mState.mCompleted = true; +} + + +CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeRefIdsStage::setup() +{ + return mState.mSource.getData().getReferenceables().getSize(); +} + +void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mSource.getData().getReferenceables().copyTo ( + stage, mState.mTarget->getData().getReferenceables()); +} + + +CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state) +: mState (state) +{} + +int CSMTools::MergeReferencesStage::setup() +{ + mIndex.clear(); + return mState.mSource.getData().getReferences().getSize(); +} + +void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getReferences().getRecord (stage); + + if (!record.isDeleted()) + { + CSMWorld::CellRef ref = record.get(); + + ref.mOriginalCell = ref.mCell; + + ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; + ref.mRefNum.mContentFile = 0; + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); + + mState.mTarget->getData().getReferences().appendRecord (newRecord); + } +} + + +CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::ListLandTexturesMergeStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + // make sure record is loaded + land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX)) + { + // list texture indices + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + + mState.mTextureIndices[key] = -1; + } + } + } +} + + +CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state) +: mState (state), mNext (mState.mTextureIndices.end()) +{} + +int CSMTools::MergeLandTexturesStage::setup() +{ + // Should use the size of mState.mTextureIndices instead, but that is not available at this + // point. Unless there are any errors in the land and land texture records this will not + // make a difference. + return mState.mSource.getData().getLandTextures().getSize(); +} + +void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages) +{ + if (stage==0) + mNext = mState.mTextureIndices.begin(); + + bool found = false; + + do + { + if (mNext==mState.mTextureIndices.end()) + return; + + mNext->second = stage+1; + + std::ostringstream stream; + stream << mNext->first.first-1 << "_" << mNext->first.second; + + int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); + + if (index!=-1) + { + CSMWorld::LandTexture texture = + mState.mSource.getData().getLandTextures().getRecord (index).get(); + + std::ostringstream stream; + stream << mNext->second-1 << "_0"; + + texture.mIndex = mNext->second-1; + texture.mId = stream.str(); + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); + + mState.mTarget->getData().getLandTextures().appendRecord (newRecord); + + found = true; + } + + ++mNext; + } + while (!found); +} + + +CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeLandStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + CSMWorld::Land newLand (land); + + newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway, + // because record is already fully loaded) + newLand.mPlugin = 0; + + if (land.mDataTypes & ESM::Land::DATA_VTEX) + { + // adjust land texture references + if (ESM::Land::LandData *data = newLand.getLandData()) + { + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + std::map, int>::const_iterator iter = + mState.mTextureIndices.find (key); + + if (iter!=mState.mTextureIndices.end()) + data->mTextures[i] = iter->second; + else + data->mTextures[i] = 0; + } + } + } + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand); + + mState.mTarget->getData().getLand().appendRecord (newRecord); + } +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp new file mode 100644 index 000000000..f88f5be9f --- /dev/null +++ b/apps/opencs/model/tools/mergestages.hpp @@ -0,0 +1,166 @@ +#ifndef CSM_TOOLS_MERGESTAGES_H +#define CSM_TOOLS_MERGESTAGES_H + +#include +#include + +#include + +#include "../doc/stage.hpp" + +#include "../world/data.hpp" + +#include "mergestate.hpp" + +namespace CSMTools +{ + class StartMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + StartMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class FinishMergedDocumentStage : public CSMDoc::Stage + { + MergeState& mState; + ToUTF8::Utf8Encoder mEncoder; + + public: + + FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template > + class MergeIdCollectionStage : public CSMDoc::Stage + { + MergeState& mState; + Collection& (CSMWorld::Data::*mAccessor)(); + + public: + + MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template + MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) + : mState (state), mAccessor (accessor) + {} + + template + int MergeIdCollectionStage::setup() + { + return (mState.mSource.getData().*mAccessor)().getSize(); + } + + template + void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) + { + const Collection& source = (mState.mSource.getData().*mAccessor)(); + Collection& target = (mState.mTarget->getData().*mAccessor)(); + + const CSMWorld::Record& record = source.getRecord (stage); + + if (!record.isDeleted()) + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); + } + + class MergeRefIdsStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeRefIdsStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeReferencesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map mIndex; + + public: + + MergeReferencesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class ListLandTexturesMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + ListLandTexturesMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandTexturesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map, int>::iterator mNext; + + public: + + MergeLandTexturesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeLandStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp new file mode 100644 index 000000000..29e1bbda7 --- /dev/null +++ b/apps/opencs/model/tools/mergestate.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_TOOLS_MERGESTATE_H +#define CSM_TOOLS_MERGESTATE_H + +#include + +#include +#include + +#include "../doc/document.hpp" + +namespace CSMTools +{ + struct MergeState + { + std::auto_ptr mTarget; + CSMDoc::Document& mSource; + bool mCompleted; + std::map, int> mTextureIndices; // (texture, content file) -> new texture + + MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} + }; +} + +#endif diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 3b2c8d290..b30088620 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -1,4 +1,3 @@ - #include "racecheck.hpp" #include diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 42abc35c9..2fdff5f38 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -1,4 +1,3 @@ - #include "regioncheck.hpp" #include diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 480691710..77a14de84 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -1,4 +1,3 @@ - #include "reportmodel.hpp" #include @@ -13,7 +12,7 @@ CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) if (severityColumn) mColumnSeverity = index++; - + if (fieldColumn) mColumnField = index++; @@ -46,7 +45,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const case Column_Type: return static_cast (mRows.at (index.row()).mId.getType()); - + case Column_Id: { CSMWorld::UniversalId id = mRows.at (index.row()).mId; @@ -56,7 +55,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const return QString ("-"); } - + case Column_Hint: return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); @@ -85,16 +84,10 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const if (index.column()==mColumnSeverity) { - switch (mRows.at (index.row()).mSeverity) - { - case CSMDoc::Message::Severity_Info: return "Information"; - case CSMDoc::Message::Severity_Warning: return "Warning"; - case CSMDoc::Message::Severity_Error: return "Error"; - case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; - case CSMDoc::Message::Severity_Default: break; - } + return QString::fromUtf8 ( + CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str()); } - + return QVariant(); } @@ -144,7 +137,7 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p void CSMTools::ReportModel::add (const CSMDoc::Message& message) { beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - + mRows.push_back (message); endInsertRows(); diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index 665edd7a3..d7c41cfcf 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -1,4 +1,3 @@ - #include "scriptcheck.hpp" #include diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 449df2c63..0409199af 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -1,4 +1,3 @@ - #include "search.hpp" #include diff --git a/apps/opencs/model/tools/searchoperation.cpp b/apps/opencs/model/tools/searchoperation.cpp index 8cbc5dc8e..8fba1cc1e 100644 --- a/apps/opencs/model/tools/searchoperation.cpp +++ b/apps/opencs/model/tools/searchoperation.cpp @@ -1,4 +1,3 @@ - #include "searchoperation.hpp" #include "../doc/state.hpp" diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp index 17859d930..3db10b0c3 100644 --- a/apps/opencs/model/tools/searchstage.cpp +++ b/apps/opencs/model/tools/searchstage.cpp @@ -1,4 +1,3 @@ - #include "searchstage.hpp" #include "../world/idtablebase.hpp" diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index 2b55526e0..77ba8d4a2 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -1,4 +1,3 @@ - #include "skillcheck.hpp" #include diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index f78932a32..6a059bee2 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -1,4 +1,3 @@ - #include "soundcheck.hpp" #include diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index bd076d2a5..91aed37ed 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -1,4 +1,3 @@ - #include "spellcheck.hpp" #include diff --git a/apps/opencs/model/tools/startscriptcheck.cpp b/apps/opencs/model/tools/startscriptcheck.cpp index e3c01368b..220751797 100644 --- a/apps/opencs/model/tools/startscriptcheck.cpp +++ b/apps/opencs/model/tools/startscriptcheck.cpp @@ -1,4 +1,3 @@ - #include "startscriptcheck.hpp" #include diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index c9c116091..fdbf406f1 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -1,4 +1,3 @@ - #include "tools.hpp" #include @@ -28,6 +27,8 @@ #include "searchoperation.hpp" #include "pathgridcheck.hpp" #include "soundgencheck.hpp" +#include "magiceffectcheck.hpp" +#include "mergeoperation.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -35,6 +36,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { case CSMDoc::State_Verifying: return &mVerifier; case CSMDoc::State_Searching: return &mSearch; + case CSMDoc::State_Merging: return &mMerge; } return 0; @@ -53,7 +55,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() std::vector settings; settings.push_back ("script-editor/warnings"); - + mVerifierOperation->configureSettings (settings); connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); @@ -108,15 +110,21 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getSounds(), mData.getReferenceables())); + mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(), + mData.getSounds(), + mData.getReferenceables(), + mData.getResources (CSMWorld::UniversalId::Type_Icons), + mData.getResources (CSMWorld::UniversalId::Type_Textures))); + mVerifier.setOperation (mVerifierOperation); } return &mVerifier; } -CSMTools::Tools::Tools (CSMDoc::Document& document) +CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) : mDocument (document), mData (document.getData()), mVerifierOperation (0), - mSearchOperation (0), mNextReportNumber (0) + mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); @@ -126,6 +134,10 @@ CSMTools::Tools::Tools (CSMDoc::Document& document) connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (verifierMessage (const CSMDoc::Message&, int))); + + connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + // don't need to connect report message, since there are no messages for merge } CSMTools::Tools::~Tools() @@ -142,6 +154,12 @@ CSMTools::Tools::~Tools() delete mSearchOperation; } + if (mMergeOperation) + { + mMerge.abortAndWait(); + delete mMergeOperation; + } + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; } @@ -153,7 +171,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId& if (mReports.find (reportNumber)==mReports.end()) mReports.insert (std::make_pair (reportNumber, new ReportModel)); - + mActiveReports[CSMDoc::State_Verifying] = reportNumber; getVerifier()->start(); @@ -183,6 +201,25 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se mSearch.start(); } +void CSMTools::Tools::runMerge (std::auto_ptr target) +{ + // not setting an active report, because merge does not produce messages + + if (!mMergeOperation) + { + mMergeOperation = new MergeOperation (mDocument, mEncoding); + mMerge.setOperation (mMergeOperation); + connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); + } + + target->flagAsDirty(); + + mMergeOperation->setTarget (target); + + mMerge.start(); +} + void CSMTools::Tools::abortOperation (int type) { if (CSMDoc::OperationHolder *operation = get (type)) @@ -195,6 +232,7 @@ int CSMTools::Tools::getRunningOperations() const { CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 }; @@ -225,4 +263,3 @@ void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type) if (iter!=mActiveReports.end()) mReports[iter->second]->add (message); } - diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 78484d15d..e16a3854c 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -1,9 +1,14 @@ #ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H +#include +#include + +#include + #include -#include +#include #include "../doc/operationholder.hpp" @@ -24,6 +29,7 @@ namespace CSMTools class ReportModel; class Search; class SearchOperation; + class MergeOperation; class Tools : public QObject { @@ -35,9 +41,12 @@ namespace CSMTools CSMDoc::OperationHolder mVerifier; SearchOperation *mSearchOperation; CSMDoc::OperationHolder mSearch; + MergeOperation *mMergeOperation; + CSMDoc::OperationHolder mMerge; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number + ToUTF8::FromType mEncoding; // not implemented Tools (const Tools&); @@ -53,7 +62,7 @@ namespace CSMTools public: - Tools (CSMDoc::Document& document); + Tools (CSMDoc::Document& document, ToUTF8::FromType encoding); virtual ~Tools(); @@ -67,7 +76,9 @@ namespace CSMTools CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); - + + void runMerge (std::auto_ptr target); + void abortOperation (int type); ///< \attention The operation is not aborted immediately. @@ -85,6 +96,10 @@ namespace CSMTools void progress (int current, int max, int type); void done (int type, bool failed); + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); }; } diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 40520a9ba..91becdb74 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -1,4 +1,3 @@ - #include "cell.hpp" #include diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index b1c8441e6..95e206e2d 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,4 +1,3 @@ - #include "cellcoordinates.hpp" #include diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp index 73b5196f1..c6988e488 100644 --- a/apps/opencs/model/world/cellselection.cpp +++ b/apps/opencs/model/world/cellselection.cpp @@ -1,4 +1,3 @@ - #include "cellselection.hpp" #include diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index b8eed4192..6134dc172 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -1,4 +1,3 @@ - #include "collectionbase.hpp" #include diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index f209e48c6..2143ec730 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -82,7 +82,6 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 59f2836c2..400e31333 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -118,7 +118,6 @@ namespace CSMWorld Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, @@ -192,6 +191,12 @@ namespace CSMWorld ColumnBase::Display_NestedHeader, flags) {} + virtual void set (Record& record, const QVariant& data) + { + // There is nothing to do here. + // This prevents exceptions from parent's implementation + } + virtual QVariant get (const Record& record) const { return true; // required by IdTree::hasChildren() diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp new file mode 100644 index 000000000..dc3d39edb --- /dev/null +++ b/apps/opencs/model/world/columnimp.cpp @@ -0,0 +1,28 @@ +#include "columnimp.hpp" + +CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) + : mMeshType(meshType) +{} + +QVariant CSMWorld::BodyPartRaceColumn::get(const Record &record) const +{ + if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + { + return QString::fromUtf8(record.get().mRace.c_str()); + } + return QVariant(QVariant::UserType); +} + +void CSMWorld::BodyPartRaceColumn::set(Record &record, const QVariant &data) +{ + ESM::BodyPart record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified(record2); +} + +bool CSMWorld::BodyPartRaceColumn::isEditable() const +{ + return true; +} diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 15dd2c15b..4e608dbbd 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,6 +9,10 @@ #include +#include +#include +#include + #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" @@ -1911,8 +1915,8 @@ namespace CSMWorld template struct MeshTypeColumn : public Column { - MeshTypeColumn() - : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType) + MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) + : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) {} virtual QVariant get (const Record& record) const @@ -2379,7 +2383,18 @@ namespace CSMWorld { return true; } - }; + }; + + struct BodyPartRaceColumn : public RaceColumn + { + const MeshTypeColumn *mMeshType; + + BodyPartRaceColumn(const MeshTypeColumn *meshType); + + virtual QVariant get(const Record &record) const; + virtual void set(Record &record, const QVariant &data); + virtual bool isEditable() const; + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 7aec68309..2e0a697a5 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -1,4 +1,3 @@ - #include "columns.hpp" #include @@ -35,6 +34,8 @@ namespace CSMWorld { ColumnId_Volume, "Volume" }, { ColumnId_MinRange, "Min Range" }, { ColumnId_MaxRange, "Max Range" }, + { ColumnId_MinMagnitude, "Min Magnitude" }, + { ColumnId_MaxMagnitude, "Max Magnitude" }, { ColumnId_SoundFile, "Sound File" }, { ColumnId_MapColour, "Map Colour" }, { ColumnId_SleepEncounter, "Sleep Encounter" }, @@ -70,7 +71,6 @@ namespace CSMWorld { ColumnId_Weight, "Weight" }, { ColumnId_EnchantmentPoints, "Enchantment Points" }, { ColumnId_Quality, "Quality" }, - { ColumnId_Ai, "AI" }, { ColumnId_AiHello, "AI Hello" }, { ColumnId_AiFlee, "AI Flee" }, { ColumnId_AiFight, "AI Fight" }, @@ -107,7 +107,6 @@ namespace CSMWorld { ColumnId_OriginalCreature, "Original Creature" }, { ColumnId_Biped, "Biped" }, { ColumnId_HasWeapon, "Has Weapon" }, - { ColumnId_NoMovement, "No Movement" }, { ColumnId_Swims, "Swims" }, { ColumnId_Flies, "Flies" }, { ColumnId_Walks, "Walks" }, @@ -199,8 +198,6 @@ namespace CSMWorld { ColumnId_RotY, "Rotation Y"}, { ColumnId_RotZ, "Rotation Z"}, - { ColumnId_Skill, "Skill" }, - { ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_DefaultProfile, "Default Profile" }, { ColumnId_BypassNewGame, "Bypass New Game" }, @@ -265,13 +262,13 @@ namespace CSMWorld { ColumnId_LevelledList,"Levelled List" }, { ColumnId_LevelledItemId,"Levelled Item" }, - { ColumnId_LevelledItemLevel,"Level" }, + { ColumnId_LevelledItemLevel,"Item Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemTypeEach, "Select a new item each instance" }, { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, - { ColumnId_SkillImpact, "Skills" }, + { ColumnId_SkillImpact, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, @@ -281,26 +278,24 @@ namespace CSMWorld { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, - { ColumnId_NpcAttributes, "Attributes" }, - { ColumnId_NpcSkills, "Skills" }, + { ColumnId_NpcAttributes, "NPC Attributes" }, + { ColumnId_NpcSkills, "NPC Skill" }, { ColumnId_UChar, "Value [0..255]" }, - { ColumnId_NpcMisc, "Misc" }, - { ColumnId_NpcLevel, "Level" }, + { ColumnId_NpcMisc, "NPC Misc" }, + { ColumnId_Level, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, - { ColumnId_NpcHealth, "Health" }, - { ColumnId_NpcMana, "Mana" }, - { ColumnId_NpcFatigue, "Fatigue" }, - { ColumnId_NpcDisposition, "Disposition" }, + { ColumnId_Mana, "Mana" }, + { ColumnId_Fatigue, "Fatigue" }, + { ColumnId_NpcDisposition, "NPC Disposition" }, { ColumnId_NpcReputation, "Reputation" }, - { ColumnId_NpcRank, "Rank" }, - { ColumnId_NpcGold, "Gold" }, + { ColumnId_NpcRank, "NPC Rank" }, + { ColumnId_Gold, "Gold" }, { ColumnId_NpcPersistence, "Persistent" }, - { ColumnId_RaceAttributes, "Attributes" }, - { ColumnId_RaceMaleValue, "Male" }, - { ColumnId_RaceFemaleValue, "Female" }, + { ColumnId_RaceAttributes, "Race Attributes" }, + { ColumnId_RaceMaleValue, "Male Attrib" }, + { ColumnId_RaceFemaleValue, "Female Attrib" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, - { ColumnId_RaceSkill, "Skills" }, { ColumnId_RaceBonus, "Bonus" }, { ColumnId_Interior, "Interior" }, @@ -315,6 +310,13 @@ namespace CSMWorld { ColumnId_FileDescription, "File Description" }, { ColumnId_Author, "Author" }, + { ColumnId_CreatureAttributes, "Creature Attributes" }, + { ColumnId_AttributeValue, "Attrib Value" }, + { ColumnId_CreatureAttack, "Creature Attack" }, + { ColumnId_MinAttack, "Min Attack" }, + { ColumnId_MaxAttack, "Max Attack" }, + { ColumnId_CreatureMisc, "Creature Misc" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, @@ -536,11 +538,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sAiWanderRepeat[] = - { - "No", "Yes", 0 - }; - static const char *sInfoCondFunc[] = { " ", "Function", "Global", "Local", "Journal", @@ -580,12 +577,10 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_AiWanderRepeat: return sAiWanderRepeat; case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; // FIXME: don't have dynamic value enum delegate, use Display_String for now //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; - case CSMWorld::Columns::ColumnId_RaceSkill: return sSkills; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index d699c67b7..fb25004ec 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -65,7 +65,7 @@ namespace CSMWorld ColumnId_Weight = 50, ColumnId_EnchantmentPoints = 51, ColumnId_Quality = 52, - ColumnId_Ai = 53, + // unused ColumnId_AiHello = 54, ColumnId_AiFlee = 55, ColumnId_AiFight = 56, @@ -102,7 +102,7 @@ namespace CSMWorld ColumnId_OriginalCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, - ColumnId_NoMovement = 90, + // unused ColumnId_Swims = 91, ColumnId_Flies = 92, ColumnId_Walks = 93, @@ -189,7 +189,7 @@ namespace CSMWorld ColumnId_RotX = 174, ColumnId_RotY = 175, ColumnId_RotZ = 176, - ColumnId_Skill = 177, + // unused ColumnId_OwnerGlobal = 178, ColumnId_DefaultProfile = 179, ColumnId_BypassNewGame = 180, @@ -276,22 +276,22 @@ namespace CSMWorld ColumnId_NpcSkills = 249, ColumnId_UChar = 250, ColumnId_NpcMisc = 251, - ColumnId_NpcLevel = 252, + ColumnId_Level = 252, ColumnId_NpcFactionID = 253, - ColumnId_NpcHealth = 254, - ColumnId_NpcMana = 255, - ColumnId_NpcFatigue = 256, + // unused + ColumnId_Mana = 255, + ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, ColumnId_NpcReputation = 258, ColumnId_NpcRank = 259, - ColumnId_NpcGold = 260, + ColumnId_Gold = 260, ColumnId_NpcPersistence = 261, ColumnId_RaceAttributes = 262, ColumnId_RaceMaleValue = 263, ColumnId_RaceFemaleValue = 264, ColumnId_RaceSkillBonus = 265, - ColumnId_RaceSkill = 266, + // unused ColumnId_RaceBonus = 267, ColumnId_Interior = 268, @@ -306,6 +306,16 @@ namespace CSMWorld ColumnId_FileDescription = 276, ColumnId_Author = 277, + ColumnId_MinMagnitude = 278, + ColumnId_MaxMagnitude = 279, + + ColumnId_CreatureAttributes = 280, + ColumnId_AttributeValue = 281, + ColumnId_CreatureAttack = 282, + ColumnId_MinAttack = 283, + ColumnId_MaxAttack = 284, + ColumnId_CreatureMisc = 285, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index b9d5bd7fe..0b1af0e84 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,4 +1,3 @@ - #include "commanddispatcher.hpp" #include diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 5e0cc8f88..d510cd103 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -21,19 +21,31 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI // Replace proxy with actual model mIndex = proxy->mapToSource (index); mModel = proxy->sourceModel(); + } + if (mIndex.parent().isValid()) + { setText ("Modify " + dynamic_cast(mModel)->nestedHeaderData ( mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } else + { setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + } // Remember record state before the modification if (CSMWorld::IdTable *table = dynamic_cast(mModel)) { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); - mRecordStateIndex = table->index(mIndex.row(), stateColumnIndex); + + int rowIndex = mIndex.row(); + if (mIndex.parent().isValid()) + { + rowIndex = mIndex.parent().row(); + } + + mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } } @@ -282,21 +294,24 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Delete row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::DeleteNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.removeRows (mNestedRow, 1, parentIndex); + mModifyParentCommand->redo(); } void CSMWorld::DeleteNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) @@ -310,20 +325,23 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Add row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::AddNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.addNestedRow (parentIndex, mNewRow); + mModifyParentCommand->redo(); } void CSMWorld::AddNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 81c40d0ab..23ffccbd7 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -200,6 +200,9 @@ namespace CSMWorld int mNestedRow; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); @@ -219,6 +222,9 @@ namespace CSMWorld int mParentColumn; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index a92a7ad79..a27ab5094 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1,4 +1,3 @@ - #include "data.hpp" #include @@ -62,7 +61,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(resourcesManager.getVFS()) + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) { int index = 0; @@ -141,7 +140,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceAttributes, ColumnBase::Display_String, + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); @@ -152,7 +151,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceSkill, ColumnBase::Display_RaceSkill)); + new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); @@ -224,9 +223,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); @@ -340,9 +339,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mBodyParts.addColumn (new StringIdColumn); mBodyParts.addColumn (new RecordStateColumn); @@ -352,9 +351,12 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female)); mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); - mBodyParts.addColumn (new MeshTypeColumn); + + int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh; + MeshTypeColumn *meshTypeColumn = new MeshTypeColumn(meshTypeFlags); + mBodyParts.addColumn (meshTypeColumn); mBodyParts.addColumn (new ModelColumn); - mBodyParts.addColumn (new RaceColumn); + mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn)); mSoundGens.addColumn (new StringIdColumn); mSoundGens.addColumn (new RecordStateColumn); @@ -483,7 +485,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mMetaData.addColumn (new FormatColumn); mMetaData.addColumn (new AuthorColumn); mMetaData.addColumn (new FileDescriptionColumn); - + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); @@ -537,14 +539,14 @@ CSMWorld::Data::~Data() delete mReader; } -Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() +boost::shared_ptr CSMWorld::Data::getResourceSystem() { - return &mResourceSystem; + return mResourceSystem; } -const Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() const +boost::shared_ptr CSMWorld::Data::getResourceSystem() const { - return &mResourceSystem; + return mResourceSystem; } const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const @@ -773,11 +775,21 @@ const CSMWorld::IdCollection& CSMWorld::Data::getLand() const return mLand; } +CSMWorld::IdCollection& CSMWorld::Data::getLand() +{ + return mLand; +} + const CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() const { return mLandTextures; } +CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() +{ + return mLandTextures; +} + const CSMWorld::IdCollection& CSMWorld::Data::getSoundGens() const { return mSoundGens; @@ -828,6 +840,12 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const return mMetaData.getRecord (0).get(); } +void CSMWorld::Data::setMetaData (const MetaData& metaData) +{ + Record record (RecordBase::State_ModifiedOnly, 0, &metaData); + mMetaData.setRecord (0, record); +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -880,7 +898,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); } - + return mReader->getRecordCount(); } @@ -939,8 +957,10 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { int index = mLand.load(*mReader, mBase); - if (index!=-1 && !mBase) - mLand.getRecord (index).mModified.mLand->loadData ( + // Load all land data for now. A future optimisation may only load non-base data + // if a suitable mechanism for avoiding race conditions can be established. + if (index!=-1/* && !mBase*/) + mLand.getRecord (index).get().loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 6e651b374..c6623279a 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -113,7 +113,7 @@ namespace CSMWorld std::map > mRefLoadCache; int mReaderIndex; - Resource::ResourceSystem mResourceSystem; + boost::shared_ptr mResourceSystem; std::vector > mReaders; @@ -138,9 +138,9 @@ namespace CSMWorld const VFS::Manager* getVFS() const; - Resource::ResourceSystem* getResourceSystem(); + boost::shared_ptr getResourceSystem(); - const Resource::ResourceSystem* getResourceSystem() const; + boost::shared_ptr getResourceSystem() const; const IdCollection& getGlobals() const; @@ -232,8 +232,12 @@ namespace CSMWorld const IdCollection& getLand() const; + IdCollection& getLand(); + const IdCollection& getLandTextures() const; + IdCollection& getLandTextures(); + const IdCollection& getSoundGens() const; IdCollection& getSoundGens(); @@ -255,6 +259,8 @@ namespace CSMWorld const MetaData& getMetaData() const; + void setMetaData (const MetaData& metaData); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 8ca19f7e9..bd1179cea 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -76,8 +76,15 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) { mIdCollection->setData (index.row(), index.column(), value); + emit dataChanged(index, index); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } diff --git a/apps/opencs/model/world/idtablebase.cpp b/apps/opencs/model/world/idtablebase.cpp index 389f5396e..274446b79 100644 --- a/apps/opencs/model/world/idtablebase.cpp +++ b/apps/opencs/model/world/idtablebase.cpp @@ -1,4 +1,3 @@ - #include "idtablebase.hpp" CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 516644713..fbf7b6cf3 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -1,29 +1,40 @@ - #include "idtableproxymodel.hpp" #include #include "idtablebase.hpp" +namespace +{ + std::string getEnumValue(const std::vector &values, int index) + { + if (index < 0 || index >= static_cast(values.size())) + { + return ""; + } + return values[index]; + } +} + void CSMWorld::IdTableProxyModel::updateColumnMap() { - mColumnMap.clear(); + Q_ASSERT(mSourceModel != NULL); + mColumnMap.clear(); if (mFilter) { std::vector columns = mFilter->getReferencedColumns(); - - const IdTableBase& table = dynamic_cast (*sourceModel()); - for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) - mColumnMap.insert (std::make_pair (*iter, - table.searchColumnIndex (static_cast (*iter)))); + mColumnMap.insert (std::make_pair (*iter, + mSourceModel->searchColumnIndex (static_cast (*iter)))); } } bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { + Q_ASSERT(mSourceModel != NULL); + // It is not possible to use filterAcceptsColumn() and check for // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) // because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can @@ -35,19 +46,40 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI if (!mFilter) return true; - return mFilter->test ( - dynamic_cast (*sourceModel()), sourceRow, mColumnMap); + return mFilter->test (*mSourceModel, sourceRow, mColumnMap); } CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) -: QSortFilterProxyModel (parent) + : QSortFilterProxyModel (parent), + mSourceModel(NULL) { setSortCaseSensitivity (Qt::CaseInsensitive); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { - return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); + Q_ASSERT(mSourceModel != NULL); + + return mapFromSource(mSourceModel->getModelIndex (id, column)); +} + +void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) +{ + QSortFilterProxyModel::setSourceModel(model); + + mSourceModel = dynamic_cast(sourceModel()); + connect(mSourceModel, + SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, + SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); } void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr& filter) @@ -60,11 +92,54 @@ void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr(left.data(ColumnBase::Role_ColumnId).toInt()); + EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id); + if (valuesIt == mEnumColumnCache.end()) + { + if (Columns::hasEnums(id)) + { + valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first; + } + } + + if (valuesIt != mEnumColumnCache.end()) + { + std::string first = getEnumValue(valuesIt->second, left.data().toInt()); + std::string second = getEnumValue(valuesIt->second, right.data().toInt()); + return first < second; + } return QSortFilterProxyModel::lessThan(left, right); } +QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const +{ + Q_ASSERT(mSourceModel != NULL); + + int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id); + return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString(); +} + void CSMWorld::IdTableProxyModel::refreshFilter() { updateColumnMap(); invalidateFilter(); } + +void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +{ + refreshFilter(); + if (!parent.isValid()) + { + emit rowAdded(getRecordId(end).toUtf8().constData()); + } +} + +void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +{ + refreshFilter(); +} + +void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) +{ + refreshFilter(); +} diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index d2a240529..cf31b5c11 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -11,6 +11,8 @@ #include "../filter/node.hpp" +#include "columns.hpp" + namespace CSMWorld { class IdTableProxyModel : public QSortFilterProxyModel @@ -20,6 +22,15 @@ namespace CSMWorld boost::shared_ptr mFilter; std::map mColumnMap; // column ID, column index in this model (or -1) + // Cache of enum values for enum columns (e.g. Modified, Record Type). + // Used to speed up comparisons during the sort by such columns. + typedef std::map > EnumColumnCache; + mutable EnumColumnCache mEnumColumnCache; + + protected: + + IdTableBase *mSourceModel; + private: void updateColumnMap(); @@ -30,15 +41,31 @@ namespace CSMWorld virtual QModelIndex getModelIndex (const std::string& id, int column) const; + virtual void setSourceModel(QAbstractItemModel *model); + void setFilter (const boost::shared_ptr& filter); void refreshFilter(); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; + + QString getRecordId(int sourceRow) const; + + protected slots: + + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + signals: + + void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 9dbe7e002..124a94e8c 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -95,8 +95,15 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, const std::pair& parentAddress(unfoldIndexAddress(index.internalId())); mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); + emit dataChanged(index, index); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.parent().row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } @@ -171,10 +178,10 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par encodedId = this->foldIndexAddress(parent); } - if (row<0 || row>=idCollection()->getSize()) + if (row < 0 || row >= rowCount(parent)) return QModelIndex(); - if (column<0 || column>=idCollection()->getColumns()) + if (column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column, encodedId); // store internal id diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 560be8131..60c613041 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -1,4 +1,3 @@ - #include "infocollection.hpp" #include diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index 6216291d0..4f0b2e752 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -9,16 +9,17 @@ namespace { QString toLower(const QString &str) { - return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toStdString()).c_str()); + return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str()); } } CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) : IdTableProxyModel(parent), mType(type), - mSourceModel(NULL), - mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : - Columns::ColumnId_Journal) + mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : + Columns::ColumnId_Journal), + mInfoColumnIndex(-1), + mLastAddedSourceRow(-1) { Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); } @@ -26,23 +27,18 @@ CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type t void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { IdTableProxyModel::setSourceModel(sourceModel); - mSourceModel = dynamic_cast(sourceModel); + if (mSourceModel != NULL) { - connect(mSourceModel, - SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, - SLOT(modelRowsChanged(const QModelIndex &, int, int))); - connect(mSourceModel, - SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, - SLOT(modelRowsChanged(const QModelIndex &, int, int))); + mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId); mFirstRowCache.clear(); } } bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { + Q_ASSERT(mSourceModel != NULL); + QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); @@ -56,8 +52,10 @@ bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QMod int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const { + Q_ASSERT(mSourceModel != NULL); + int row = currentRow; - int column = mSourceModel->findColumnIndex(mInfoColumnId); + int column = mInfoColumnIndex; QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()); if (mFirstRowCache.contains(info)) @@ -73,7 +71,41 @@ int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const return row; } -void CSMWorld::InfoTableProxyModel::modelRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) { + refreshFilter(); mFirstRowCache.clear(); } + +void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +{ + refreshFilter(); + + if (!parent.isValid()) + { + mFirstRowCache.clear(); + // We can't re-sort the model here, because the topic of the added row isn't set yet. + // Store the row index for using in the first dataChanged() after this row insertion. + mLastAddedSourceRow = end; + } +} + +void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + refreshFilter(); + + if (mLastAddedSourceRow != -1 && + topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) + { + // Now the topic of the last added row is set, + // so we can re-sort the model to ensure the corrent position of this row + int column = sortColumn(); + Qt::SortOrder order = sortOrder(); + sort(mInfoColumnIndex); // Restore the correct position of an added row + sort(column, order); // Restore the original sort order + emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData()); + + // Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion + mLastAddedSourceRow = -1; + } +} diff --git a/apps/opencs/model/world/infotableproxymodel.hpp b/apps/opencs/model/world/infotableproxymodel.hpp index 28d6017b3..51d93f9a1 100644 --- a/apps/opencs/model/world/infotableproxymodel.hpp +++ b/apps/opencs/model/world/infotableproxymodel.hpp @@ -16,25 +16,29 @@ namespace CSMWorld Q_OBJECT UniversalId::Type mType; - IdTableBase *mSourceModel; Columns::ColumnId mInfoColumnId; ///< Contains ID for Topic or Journal ID + int mInfoColumnIndex; + int mLastAddedSourceRow; mutable QHash mFirstRowCache; int getFirstInfoRow(int currentRow) const; ///< Finds the first row with the same topic (journal entry) as in \a currentRow + ///< \a currentRow is a row of the source model. public: InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0); - void setSourceModel(QAbstractItemModel *sourceModel); + virtual void setSourceModel(QAbstractItemModel *sourceModel); protected: virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - private slots: - void modelRowsChanged(const QModelIndex &parent, int start, int end); + protected slots: + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 119e18761..222f9bc02 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -4,25 +4,13 @@ namespace CSMWorld { - - Land::Land() - { - mLand.reset(new ESM::Land()); - } - void Land::load(ESM::ESMReader &esm) { - mLand->load(esm); + ESM::Land::load(esm); std::ostringstream stream; - stream << "#" << mLand->mX << " " << mLand->mY; + stream << "#" << mX << " " << mY; mId = stream.str(); } - - void Land::blank() - { - /// \todo - } - } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e97a2d7dd..22cedb56d 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -2,7 +2,7 @@ #define CSM_WORLD_LAND_H #include -#include + #include namespace CSMWorld @@ -11,18 +11,12 @@ namespace CSMWorld /// /// \todo Add worldspace support to the Land record. /// \todo Add a proper copy constructor (currently worked around using shared_ptr) - struct Land + struct Land : public ESM::Land { - Land(); - - boost::shared_ptr mLand; - std::string mId; /// Loads the metadata and ID void load (ESM::ESMReader &esm); - - void blank(); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 4725866a5..e7772129c 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -9,13 +9,7 @@ namespace CSMWorld { ESM::LandTexture::load(esm); - int plugin = esm.getIndex(); - - std::ostringstream stream; - - stream << mIndex << "_" << plugin; - - mId = stream.str(); + mPluginIndex = esm.getIndex(); } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index b13903186..c0b6eeba9 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -7,13 +7,10 @@ namespace CSMWorld { - /// \brief Wrapper for LandTexture record. Encodes mIndex and the plugin index (obtained from ESMReader) - /// in the ID. - /// - /// \attention The mId field of the ESM::LandTexture struct is not used. + /// \brief Wrapper for LandTexture record, providing info which plugin the LandTexture was loaded from. struct LandTexture : public ESM::LandTexture { - std::string mId; + int mPluginIndex; void load (ESM::ESMReader &esm); }; diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp index 40b8e9519..960fdc9e4 100644 --- a/apps/opencs/model/world/metadata.cpp +++ b/apps/opencs/model/world/metadata.cpp @@ -1,4 +1,3 @@ - #include "metadata.hpp" #include diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index dc5cd2299..002838c67 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -922,7 +922,7 @@ namespace CSMWorld switch (subColIndex) { - case 0: return QString(ESM::Attribute::sAttributeNames[subRowIndex].c_str()); + case 0: return subRowIndex; case 1: return race.mData.mAttributeValues[subRowIndex].mMale; case 2: return race.mData.mAttributeValues[subRowIndex].mFemale; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); diff --git a/apps/opencs/model/world/nestedtableproxymodel.cpp b/apps/opencs/model/world/nestedtableproxymodel.cpp index 052e629aa..edcc7a070 100644 --- a/apps/opencs/model/world/nestedtableproxymodel.cpp +++ b/apps/opencs/model/world/nestedtableproxymodel.cpp @@ -75,10 +75,10 @@ QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QM { assert (!parent.isValid()); - int rows = mMainModel->rowCount(parent); - int columns = mMainModel->columnCount(parent); + int numRows = rowCount(parent); + int numColumns = columnCount(parent); - if (row < 0 || row >= rows || column < 0 || column >= columns) + if (row < 0 || row >= numRows || column < 0 || column >= numColumns) return QModelIndex(); return createIndex(row, column); diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index ef2f4d320..f13a36afc 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -1,4 +1,3 @@ - #include "record.hpp" CSMWorld::RecordBase::~RecordBase() {} diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 13706c950..638f7ec9c 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,4 +1,3 @@ - #include "ref.hpp" #include diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index ff30dafae..f8818807b 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,4 +1,3 @@ - #include "refcollection.hpp" #include diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index c8cca5c3d..da0cc0760 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -296,12 +296,11 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), mType(NULL), - mSoul(NULL), mScale(NULL), mOriginal(NULL), - mCombat(NULL), - mMagic(NULL), - mStealth(NULL) + mAttributes(NULL), + mAttacks(NULL), + mMisc(NULL) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -317,23 +316,20 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con if (column==mColumns.mType) return record.get().mData.mType; - if (column==mColumns.mSoul) - return record.get().mData.mSoul; - if (column==mColumns.mScale) return record.get().mScale; if (column==mColumns.mOriginal) return QString::fromUtf8 (record.get().mOriginal.c_str()); - if (column==mColumns.mCombat) - return static_cast (record.get().mData.mCombat); + if (column==mColumns.mAttributes) + return true; // Required to show nested tables in dialogue subview - if (column==mColumns.mMagic) - return static_cast (record.get().mData.mMagic); + if (column==mColumns.mAttacks) + return true; // Required to show nested tables in dialogue subview - if (column==mColumns.mStealth) - return static_cast (record.get().mData.mStealth); + if (column==mColumns.mMisc) + return true; // Required to show nested items in dialogue subview std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -354,18 +350,10 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa if (column==mColumns.mType) creature.mData.mType = value.toInt(); - else if (column==mColumns.mSoul) - creature.mData.mSoul = value.toInt(); else if (column==mColumns.mScale) creature.mScale = value.toFloat(); else if (column==mColumns.mOriginal) creature.mOriginal = value.toString().toUtf8().constData(); - else if (column==mColumns.mCombat) - creature.mData.mCombat = value.toInt(); - else if (column==mColumns.mMagic) - creature.mData.mMagic = value.toInt(); - else if (column==mColumns.mStealth) - creature.mData.mStealth = value.toInt(); else { std::map::const_iterator iter = @@ -694,18 +682,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; if (subColIndex == 0) - switch (subRowIndex) - { - case 0: return QString("Strength"); - case 1: return QString("Intelligence"); - case 2: return QString("Willpower"); - case 3: return QString("Agility"); - case 4: return QString("Speed"); - case 5: return QString("Endurance"); - case 6: return QString("Personality"); - case 7: return QString("Luck"); - default: return QVariant(); // throw an exception here? - } + return subRowIndex; else if (subColIndex == 1) switch (subRowIndex) { @@ -815,7 +792,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu throw std::runtime_error ("index out of range"); if (subColIndex == 0) - return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); + return subRowIndex; else if (subColIndex == 1) return static_cast(npcStruct.mSkills[subRowIndex]); else @@ -975,6 +952,289 @@ int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, return 1; // fixed at size 1 } +CSMWorld::CreatureAttributesRefIdAdapter::CreatureAttributesRefIdAdapter() +{} + +void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subColIndex == 0) + return subRowIndex; + else if (subColIndex == 1) + switch (subRowIndex) + { + case 0: return creature.mData.mStrength; + case 1: return creature.mData.mIntelligence; + case 2: return creature.mData.mWillpower; + case 3: return creature.mData.mAgility; + case 4: return creature.mData.mSpeed; + case 5: return creature.mData.mEndurance; + case 6: return creature.mData.mPersonality; + case 7: return creature.mData.mLuck; + default: return QVariant(); // throw an exception here? + } + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subColIndex == 1) + switch(subRowIndex) + { + case 0: creature.mData.mStrength = value.toInt(); break; + case 1: creature.mData.mIntelligence = value.toInt(); break; + case 2: creature.mData.mWillpower = value.toInt(); break; + case 3: creature.mData.mAgility = value.toInt(); break; + case 4: creature.mData.mSpeed = value.toInt(); break; + case 5: creature.mData.mEndurance = value.toInt(); break; + case 6: creature.mData.mPersonality = value.toInt(); break; + case 7: creature.mData.mLuck = value.toInt(); break; + default: return; // throw an exception here? + } + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 2; +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 8 attributes + return 8; +} + +CSMWorld::CreatureAttackRefIdAdapter::CreatureAttackRefIdAdapter() +{} + +void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + return subRowIndex + 1; + else if (subColIndex < 3) // 1 or 2 + return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 1 || subColIndex == 2) + creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)] = value.toInt(); + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 3 attacks + return 3; +} + +CSMWorld::CreatureMiscRefIdAdapter::CreatureMiscRefIdAdapter() +{} + +CSMWorld::CreatureMiscRefIdAdapter::~CreatureMiscRefIdAdapter() +{} + +void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + throw std::logic_error ("cannot add a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + throw std::logic_error ("cannot remove a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + throw std::logic_error ("table operation not supported"); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + throw std::logic_error ("table operation not supported"); +} + +QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + switch (subColIndex) + { + case 0: return creature.mData.mLevel; + case 1: return creature.mData.mHealth; + case 2: return creature.mData.mMana; + case 3: return creature.mData.mFatigue; + case 4: return creature.mData.mSoul; + case 5: return creature.mData.mCombat; + case 6: return creature.mData.mMagic; + case 7: return creature.mData.mStealth; + case 8: return creature.mData.mGold; + default: return QVariant(); // throw an exception here? + } +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + switch(subColIndex) + { + case 0: creature.mData.mLevel = value.toInt(); break; + case 1: creature.mData.mHealth = value.toInt(); break; + case 2: creature.mData.mMana = value.toInt(); break; + case 3: creature.mData.mFatigue = value.toInt(); break; + case 4: creature.mData.mSoul = value.toInt(); break; + case 5: creature.mData.mCombat = value.toInt(); break; + case 6: creature.mData.mMagic = value.toInt(); break; + case 7: creature.mData.mStealth = value.toInt(); break; + case 8: creature.mData.mGold = value.toInt(); break; + default: return; // throw an exception here? + } + + record.setModified (creature); +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 9; // Level, Health, Mana, Fatigue, Soul, Combat, Magic, Steath, Gold +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 1; // fixed at size 1 +} + CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) : EnchantableColumns (columns) {} diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 869996da5..53da63806 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -2,7 +2,6 @@ #define CSM_WOLRD_REFIDADAPTERIMP_H #include -#include #include @@ -480,7 +479,6 @@ namespace CSMWorld struct ActorColumns : public NameColumns { - const RefIdColumn *mHasAi; const RefIdColumn *mHello; const RefIdColumn *mFlee; const RefIdColumn *mFight; @@ -525,9 +523,6 @@ namespace CSMWorld const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - if (column==mActors.mHello) return record.get().mAiData.mHello; @@ -569,9 +564,7 @@ namespace CSMWorld data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mActors.mHasAi) - record2.mHasAI = value.toInt(); - else if (column==mActors.mHello) + if (column==mActors.mHello) record2.mAiData.mHello = value.toInt(); else if (column==mActors.mFlee) record2.mAiData.mFlee = value.toInt(); @@ -697,12 +690,11 @@ namespace CSMWorld { std::map mFlags; const RefIdColumn *mType; - const RefIdColumn *mSoul; const RefIdColumn *mScale; const RefIdColumn *mOriginal; - const RefIdColumn *mCombat; - const RefIdColumn *mMagic; - const RefIdColumn *mStealth; + const RefIdColumn *mAttributes; + const RefIdColumn *mAttacks; + const RefIdColumn *mMisc; CreatureColumns (const ActorColumns& actorColumns); }; @@ -939,6 +931,97 @@ namespace CSMWorld virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; + class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttributesRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttackRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase + { + CreatureMiscRefIdAdapter (const CreatureMiscRefIdAdapter&); + CreatureMiscRefIdAdapter& operator= (const CreatureMiscRefIdAdapter&); + + public: + + CreatureMiscRefIdAdapter (); + virtual ~CreatureMiscRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + template class EffectsListAdapter; @@ -1567,7 +1650,7 @@ namespace CSMWorld return QVariant(); case 5: // wander repeat if (content.mType == ESM::AI_Wander) - return content.mWander.mShouldRepeat; + return content.mWander.mShouldRepeat != 0; else return QVariant(); case 6: // activate name diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 5495926b4..d7b7cedfa 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -93,9 +93,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); EnchantableColumns enchantableColumns (inventoryColumns); @@ -113,8 +113,6 @@ CSMWorld::RefIdCollection::RefIdCollection() ActorColumns actorsColumns (nameColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Ai, ColumnBase::Display_Boolean)); - actorsColumns.mHasAi = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer)); actorsColumns.mHello = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer)); @@ -198,7 +196,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderIdle, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_YesNo)); + new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( @@ -297,21 +295,10 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType)); creatureColumns.mType = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_SoulPoints, ColumnBase::Display_Integer)); - creatureColumns.mSoul = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float)); creatureColumns.mScale = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_Creature)); creatureColumns.mOriginal = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_CombatState, ColumnBase::Display_Integer)); - creatureColumns.mCombat = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_MagicState, ColumnBase::Display_Integer)); - creatureColumns.mMagic = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_StealthState, ColumnBase::Display_Integer)); - creatureColumns.mStealth = &mColumns.back(); static const struct { @@ -321,7 +308,6 @@ CSMWorld::RefIdCollection::RefIdCollection() { { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, - { Columns::ColumnId_NoMovement, ESM::Creature::None }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, @@ -351,6 +337,59 @@ CSMWorld::RefIdCollection::RefIdCollection() creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttributes, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttributes = &mColumns.back(); + std::map creaAttrMap; + creaAttrMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttributesRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaAttrMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); + + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttack, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttacks = &mColumns.back(); + std::map attackMap; + attackMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttackRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attackMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); + + // Nested list + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureMisc, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); + creatureColumns.mMisc = &mColumns.back(); + std::map creaMiscMap; + creaMiscMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureMiscRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaMiscMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_Sound)); const RefIdColumn *openSound = &mColumns.back(); @@ -381,7 +420,7 @@ CSMWorld::RefIdCollection::RefIdCollection() { Columns::ColumnId_Portable, ESM::Light::Carry }, { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, { Columns::ColumnId_Flickering, ESM::Light::Flicker }, - { Columns::ColumnId_SlowFlickering, ESM::Light::Flicker }, + { Columns::ColumnId_SlowFlickering, ESM::Light::FlickerSlow }, { Columns::ColumnId_Pulsing, ESM::Light::Pulse }, { Columns::ColumnId_SlowPulsing, ESM::Light::PulseSlow }, { Columns::ColumnId_Fire, ESM::Light::Fire }, @@ -440,7 +479,7 @@ CSMWorld::RefIdCollection::RefIdCollection() attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -452,7 +491,7 @@ CSMWorld::RefIdCollection::RefIdCollection() skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_SkillImpact, CSMWorld::ColumnBase::Display_SkillImpact, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -464,15 +503,15 @@ CSMWorld::RefIdCollection::RefIdCollection() miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcHealth, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcMana, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcFatigue, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( @@ -480,7 +519,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcGold, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcPersistence, CSMWorld::ColumnBase::Display_Boolean)); @@ -914,3 +953,8 @@ const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdap } throw std::runtime_error("No such column in the nestedadapters"); } + +void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const +{ + mData.copyTo (index, target.mData); +} diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 4d511d12d..de5a709c6 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -138,6 +138,7 @@ namespace CSMWorld void save (int index, ESM::ESMWriter& writer) const; const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( + void copyTo (int index, RefIdCollection& target) const; }; } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7f5c25f36..7696c3763 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -1,7 +1,7 @@ - #include "refiddata.hpp" #include +#include #include @@ -345,7 +345,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati return mStatics; } -void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) { std::map::iterator iter = mRecordContainers.find (type); @@ -358,3 +358,16 @@ void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::U mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); } + +void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const +{ + LocalIndex localIndex = globalToLocalIndex (index); + + RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; + + std::string id = source->getId (localIndex.first); + + std::auto_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); + + target.insertRecord (*newRecord, localIndex.second, id); +} diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 85d16a6eb..8909ae4fb 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -210,7 +210,8 @@ namespace CSMWorld void erase (int index, int count); - void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); + void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, + const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; @@ -252,11 +253,9 @@ namespace CSMWorld const RefIdDataContainer& getProbes() const; const RefIdDataContainer& getRepairs() const; const RefIdDataContainer& getStatics() const; + + void copyTo (int index, RefIdData& target) const; }; } #endif - - - - diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 42bde9c81..10c67c909 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 4dd480e77..b31e211ee 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -1,4 +1,3 @@ - #include "resources.hpp" #include diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 3e2f72d93..2ec661cb1 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -1,4 +1,3 @@ - #include "resourcesmanager.hpp" #include diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 2cd44781a..5227ec3e6 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -1,4 +1,3 @@ - #include "resourcetable.hpp" #include diff --git a/apps/opencs/model/world/scope.cpp b/apps/opencs/model/world/scope.cpp index 6e4ce4c02..b026c34c3 100644 --- a/apps/opencs/model/world/scope.cpp +++ b/apps/opencs/model/world/scope.cpp @@ -1,4 +1,3 @@ - #include "scope.hpp" #include diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index a8c2c9452..f644ad37a 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -1,4 +1,3 @@ - #include "scriptcontext.hpp" #include @@ -40,8 +39,6 @@ char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const { - /// \todo invalidate locals cache on change to scripts - std::string id2 = Misc::StringUtils::lowerCase (id); int index = mData.getScripts().searchId (id2); @@ -121,3 +118,18 @@ void CSMWorld::ScriptContext::clear() mIdsUpdated = false; mLocals.clear(); } + +bool CSMWorld::ScriptContext::clearLocals (const std::string& script) +{ + std::map::iterator iter = + mLocals.find (Misc::StringUtils::lowerCase (script)); + + if (iter!=mLocals.end()) + { + mLocals.erase (iter); + mIdsUpdated = false; + return true; + } + + return false; +} diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 29ee42645..2cd59f070 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -46,6 +46,9 @@ namespace CSMWorld void clear(); ///< Remove all cached data. + + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); }; } diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 06d252435..a42e97561 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,4 +1,3 @@ - #ifndef TABLEMIMEDATA_H #define TABLEMIMEDATA_H diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 73d893a26..77db2be10 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -1,4 +1,3 @@ - #include "universalid.hpp" #include diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 6571ad7c8..620d853ac 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -1,4 +1,3 @@ - #include "adjusterwidget.hpp" #include @@ -64,6 +63,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) QString message; mValid = (!name.isEmpty()); + bool warning = false; if (!mValid) { @@ -106,13 +106,14 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { /// \todo add an user setting to make this an error. message += "

    A file with the same name already exists. If you continue, it will be overwritten."; + warning = true; } } } mMessage->setText (message); mIcon->setPixmap (style()->standardIcon ( - mValid ? QStyle::SP_MessageBoxInformation : QStyle::SP_MessageBoxWarning). + mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical). pixmap (QSize (16, 16))); emit stateChanged (mValid); diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index f18fe695a..110d561c1 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -1,4 +1,3 @@ - #include "filewidget.hpp" #include @@ -56,3 +55,11 @@ void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) { mType->setVisible(visible); } + +void CSVDoc::FileWidget::setName (const std::string& text) +{ + QString text2 = QString::fromUtf8 (text.c_str()); + + mInput->setText (text2); + textChanged (text2); +} diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index ff09d71a3..ab06f37f1 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -3,6 +3,8 @@ #include +#include + class QLabel; class QString; class QLineEdit; @@ -29,6 +31,8 @@ namespace CSVDoc void extensionLabelIsVisible(bool visible); + void setName (const std::string& text); + private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp index b88381385..f0d9655dd 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.cpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -1,4 +1,3 @@ - #include "globaldebugprofilemenu.hpp" #include diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 30235d0f5..713295d70 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 32b483728..b3e2a4f63 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -1,4 +1,3 @@ - #include "newgame.hpp" #include diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp index 95cbf012d..e5c1e7b89 100644 --- a/apps/opencs/view/doc/operation.cpp +++ b/apps/opencs/view/doc/operation.cpp @@ -19,6 +19,7 @@ void CSVDoc::Operation::updateLabel (int threads) case CSMDoc::State_Saving: name = "saving"; break; case CSMDoc::State_Verifying: name = "verifying"; break; case CSMDoc::State_Searching: name = "searching"; break; + case CSMDoc::State_Merging: name = "merging"; break; } std::ostringstream stream; @@ -122,7 +123,7 @@ void CSVDoc::Operation::setBarColor (int type) bottomColor = "#9ECB2D"; //green gloss break; - case CSMDoc::State_Compiling: + case CSMDoc::State_Merging: topColor = "#F3E2C7"; midTopColor = "#C19E67"; diff --git a/apps/opencs/view/doc/runlogsubview.cpp b/apps/opencs/view/doc/runlogsubview.cpp index 129396999..2b7182228 100644 --- a/apps/opencs/view/doc/runlogsubview.cpp +++ b/apps/opencs/view/doc/runlogsubview.cpp @@ -1,4 +1,3 @@ - #include "runlogsubview.hpp" #include diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 58a46c603..a9d697f1c 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -1,4 +1,3 @@ - #include "startup.hpp" #include diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp index 3137f7e32..82a8aeb05 100644 --- a/apps/opencs/view/doc/subviewfactory.cpp +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -1,4 +1,3 @@ - #include "subviewfactory.hpp" #include diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index c4abb2622..ea11bb0f9 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -66,6 +66,10 @@ void CSVDoc::View::setupFileMenu() connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); file->addAction (mVerify); + mMerge = new QAction (tr ("Merge"), this); + connect (mMerge, SIGNAL (triggered()), this, SLOT (merge())); + file->addAction (mMerge); + QAction *loadErrors = new QAction (tr ("Load Error Log"), this); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); file->addAction (loadErrors); @@ -393,6 +397,9 @@ void CSVDoc::View::updateActions() mGlobalDebugProfileMenu->updateActions (running); mStopDebug->setEnabled (running); + + mMerge->setEnabled (mDocument->getContentFiles().size()>1 && + !(mDocument->getState() & CSMDoc::State_Merging)); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) @@ -471,6 +478,7 @@ void CSVDoc::View::updateDocumentState() static const int operations[] = { CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 // end marker }; @@ -968,3 +976,8 @@ void CSVDoc::View::updateScrollbar() else mSubViewWindow.setMinimumWidth(0); } + +void CSVDoc::View::merge() +{ + emit mergeDocument (mDocument); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 7f4255f8f..2dd717440 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -43,6 +43,7 @@ namespace CSVDoc QAction *mVerify; QAction *mShowStatusBar; QAction *mStopDebug; + QAction *mMerge; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; @@ -114,9 +115,6 @@ namespace CSVDoc Operations *getOperations() const; - /// Function called by view manager when user preferences are updated - void updateEditorSetting (const QString &, const QString &); - signals: void newGameRequest(); @@ -129,6 +127,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); @@ -237,6 +237,8 @@ namespace CSVDoc void closeRequest (SubView *subView); void moveScrollBarToEnd(int min, int max); + + void merge(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 7aec001cf..ca4ad2d00 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -1,4 +1,3 @@ - #include "viewmanager.hpp" #include @@ -97,16 +96,14 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_MeshType, CSMWorld::Columns::ColumnId_MeshType, false }, { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, - { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, true }, + { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, { CSMWorld::ColumnBase::Display_SkillImpact, CSMWorld::Columns::ColumnId_SkillImpact, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, - { CSMWorld::ColumnBase::Display_YesNo, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, - { CSMWorld::ColumnBase::Display_RaceSkill, CSMWorld::Columns::ColumnId_RaceSkill, true }, }; for (std::size_t i=0; i diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 7a42ef0a5..c6c6cc6cc 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,4 +1,3 @@ - #include "filterbox.hpp" #include diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 97490d508..2bf589215 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,4 +1,3 @@ - #include "recordfilterbox.hpp" #include diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 063413248..28a48cfbd 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,4 +1,3 @@ - #include "cell.hpp" #include @@ -32,7 +31,7 @@ bool CSVRender::Cell::addObjects (int start, int end) bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); - + for (int i=start; i<=end; ++i) { std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); @@ -68,15 +67,16 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st int landIndex = land.searchId(mId); if (landIndex != -1) { - const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); - if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) - { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); - mTerrain->loadCell(esmLand->mX, - esmLand->mY); + const ESM::Land& esmLand = land.getRecord(mId).get(); - mX = esmLand->mX; - mY = esmLand->mY; + if (esmLand.getLandData (ESM::Land::DATA_VHGT)) + { + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); + mTerrain->loadCell(esmLand.mX, + esmLand.mY); + + mX = esmLand.mX; + mY = esmLand.mY; } } } diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index 9361030a3..8a99ba049 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -1,4 +1,3 @@ - #include "editmode.hpp" #include "worldspacewidget.hpp" diff --git a/apps/opencs/view/render/lightingbright.cpp b/apps/opencs/view/render/lightingbright.cpp index 00c4fb815..07900d6b0 100644 --- a/apps/opencs/view/render/lightingbright.cpp +++ b/apps/opencs/view/render/lightingbright.cpp @@ -1,4 +1,3 @@ - #include "lightingbright.hpp" #include diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index ea27b1432..f05e311f9 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "../../model/world/data.hpp" #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" @@ -116,9 +118,14 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) -: mData (data), mBaseNode(0), mParentNode(parentNode), mResourceSystem(data.getResourceSystem()), mForceBaseToZero (forceBaseToZero) +: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero) { mBaseNode = new osg::PositionAttitudeTransform; + mOutline = new osgFX::Scribe; + mOutline->addChild(mBaseNode); + + mBaseNode->setUserData(new ObjectHolder(this)); + parentNode->addChild(mBaseNode); // 0x1 reserved for separating cull and update visitors @@ -145,6 +152,23 @@ CSVRender::Object::~Object() mParentNode->removeChild(mBaseNode); } +void CSVRender::Object::setSelected(bool selected) +{ + mSelected = selected; + + mParentNode->removeChild(mOutline); + mParentNode->removeChild(mBaseNode); + if (selected) + mParentNode->addChild(mOutline); + else + mParentNode->addChild(mBaseNode); +} + +bool CSVRender::Object::getSelected() const +{ + return mSelected; +} + bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 9f411ffc6..9b3749069 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -6,6 +6,7 @@ #include #include +#include class QModelIndex; @@ -16,6 +17,11 @@ namespace osg class Group; } +namespace osgFX +{ + class Scribe; +} + namespace Resource { class ResourceSystem; @@ -29,12 +35,29 @@ namespace CSMWorld namespace CSVRender { + + class Object; + + // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing a ray query + class ObjectHolder : public osg::Referenced + { + public: + ObjectHolder(Object* obj) + : mObject(obj) + { + } + + Object* mObject; + }; + class Object { const CSMWorld::Data& mData; std::string mReferenceId; std::string mReferenceableId; osg::ref_ptr mBaseNode; + osg::ref_ptr mOutline; + bool mSelected; osg::Group* mParentNode; Resource::ResourceSystem* mResourceSystem; bool mForceBaseToZero; @@ -68,6 +91,11 @@ namespace CSVRender ~Object(); + /// Mark the object as selected, selected objects show an outline effect + void setSelected(bool selected); + + bool getSelected() const; + /// \return Did this call result in a modification of the visual representation of /// this object? bool referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 2b53483ad..b0c1e74a9 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "pagedworldspacewidget.hpp" #include @@ -171,7 +170,10 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { - osg::Vec3d position = mView->getCamera()->getViewMatrix().getTrans(); + osg::Vec3d eye, center, up; + mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d position = eye; + std::ostringstream stream; stream diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index f0cbc939a..a03b277d3 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,4 +1,3 @@ - #include "previewwidget.hpp" #include @@ -8,7 +7,7 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) -: SceneWidget (data.getResourceSystem()->getSceneManager(), parent), mData (data), mObject(data, mRootNode, id, referenceable) +: SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable) { mView->setCameraManipulator(new osgGA::TrackballManipulator); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 208a7a5b7..fb3bcb3c3 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "../widget/scenetoolmode.hpp" #include "../../model/settings/usersettings.hpp" @@ -51,9 +52,13 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) osg::ref_ptr window = new osgQt::GraphicsWindowQt(traits.get()); QLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(window->getGLWidget()); setLayout(layout); + // Pass events through this widget first + window->getGLWidget()->installEventFilter(this); + mView->getCamera()->setGraphicsContext(window); mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); @@ -92,6 +97,24 @@ void RenderWidget::setVisibilityMask(int mask) mView->getCamera()->setCullMask(mask<<1); } +bool RenderWidget::eventFilter(QObject* obj, QEvent* event) +{ + // handle event in this widget, is there a better way to do this? + if (event->type() == QEvent::MouseButtonPress) + mousePressEvent(static_cast(event)); + if (event->type() == QEvent::MouseButtonRelease) + mouseReleaseEvent(static_cast(event)); + if (event->type() == QEvent::MouseMove) + mouseMoveEvent(static_cast(event)); + if (event->type() == QEvent::KeyPress) + keyPressEvent(static_cast(event)); + if (event->type() == QEvent::KeyRelease) + keyReleaseEvent(static_cast(event)); + + // Always pass the event on to GLWidget, i.e. to OSG event queue + return QObject::eventFilter(obj, event); +} + // -------------------------------------------------- CompositeViewer::CompositeViewer() @@ -132,9 +155,9 @@ void CompositeViewer::update() // --------------------------------------------------- -SceneWidget::SceneWidget(Resource::SceneManager* sceneManager, QWidget *parent, Qt::WindowFlags f) +SceneWidget::SceneWidget(boost::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f) : RenderWidget(parent, f) - , mSceneManager(sceneManager) + , mResourceSystem(resourceSystem) , mLighting(NULL) , mHasDefaultAmbient(false) { @@ -142,12 +165,16 @@ SceneWidget::SceneWidget(Resource::SceneManager* sceneManager, QWidget *parent, mView->setLightingMode(osgViewer::View::NO_LIGHT); setLighting(&mLightingDay); + + /// \todo make shortcut configurable + QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); + connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); } SceneWidget::~SceneWidget() { // Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects - mSceneManager->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); + mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); } void SceneWidget::setLighting(Lighting *lighting) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index c269f355d..63da01a45 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "lightingday.hpp" #include "lightingnight.hpp" #include "lightingbright.hpp" @@ -13,7 +15,7 @@ namespace Resource { - class SceneManager; + class ResourceSystem; } namespace osg @@ -43,6 +45,8 @@ namespace CSVRender void setVisibilityMask(int mask); + bool eventFilter(QObject *, QEvent *); + protected: osg::ref_ptr mView; @@ -57,7 +61,7 @@ namespace CSVRender { Q_OBJECT public: - SceneWidget(Resource::SceneManager* sceneManager, QWidget* parent = 0, Qt::WindowFlags f = 0); + SceneWidget(boost::shared_ptr resourceSystem, QWidget* parent = 0, Qt::WindowFlags f = 0); virtual ~SceneWidget(); CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); @@ -73,7 +77,7 @@ namespace CSVRender void setAmbient(const osg::Vec4f& ambient); - Resource::SceneManager* mSceneManager; + boost::shared_ptr mResourceSystem; Lighting* mLighting; @@ -86,6 +90,10 @@ namespace CSVRender private slots: void selectLightingMode (const std::string& mode); + + signals: + + void focusToolbarRequest(); }; diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index fe302cef1..860ed0080 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -9,7 +9,7 @@ namespace CSVRender { } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { std::ostringstream stream; stream << "#" << cellX << " " << cellY; @@ -20,19 +20,26 @@ namespace CSVRender if (index == -1) return NULL; - ESM::Land* land = mData.getLand().getRecord(index).get().mLand.get(); + const ESM::Land& land = mData.getLand().getRecord(index).get(); int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(mask)) - land->loadData(mask); - return land; + land.loadData (mask); + return &land; } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - std::ostringstream stream; - stream << index << "_" << plugin; + int numRecords = mData.getLandTextures().getSize(); - return &mData.getLandTextures().getRecord(stream.str()).get(); + for (int i=0; imIndex == index && ltex->mPluginIndex == plugin) + return ltex; + } + + std::stringstream error; + error << "Can't find LandTexture " << index << " from plugin " << plugin; + throw std::runtime_error(error.str()); } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 97782ad17..16b0f3ec7 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -18,7 +18,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 8e6e72cba..3e1733c81 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "unpagedworldspacewidget.hpp" #include @@ -167,7 +166,9 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { - osg::Vec3d position = mView->getCamera()->getViewMatrix().getTrans(); + osg::Vec3d eye, center, up; + mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d position = eye; std::ostringstream stream; diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 3a70b7844..a1f0f6bf2 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,7 +1,7 @@ - #include "worldspacewidget.hpp" #include +#include #include #include @@ -13,6 +13,8 @@ #include #include +#include + #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" @@ -20,11 +22,12 @@ #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" +#include "object.hpp" #include "elements.hpp" #include "editmode.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (document.getData().getResourceSystem()->getSceneManager(), parent), mSceneElements(0), mRun(0), mDocument(document), +: SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), mInteractionMask (0) { setAcceptDrops(true); @@ -371,11 +374,49 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) { - if(event->buttons() & Qt::RightButton) + if (event->button() != Qt::RightButton) + return; + + // (0,0) is considered the lower left corner of an OpenGL window + int x = event->x(); + int y = height() - event->y(); + + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); + + visitor.setTraversalMask(getInteractionMask() << 1); + + mView->getCamera()->accept(visitor); + + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) { - //mMouse->mousePressEvent(event); + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + + // reject back-facing polygons + osg::Vec3f normal = intersection.getWorldIntersectNormal(); + normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); + if (normal.z() < 0) + continue; + + for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + { + osg::Node* node = *it; + if (CSVRender::ObjectHolder* holder = dynamic_cast(node->getUserData())) + { + // hit an Object, toggle its selection state + CSVRender::Object* obj = holder->mObject; + obj->setSelected(!obj->getSelected()); + return; + } + } + + // must be terrain, report coordinates + std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; + return; } - //SceneWidget::mousePressEvent(event); } void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp new file mode 100644 index 000000000..566a5ee06 --- /dev/null +++ b/apps/opencs/view/tools/merge.cpp @@ -0,0 +1,142 @@ + +#include "merge.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/doc/documentmanager.hpp" + +#include "../doc/filewidget.hpp" +#include "../doc/adjusterwidget.hpp" + +void CSVTools::Merge::keyPressEvent (QKeyEvent *event) +{ + if (event->key()==Qt::Key_Escape) + { + event->accept(); + cancel(); + } + else + QWidget::keyPressEvent (event); +} + +CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) +: QWidget (parent), mDocument (0), mDocumentManager (documentManager) +{ + setWindowTitle ("Merge Content Files into a new Game File"); + + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout (mainLayout); + + QSplitter *splitter = new QSplitter (Qt::Horizontal, this); + + mainLayout->addWidget (splitter, 1); + + // left panel (files to be merged) + QWidget *left = new QWidget (this); + left->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (left); + + QVBoxLayout *leftLayout = new QVBoxLayout; + left->setLayout (leftLayout); + + leftLayout->addWidget (new QLabel ("Files to be merged", this)); + + mFiles = new QListWidget (this); + leftLayout->addWidget (mFiles, 1); + + // right panel (new game file) + QWidget *right = new QWidget (this); + right->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (right); + + QVBoxLayout *rightLayout = new QVBoxLayout; + rightLayout->setAlignment (Qt::AlignTop); + right->setLayout (rightLayout); + + rightLayout->addWidget (new QLabel ("New game file", this)); + + mNewFile = new CSVDoc::FileWidget (this); + mNewFile->setType (false); + mNewFile->extensionLabelIsVisible (true); + rightLayout->addWidget (mNewFile); + + mAdjuster = new CSVDoc::AdjusterWidget (this); + + rightLayout->addWidget (mAdjuster); + + connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)), + mAdjuster, SLOT (setName (const QString&, bool))); + connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); + + // buttons + QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + + connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel())); + + mOkay = new QPushButton ("Merge", this); + connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); + mOkay->setDefault (true); + buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); + + mainLayout->addWidget (buttons); +} + +void CSVTools::Merge::configure (CSMDoc::Document *document) +{ + mDocument = document; + + mNewFile->setName (""); + + // content files + while (mFiles->count()) + delete mFiles->takeItem (0); + + std::vector files = document->getContentFiles(); + + for (std::vector::const_iterator iter (files.begin()); + iter!=files.end(); ++iter) + mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str())); +} + +void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData) +{ + mAdjuster->setLocalData (localData); +} + +CSMDoc::Document *CSVTools::Merge::getDocument() const +{ + return mDocument; +} + +void CSVTools::Merge::cancel() +{ + mDocument = 0; + hide(); +} + +void CSVTools::Merge::accept() +{ + if ((mDocument->getState() & CSMDoc::State_Merging)==0) + { + std::vector< boost::filesystem::path > files (1, mAdjuster->getPath()); + + std::auto_ptr target ( + mDocumentManager.makeDocument (files, files[0], true)); + + mDocument->runMerge (target); + + hide(); + } +} + +void CSVTools::Merge::stateChanged (bool valid) +{ + mOkay->setEnabled (valid); +} diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp new file mode 100644 index 000000000..d4ed7e34b --- /dev/null +++ b/apps/opencs/view/tools/merge.hpp @@ -0,0 +1,61 @@ +#ifndef CSV_TOOLS_REPORTTABLE_H +#define CSV_TOOLS_REPORTTABLE_H + +#include + +#include + +class QPushButton; +class QListWidget; + +namespace CSMDoc +{ + class Document; + class DocumentManager; +} + +namespace CSVDoc +{ + class FileWidget; + class AdjusterWidget; +} + +namespace CSVTools +{ + class Merge : public QWidget + { + Q_OBJECT + + CSMDoc::Document *mDocument; + QPushButton *mOkay; + QListWidget *mFiles; + CSVDoc::FileWidget *mNewFile; + CSVDoc::AdjusterWidget *mAdjuster; + CSMDoc::DocumentManager& mDocumentManager; + + void keyPressEvent (QKeyEvent *event); + + public: + + Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = 0); + + /// Configure dialogue for a new merge + void configure (CSMDoc::Document *document); + + void setLocalData (const boost::filesystem::path& localData); + + CSMDoc::Document *getDocument() const; + + public slots: + + void cancel(); + + private slots: + + void accept(); + + void stateChanged (bool valid); + }; +} + +#endif diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index e29447f25..a7316359e 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -1,4 +1,3 @@ - #include "reportsubview.hpp" #include "reporttable.hpp" diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 550c53969..d4cecc971 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -1,4 +1,3 @@ - #include "reporttable.hpp" #include diff --git a/apps/opencs/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp index 1307c1aab..d98044760 100644 --- a/apps/opencs/view/tools/searchbox.cpp +++ b/apps/opencs/view/tools/searchbox.cpp @@ -1,4 +1,3 @@ - #include "searchbox.hpp" #include diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index dc670af40..d3fdbbf5d 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -1,4 +1,3 @@ - #include "searchsubview.hpp" #include @@ -16,7 +15,7 @@ void CSVTools::SearchSubView::replace (bool selection) { if (mLocked) return; - + std::vector indices = mTable->getReplaceIndices (selection); std::string replace = mSearchBox.getReplaceText(); @@ -29,7 +28,7 @@ void CSVTools::SearchSubView::replace (bool selection) CSMTools::Search search (mSearch); CSMWorld::IdTableBase *currentTable = 0; - + // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) @@ -46,7 +45,7 @@ void CSVTools::SearchSubView::replace (bool selection) search.configure (table); currentTable = table; } - + std::string hint = model.getHint (*iter); if (search.verify (mDocument, table, id, hint)) @@ -63,7 +62,7 @@ void CSVTools::SearchSubView::replace (bool selection) void CSVTools::SearchSubView::showEvent (QShowEvent *event) { CSVDoc::SubView::showEvent (event); - mSearchBox.focus(); + mSearchBox.focus(); } CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) @@ -71,25 +70,23 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (&mSearchBox); - + layout->addWidget (mTable = new ReportTable (document, id, true), 2); QWidget *widget = new QWidget; - + widget->setLayout (layout); setWidget (widget); stateChanged (document.getState(), &document); - + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); - + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (stateChanged (int, CSMDoc::Document *))); @@ -124,7 +121,7 @@ void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) mSearch = search; mSearch.setPadding (paddingBefore, paddingAfter); - + mTable->clear(); mDocument.runSearch (getUniversalId(), mSearch); } diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index 8a343ebe8..8c3d6d50e 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" diff --git a/apps/opencs/view/widget/droplineedit.cpp b/apps/opencs/view/widget/droplineedit.cpp index 1df598cb8..2ca306461 100644 --- a/apps/opencs/view/widget/droplineedit.cpp +++ b/apps/opencs/view/widget/droplineedit.cpp @@ -35,7 +35,7 @@ void CSVWidget::DropLineEdit::dropEvent(QDropEvent *event) if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, mDropType); - setText(QString::fromUtf8(id.getId().c_str())); + setText(QString::fromUtf8(id.getId().c_str())); emit tableMimeDataDropped(id, CSVWorld::DragDropUtils::getTableMimeData(*event)->getDocumentPtr()); } } diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 56896b422..7c62f6bb1 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -1,4 +1,3 @@ - #include "modebutton.hpp" CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QWidget *parent) diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 1baeb7ca2..424aaf68a 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -1,4 +1,3 @@ - #include "pushbutton.hpp" #include diff --git a/apps/opencs/view/widget/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp index b8e9f895f..796b98567 100644 --- a/apps/opencs/view/widget/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -1,4 +1,3 @@ - #include "scenetool.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp index f7023b31f..b2e988dc9 100644 --- a/apps/opencs/view/widget/scenetoolbar.cpp +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -1,4 +1,3 @@ - #include "scenetoolbar.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 39e051c48..9f963873c 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -1,4 +1,3 @@ - #include "scenetoolmode.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 4c9eb676e..1e2d44e7a 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -1,4 +1,3 @@ - #include "scenetoolrun.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index 07c448e45..d7251882a 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 313e519cb..e0431476e 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle2.hpp" #include diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index c7d909f4c..2a710a940 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -1,4 +1,3 @@ - #include "cellcreator.hpp" #include diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index 7a8c8d48f..7a93339c5 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -1,4 +1,3 @@ - #include "creator.hpp" #include diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 3d451ed2d..7c6fb2e81 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -1,4 +1,3 @@ - #include "dialoguecreator.hpp" #include diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index ed50b81cd..7402f62ee 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -31,6 +31,7 @@ #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" +#include "../../model/settings/usersettings.hpp" #include "../widget/coloreditor.hpp" #include "../widget/droplineedit.hpp" @@ -66,7 +67,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo CSMWorld::Columns::ColumnId columnId = static_cast ( mTable->getColumnId (index.column())); - + if (QVariant::String == v.type()) { label->setText(v.toString()); @@ -75,7 +76,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo { int data = v.toInt(); std::vector enumNames (CSMWorld::Columns::getEnums (columnId)); - + label->setText(QString::fromUtf8(enumNames.at(data).c_str())); } else @@ -103,7 +104,9 @@ QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - return new QLabel(parent); + QLabel *label = new QLabel(parent); + label->setTextInteractionFlags (Qt::TextSelectableByMouse); + return label; } /* @@ -324,11 +327,11 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di Q_ASSERT(mWidget != NULL); Q_ASSERT(CSMWorld::ColumnBase::isId(display)); Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); - + mWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(mWidget, - SIGNAL(customContextMenuRequested(const QPoint &)), - this, + connect(mWidget, + SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(showContextMenu(const QPoint &))); mEditIdAction = new QAction(this); @@ -352,7 +355,7 @@ void CSVWorld::IdContextMenu::excludeId(const std::string &id) QString CSVWorld::IdContextMenu::getWidgetValue() const { - QLineEdit *lineEdit = qobject_cast(mWidget); + QLineEdit *lineEdit = qobject_cast(mWidget); QLabel *label = qobject_cast(mWidget); QString value = ""; @@ -411,7 +414,7 @@ void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { removeEditIdActionFromMenu(); } - + if (!mContextMenu->actions().isEmpty()) { mContextMenu->exec(mWidget->mapToGlobal(pos)); @@ -569,8 +572,6 @@ void CSVWorld::EditWidget::remake(int row) table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setEnabled(false); } - else - table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); @@ -588,9 +589,9 @@ void CSVWorld::EditWidget::remake(int row) tablesLayout->addWidget(label); tablesLayout->addWidget(table); - connect(table, - SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), - this, + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) @@ -741,13 +742,15 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa mMainLayout = new QVBoxLayout(mainWidget); setWidget (mainWidget); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + mEditWidget = new EditWidget(mainWidget, - mTable->getModelIndex(getUniversalId().getId(), 0).row(), mTable, mCommandDispatcher, document, false); + mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, mCommandDispatcher, document, false); mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - dataChanged(mTable->getModelIndex (getUniversalId().getId(), 0)); + dataChanged(mTable->getModelIndex (getUniversalId().getId(), idColumn)); connect(mEditWidget, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), @@ -760,8 +763,9 @@ void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) if (!mEditWidget) // hack to indicate that getUniversalId().getId() is no longer valid return; + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mLocked = locked; - QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid()) { @@ -776,7 +780,8 @@ void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) { - QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) @@ -809,9 +814,15 @@ void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); - if (currentIndex.isValid() && currentIndex.row() >= start && currentIndex.row() <= end) + if (!currentIndex.isValid()) + { + return; + } + + if (currentIndex.parent() == parent && currentIndex.row() >= start && currentIndex.row() <= end) { if(mEditWidget) { @@ -830,50 +841,80 @@ void CSVWorld::SimpleDialogueSubView::updateCurrentId() } -CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) -: SimpleDialogueSubView (id, document) +void CSVWorld::DialogueSubView::addButtonBar() { - // bottom box - mBottom = new TableBottomBox (creatorFactory, document, id, this); + if (mButtons) + return; - mBottom->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Fixed); - - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - this, SLOT (requestFocus (const std::string&))); - - // button bar - mButtons = new RecordButtonBar (id, getTable(), mBottom, + mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom, &getCommandDispatcher(), this); - // layout - getMainLayout().addWidget (mButtons); - getMainLayout().addWidget (mBottom); + getMainLayout().insertWidget (1, mButtons); // connections connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); - + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); } +CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) +: SimpleDialogueSubView (id, document), mButtons (0) +{ + // bottom box + mBottom = new TableBottomBox (creatorFactory, document, id, this); + + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (requestFocus (const std::string&))); + + // button bar + if (CSMSettings::UserSettings::instance().setting ("dialogues/toolbar", QString("true")) == "true") + addButtonBar(); + + // layout + getMainLayout().addWidget (mBottom); +} + void CSVWorld::DialogueSubView::setEditLock (bool locked) { SimpleDialogueSubView::setEditLock (locked); - mButtons->setEditLock (locked); + + if (mButtons) + mButtons->setEditLock (locked); } void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value) { SimpleDialogueSubView::updateUserSetting (name, value); - mButtons->updateUserSetting (name, value); + + if (name=="dialogues/toolbar") + { + if (value.at(0)==QString ("true")) + { + addButtonBar(); + } + else + { + if (mButtons) + { + getMainLayout().removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } + } + } + + if (mButtons) + mButtons->updateUserSetting (name, value); } void CSVWorld::DialogueSubView::showPreview () { - QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview && @@ -885,7 +926,8 @@ void CSVWorld::DialogueSubView::showPreview () void CSVWorld::DialogueSubView::viewRecord () { - QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && currentIndex.row() < getTable().rowCount()) @@ -908,7 +950,7 @@ void CSVWorld::DialogueSubView::switchToRow (int row) setUniversalId (CSMWorld::UniversalId (type, id)); updateCurrentId(); - + getEditWidget().remake (row); int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification); @@ -920,8 +962,9 @@ void CSVWorld::DialogueSubView::switchToRow (int row) void CSVWorld::DialogueSubView::requestFocus (const std::string& id) { - QModelIndex index = getTable().getModelIndex (id, 0); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex index = getTable().getModelIndex (id, idColumn); if (index.isValid()) - switchToRow (index.row()); + switchToRow (index.row()); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index d82936e45..2ae0f9720 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -195,8 +195,8 @@ namespace CSVWorld CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor - void createEditorContextMenu(QWidget *editor, - CSMWorld::ColumnBase::Display display, + void createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, int currentRow) const; public: @@ -236,7 +236,7 @@ namespace CSVWorld void updateCurrentId(); bool isLocked() const; - + public: SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); @@ -256,10 +256,14 @@ namespace CSVWorld class DialogueSubView : public SimpleDialogueSubView { Q_OBJECT - + TableBottomBox* mBottom; RecordButtonBar *mButtons; + private: + + void addButtonBar(); + public: DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, @@ -268,14 +272,14 @@ namespace CSVWorld virtual void setEditLock (bool locked); virtual void updateUserSetting (const QString& name, const QStringList& value); - + private slots: void showPreview(); void viewRecord(); - void switchToRow (int row); + void switchToRow (int row); void requestFocus (const std::string& id); }; diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 2190a62c6..e582e3356 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -1,4 +1,3 @@ - #include "enumdelegate.hpp" #include diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp new file mode 100644 index 000000000..2cf6222a6 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -0,0 +1,239 @@ +#include "extendedcommandconfigurator.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/data.hpp" + +CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent) + : QWidget(parent), + mNumUsedCheckBoxes(0), + mNumChecked(0), + mMode(Mode_None), + mData(document.getData()), + mEditLock(false) +{ + mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); + + connect(&mData, SIGNAL(idListChanged()), this, SLOT(dataIdListChanged())); + + mPerformButton = new QPushButton(this); + mPerformButton->setDefault(true); + mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); + + mCancelButton = new QPushButton("Cancel", this); + mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); + + mTypeGroup = new QGroupBox(this); + + QGridLayout *groupLayout = new QGridLayout(mTypeGroup); + groupLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + mTypeGroup->setLayout(groupLayout); + + QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addWidget(mTypeGroup); + mainLayout->addWidget(mPerformButton); + mainLayout->addWidget(mCancelButton); +} + +void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) +{ + mMode = mode; + if (mMode != Mode_None) + { + mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); + mSelectedIds = selectedIds; + mCommandDispatcher->setSelection(mSelectedIds); + + setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); + setupGroupLayout(); + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::setEditLock(bool locked) +{ + if (mEditLock != locked) + { + mEditLock = locked; + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + setupGroupLayout(); +} + +void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() +{ + if (mMode == Mode_None) + { + return; + } + + int groupWidth = mTypeGroup->geometry().width(); + QGridLayout *layout = qobject_cast(mTypeGroup->layout()); + + // Find the optimal number of rows to place the checkboxes within the available space + int divider = 1; + do + { + while (layout->itemAt(0) != NULL) + { + layout->removeItem(layout->itemAt(0)); + } + + int counter = 0; + int itemsPerRow = mNumUsedCheckBoxes / divider; + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < mNumUsedCheckBoxes) + { + int row = counter / itemsPerRow; + int column = counter - (counter / itemsPerRow) * itemsPerRow; + layout->addWidget(current->first, row, column); + } + ++counter; + } + divider *= 2; + } + while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); +} + +void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) +{ + // Make sure that we have enough checkboxes + int numTypes = static_cast(types.size()); + int numCheckBoxes = static_cast(mTypeCheckBoxes.size()); + if (numTypes > numCheckBoxes) + { + for (int i = numTypes - numCheckBoxes; i > 0; --i) + { + QCheckBox *checkBox = new QCheckBox(mTypeGroup); + connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxStateChanged(int))); + mTypeCheckBoxes.insert(std::make_pair(checkBox, CSMWorld::UniversalId::Type_None)); + } + } + + // Set up the checkboxes + int counter = 0; + CheckBoxMap::iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < numTypes) + { + CSMWorld::UniversalId type = types[counter]; + current->first->setText(QString::fromUtf8(type.getTypeName().c_str())); + current->first->setChecked(true); + current->second = type; + ++counter; + } + else + { + current->first->hide(); + } + } + mNumChecked = mNumUsedCheckBoxes = numTypes; +} + +void CSVWorld::ExtendedCommandConfigurator::lockWidgets(bool locked) +{ + mPerformButton->setEnabled(!mEditLock && mNumChecked > 0); + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (int i = 0; current != end && i < mNumUsedCheckBoxes; ++current, ++i) + { + current->first->setEnabled(!mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() +{ + std::vector types; + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (current->first->isChecked()) + { + types.push_back(current->second); + } + } + + mCommandDispatcher->setExtendedTypes(types); + if (mMode == Mode_Delete) + { + mCommandDispatcher->executeExtendedDelete(); + } + else + { + mCommandDispatcher->executeExtendedRevert(); + } + emit done(); +} + +void CSVWorld::ExtendedCommandConfigurator::checkBoxStateChanged(int state) +{ + switch (state) + { + case Qt::Unchecked: + --mNumChecked; + break; + case Qt::Checked: + ++mNumChecked; + break; + case Qt::PartiallyChecked: // Not used + break; + } + + mPerformButton->setEnabled(mNumChecked > 0); +} + +void CSVWorld::ExtendedCommandConfigurator::dataIdListChanged() +{ + bool idsRemoved = false; + for (int i = 0; i < static_cast(mSelectedIds.size()); ++i) + { + if (!mData.hasId(mSelectedIds[i])) + { + std::swap(mSelectedIds[i], mSelectedIds.back()); + mSelectedIds.pop_back(); + idsRemoved = true; + --i; + } + } + + // If all selected IDs were removed, cancel the configurator + if (mSelectedIds.empty()) + { + emit done(); + return; + } + + if (idsRemoved) + { + mCommandDispatcher->setSelection(mSelectedIds); + } +} diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp new file mode 100644 index 000000000..641b4a524 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -0,0 +1,78 @@ +#ifndef CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP +#define CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP + +#include + +#include + +#include "../../model/world/universalid.hpp" + +class QPushButton; +class QGroupBox; +class QCheckBox; +class QLabel; +class QHBoxLayout; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; + class Data; +} + +namespace CSVWorld +{ + class ExtendedCommandConfigurator : public QWidget + { + Q_OBJECT + + public: + enum Mode { Mode_None, Mode_Delete, Mode_Revert }; + + private: + typedef std::map CheckBoxMap; + + QPushButton *mPerformButton; + QPushButton *mCancelButton; + QGroupBox *mTypeGroup; + CheckBoxMap mTypeCheckBoxes; + int mNumUsedCheckBoxes; + int mNumChecked; + + Mode mMode; + CSMWorld::CommandDispatcher *mCommandDispatcher; + CSMWorld::Data &mData; + std::vector mSelectedIds; + + bool mEditLock; + + void setupGroupLayout(); + void setupCheckBoxes(const std::vector &types); + void lockWidgets(bool locked); + + public: + ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent = 0); + + void configure(Mode mode, const std::vector &selectedIds); + void setEditLock(bool locked); + + protected: + virtual void resizeEvent(QResizeEvent *event); + + private slots: + void performExtendedCommand(); + void checkBoxStateChanged(int state); + void dataIdListChanged(); + + signals: + void done(); + }; +} + +#endif diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 5f04d9a7a..8ed52bf80 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -1,4 +1,3 @@ - #include "genericcreator.hpp" #include diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 13b05d2d1..1092d7217 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -1,4 +1,3 @@ - #include "idvalidator.hpp" #include diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 268a82a28..1139afd69 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -1,4 +1,3 @@ - #include "infocreator.hpp" #include diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index 1c2d6b95c..f3312bb20 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -1,4 +1,3 @@ - #include "previewsubview.hpp" #include @@ -13,8 +12,6 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo { QHBoxLayout *layout = new QHBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - if (document.getData().getReferenceables().searchId (id.getId())==-1) { std::string referenceableId = diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 63c0dd0a1..1a838a3b3 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -1,4 +1,3 @@ - #include "recordbuttonbar.hpp" #include @@ -17,18 +16,17 @@ void CSVWorld::RecordButtonBar::updateModificationButtons() mCloneButton->setDisabled (createAndDeleteDisabled); mAddButton->setDisabled (createAndDeleteDisabled); - mDeleteButton->setDisabled (createAndDeleteDisabled); bool commandDisabled = !mCommandDispatcher || mLocked; - + mRevertButton->setDisabled (commandDisabled); - mDeleteButton->setDisabled (commandDisabled); + mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled); } void CSVWorld::RecordButtonBar::updatePrevNextButtons() { int rows = mTable.rowCount(); - + if (rows<=1) { mPrevButton->setDisabled (true); @@ -62,12 +60,12 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, mPrevButton->setIcon(QIcon(":/go-previous.png")); mPrevButton->setToolTip ("Switch to previous record"); buttonsLayout->addWidget (mPrevButton, 0); - + mNextButton = new QToolButton (this); mNextButton->setIcon(QIcon(":/go-next.png")); mNextButton->setToolTip ("Switch to next record"); buttonsLayout->addWidget (mNextButton, 1); - + buttonsLayout->addStretch(2); // optional buttons of the right section @@ -94,22 +92,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, mCloneButton->setIcon(QIcon(":/edit-clone.png")); mCloneButton->setToolTip ("Clone record"); buttonsLayout->addWidget(mCloneButton); - + mAddButton = new QToolButton (this); mAddButton->setIcon(QIcon(":/add.png")); mAddButton->setToolTip ("Add new record"); buttonsLayout->addWidget(mAddButton); - + mDeleteButton = new QToolButton (this); mDeleteButton->setIcon(QIcon(":/edit-delete.png")); mDeleteButton->setToolTip ("Delete record"); buttonsLayout->addWidget(mDeleteButton); - + mRevertButton = new QToolButton (this); mRevertButton->setIcon(QIcon(":/edit-undo.png")); mRevertButton->setToolTip ("Revert record"); buttonsLayout->addWidget(mRevertButton); - + setLayout (buttonsLayout); // connections @@ -132,7 +130,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); - + updateModificationButtons(); updatePrevNextButtons(); } @@ -170,7 +168,7 @@ void CSVWorld::RecordButtonBar::cloneRequest() } void CSVWorld::RecordButtonBar::nextId() -{ +{ int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; if (newRow >= mTable.rowCount()) @@ -180,8 +178,8 @@ void CSVWorld::RecordButtonBar::nextId() newRow = 0; else return; - } - + } + emit switchToRow (newRow); } @@ -197,7 +195,7 @@ void CSVWorld::RecordButtonBar::prevId() else return; } - + emit switchToRow (newRow); } diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index e8055ed31..1357ca46f 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -1,4 +1,3 @@ - #include "referenceablecreator.hpp" #include diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 2b0d44df0..73ca62e02 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -1,4 +1,3 @@ - #include "referencecreator.hpp" #include diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index bc96b0952..49764bd17 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index 411e24e75..996d1dc8b 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,4 +1,3 @@ - #include "regionmapsubview.hpp" #include "regionmap.hpp" diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 397d24929..c2f3442f8 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,4 +1,3 @@ - #include "scenesubview.hpp" #include @@ -31,8 +30,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); @@ -135,11 +132,6 @@ void CSVWorld::SceneSubView::setEditLock (bool locked) } -void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) -{ - -} - void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); @@ -250,8 +242,6 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mToolbar = toolbar; connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); - connect (this, SIGNAL (updateSceneUserSetting(const QString &, const QStringList &)), - mScene, SLOT (updateUserSetting(const QString &, const QStringList &))); connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); mLayout->addWidget (mToolbar, 0); @@ -260,8 +250,3 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mScene->selectDefaultNavigationMode(); setFocusProxy (mScene); } - -void CSVWorld::SceneSubView::updateUserSetting (const QString &key, const QStringList &list) -{ - emit updateSceneUserSetting(key, list); -} diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index fc45347d0..a34d71901 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -52,8 +52,6 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString& key, const QString& value); - virtual void setStatusBar (bool show); virtual void useHint (const std::string& hint); @@ -83,14 +81,6 @@ namespace CSVWorld void cellSelectionChanged (const CSMWorld::UniversalId& id); void handleDrop(const std::vector& data); - - public slots: - - void updateUserSetting (const QString &, const QStringList &); - - signals: - - void updateSceneUserSetting (const QString &, const QStringList &); }; } diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp new file mode 100644 index 000000000..a9e315c73 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -0,0 +1,147 @@ +#include "scripterrortable.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/settings/usersettings.hpp" + +void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) +{ + std::ostringstream stream; + stream << message << " (" << loc.mLiteral << ")"; + + addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, + loc.mLine, loc.mColumn-loc.mLiteral.length()); +} + +void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) +{ + addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error); +} + +void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, + CSMDoc::Message::Severity severity, int line, int column) +{ + int row = rowCount(); + + setRowCount (row+1); + + QTableWidgetItem *severityItem = new QTableWidgetItem ( + QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str())); + severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 0, severityItem); + + if (line!=-1) + { + QTableWidgetItem *lineItem = new QTableWidgetItem; + lineItem->setData (Qt::DisplayRole, line+1); + lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 1, lineItem); + + QTableWidgetItem *columnItem = new QTableWidgetItem; + columnItem->setData (Qt::DisplayRole, column); + columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 3, columnItem); + } + + QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); + messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 2, messageItem); +} + +void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value) +{ + if (value=="Ignore") + Compiler::ErrorHandler::setWarningsMode (0); + else if (value=="Normal") + Compiler::ErrorHandler::setWarningsMode (1); + else if (value=="Strict") + Compiler::ErrorHandler::setWarningsMode (2); +} + +CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) +: QTableWidget (parent), mContext (document.getData()) +{ + setColumnCount (4); + + QStringList headers; + headers << "Severity" << "Line" << "Description"; + setHorizontalHeaderLabels (headers); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); +#else + horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); +#endif + horizontalHeader()->setStretchLastSection (true); + verticalHeader()->hide(); + setColumnHidden (3, true); + + setSelectionMode (QAbstractItemView::NoSelection); + + Compiler::registerExtensions (mExtensions); + mContext.setExtensions (&mExtensions); + + setWarningsMode (CSMSettings::UserSettings::instance().settingValue ("script-editor/warnings")); + + connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); +} + +void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value) +{ + if (name=="script-editor/warnings" && !value.isEmpty()) + setWarningsMode (value.at (0)); +} + +void CSVWorld::ScriptErrorTable::update (const std::string& source) +{ + clear(); + + try + { + std::istringstream input (source); + + Compiler::Scanner scanner (*this, input, mContext.getExtensions()); + + Compiler::FileParser parser (*this, mContext); + + scanner.scan (parser); + } + catch (const Compiler::SourceException&) + { + // error has already been reported via error handler + } + catch (const std::exception& error) + { + addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); + } +} + +void CSVWorld::ScriptErrorTable::clear() +{ + setRowCount (0); +} + +bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) +{ + return mContext.clearLocals (script); +} + +void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) +{ + if (item (row, 1)) + { + int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); + int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); + emit highlightError (scriptLine-1, scriptColumn); + } +} diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp new file mode 100644 index 000000000..33af7c864 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -0,0 +1,62 @@ +#ifndef CSV_WORLD_SCRIPTERRORTABLE_H +#define CSV_WORLD_SCRIPTERRORTABLE_H + +#include + +#include +#include + +#include "../../model/world/scriptcontext.hpp" +#include "../../model/doc/messages.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler + { + Q_OBJECT + + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; + + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); + ///< Report error to the user. + + virtual void report (const std::string& message, Type type); + ///< Report a file related error + + void addMessage (const std::string& message, CSMDoc::Message::Severity severity, + int line = -1, int column = -1); + + void setWarningsMode (const QString& value); + + public: + + ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); + + void updateUserSetting (const QString& name, const QStringList& value); + + void update (const std::string& source); + + void clear(); + + /// Clear local variable cache for \a script. + /// + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); + + private slots: + + void cellClicked (int row, int column); + + signals: + + void highlightError (int line, int column); + }; +} + +#endif diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index 4923a44d8..487b5b139 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -1,4 +1,3 @@ - #include "scripthighlighter.hpp" #include diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 411eb3660..d99f789b3 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" @@ -15,45 +16,97 @@ #include "../../model/settings/usersettings.hpp" #include "scriptedit.hpp" +#include "recordbuttonbar.hpp" +#include "tablebottombox.hpp" +#include "genericcreator.hpp" +#include "scripterrortable.hpp" + +void CSVWorld::ScriptSubView::addButtonBar() +{ + if (mButtons) + return; + + mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); + + mLayout.insertWidget (1, mButtons); + + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); +} + +void CSVWorld::ScriptSubView::recompile() +{ + if (!mCompileDelay->isActive() && !isDeleted()) + mCompileDelay->start ( + CSMSettings::UserSettings::instance().setting ("script-editor/compile-delay").toInt()); +} + +bool CSVWorld::ScriptSubView::isDeleted() const +{ + return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt() + ==CSMWorld::RecordBase::State_Deleted; +} + +void CSVWorld::ScriptSubView::updateDeletedState() +{ + if (isDeleted()) + { + mErrors->clear(); + mEditor->setEnabled (false); + } + else + { + mEditor->setEnabled (true); + recompile(); + } +} CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0) +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { - QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + std::vector selection (1, id.getId()); + mCommandDispatcher.setSelection (selection); - mBottom = new QWidget(this); - QStackedLayout *bottmLayout = new QStackedLayout(mBottom); - bottmLayout->setContentsMargins (0, 0, 0, 0); - QStatusBar *statusBar = new QStatusBar(mBottom); - mStatus = new QLabel(mBottom); - statusBar->addWidget (mStatus); - bottmLayout->addWidget (statusBar); - mBottom->setLayout (bottmLayout); + mMain = new QSplitter (this); + mMain->setOrientation (Qt::Vertical); + mLayout.addWidget (mMain, 2); - layout->addWidget (mBottom, 0); - layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this); + mMain->addWidget (mEditor); + mMain->setCollapsible (0, false); - QWidget *widget = new QWidget; - widget->setLayout (layout); + mErrors = new ScriptErrorTable (document, this); + mMain->addWidget (mErrors); + + QWidget *widget = new QWidget (this);; + widget->setLayout (&mLayout); setWidget (widget); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); - for (int i=0; icolumnCount(); ++i) - if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)== - CSMWorld::ColumnBase::Display_ScriptFile) - { - mColumn = i; - break; - } + mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); + mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - if (mColumn==-1) - throw std::logic_error ("Can't find script column"); + QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); - mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); + mEditor->setPlainText (source); + // bottom box and buttons + mBottom = new TableBottomBox (CreatorFactory(), document, id, this); + if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true") + addButtonBar(); + + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (switchToId (const std::string&))); + + mLayout.addWidget (mBottom); + + // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), @@ -64,35 +117,80 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); + + mErrors->update (source.toUtf8().constData()); + + connect (mErrors, SIGNAL (highlightError (int, int)), + this, SLOT (highlightError (int, int))); + + mCompileDelay = new QTimer (this); + mCompileDelay->setSingleShot (true); + connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); + + updateDeletedState(); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) { if (name == "script-editor/show-linenum") { - std::string showLinenum = value.at(0).toStdString(); + std::string showLinenum = value.at(0).toUtf8().constData(); mEditor->showLineNum(showLinenum == "true"); mBottom->setVisible(showLinenum == "true"); } else if (name == "script-editor/mono-font") { - mEditor->setMonoFont(value.at(0).toStdString() == "true"); + mEditor->setMonoFont (value.at(0)==QString ("true")); } + else if (name=="script-editor/toolbar") + { + if (value.at(0)==QString ("true")) + { + addButtonBar(); + } + else + { + if (mButtons) + { + mLayout.removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } + } + } + else if (name=="script-editor/compile-delay") + { + mCompileDelay->setInterval (value.at (0).toInt()); + } + + if (mButtons) + mButtons->updateUserSetting (name, value); + + mErrors->updateUserSetting (name, value); + + if (name=="script-editor/warnings") + recompile(); +} + +void CSVWorld::ScriptSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar (show); } void CSVWorld::ScriptSubView::updateStatusBar () { - std::ostringstream stream; - - stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", " - << mEditor->textCursor().columnNumber() + 1 << ")"; - - mStatus->setText (QString::fromUtf8 (stream.str().c_str())); + mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, + mEditor->textCursor().columnNumber() + 1); } void CSVWorld::ScriptSubView::setEditLock (bool locked) { mEditor->setReadOnly (locked); + + if (mButtons) + mButtons->setEditLock (locked); + + mCommandDispatcher.setEditLock (locked); } void CSVWorld::ScriptSubView::useHint (const std::string& hint) @@ -129,8 +227,12 @@ void CSVWorld::ScriptSubView::textChanged() ScriptEdit::ChangeLock lock (*mEditor); + QString source = mEditor->toPlainText(); + mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, - mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); + mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); + + recompile(); } void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -140,22 +242,94 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo ScriptEdit::ChangeLock lock (*mEditor); + bool updateRequired = false; + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() && - index.column()>=topLeft.column() && index.column()<=bottomRight.column()) + if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) { - QTextCursor cursor = mEditor->textCursor(); - mEditor->setPlainText (mModel->data (index).toString()); - mEditor->setTextCursor (cursor); + if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column()) + updateDeletedState(); + + if (mColumn>=topLeft.column() && mColumn<=bottomRight.column()) + { + QString source = mModel->data (index).toString(); + + QTextCursor cursor = mEditor->textCursor(); + mEditor->setPlainText (source); + mEditor->setTextCursor (cursor); + + updateRequired = true; + } } + + if (updateRequired) + recompile(); } void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { + bool updateRequired = false; + + for (int i=start; i<=end; ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + + if (updateRequired) + recompile(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (!parent.isValid() && index.row()>=start && index.row()<=end) emit closeRequest(); } +void CSVWorld::ScriptSubView::switchToRow (int row) +{ + int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); + setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); + + mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString()); + + std::vector selection (1, id); + mCommandDispatcher.setSelection (selection); + + updateDeletedState(); +} + +void CSVWorld::ScriptSubView::switchToId (const std::string& id) +{ + switchToRow (mModel->getModelIndex (id, 0).row()); +} + +void CSVWorld::ScriptSubView::highlightError (int line, int column) +{ + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); +} + +void CSVWorld::ScriptSubView::updateRequest() +{ + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + + QString source = mModel->data (index).toString(); + + mErrors->update (source.toUtf8().constData()); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 1c6474e54..907dc7958 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -1,10 +1,17 @@ #ifndef CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H +#include + +#include "../../model/world/commanddispatcher.hpp" + #include "../doc/subview.hpp" class QModelIndex; class QLabel; +class QVBoxLayout; +class QSplitter; +class QTime; namespace CSMDoc { @@ -19,6 +26,9 @@ namespace CSMWorld namespace CSVWorld { class ScriptEdit; + class RecordButtonBar; + class TableBottomBox; + class ScriptErrorTable; class ScriptSubView : public CSVDoc::SubView { @@ -28,8 +38,25 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; - QWidget *mBottom; - QLabel *mStatus; + int mIdColumn; + int mStateColumn; + TableBottomBox *mBottom; + RecordButtonBar *mButtons; + CSMWorld::CommandDispatcher mCommandDispatcher; + QVBoxLayout mLayout; + QSplitter *mMain; + ScriptErrorTable *mErrors; + QTimer *mCompileDelay; + + private: + + void addButtonBar(); + + void recompile(); + + bool isDeleted() const; + + void updateDeletedState(); public: @@ -41,6 +68,8 @@ namespace CSVWorld virtual void updateUserSetting (const QString& name, const QStringList& value); + virtual void setStatusBar (bool show); + public slots: void textChanged(); @@ -52,6 +81,14 @@ namespace CSVWorld private slots: void updateStatusBar(); + + void switchToRow (int row); + + void switchToId (const std::string& id); + + void highlightError (int line, int column); + + void updateRequest(); }; } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index b8a6ba429..299ac6ebe 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 74343a5f6..73bef7b26 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,4 +1,3 @@ - #include "table.hpp" #include @@ -33,28 +32,14 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { // configure dispatcher - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - std::vector records; - - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); - - records.push_back (mModel->data ( - mModel->index (row, columnIndex)).toString().toUtf8().constData()); - } - - mDispatcher->setSelection (records); + mDispatcher->setSelection (getSelectedIds()); std::vector extendedTypes = mDispatcher->getExtendedTypes(); mDispatcher->setExtendedTypes (extendedTypes); // create context menu + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu (this); /// \todo add menu items for select all and clear selection @@ -288,12 +273,16 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, horizontalHeader()->setResizeMode (QHeaderView::Interactive); #endif verticalHeader()->hide(); - setSortingEnabled (sorting); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - int columns = mModel->columnCount(); + setSortingEnabled (sorting); + if (sorting) + { + sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); + } + int columns = mModel->columnCount(); for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); @@ -352,16 +341,12 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); - /// \todo add a user option, that redirects the extended action to an input panel (in - /// the bottom bar) that lets the user select which record collections should be - /// modified. - mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); - connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); addAction (mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); - connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); mEditIdAction = new TableEditIdAction (*this, this); @@ -371,8 +356,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); - connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), + this, SLOT (rowAdded (const std::string &))); /// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) @@ -411,6 +398,22 @@ CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } +std::vector CSVWorld::Table::getSelectedIds() const +{ + std::vector ids; + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); + iter != selectedRows.end(); + ++iter) + { + int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); + ids.push_back (mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData()); + } + return ids; +} + void CSVWorld::Table::editRecord() { if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -543,6 +546,34 @@ void CSVWorld::Table::previewRecord() } } +void CSVWorld::Table::executeExtendedDelete() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + QString configSetting = settings.settingValue ("table-input/extended-config"); + if (configSetting == "true") + { + emit extendedDeleteConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection); + } +} + +void CSVWorld::Table::executeExtendedRevert() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + QString configSetting = settings.settingValue ("table-input/extended-config"); + if (configSetting == "true") + { + emit extendedRevertConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection); + } +} + void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList &list) { if (name=="table-input/jump-to-added") @@ -653,10 +684,6 @@ void CSVWorld::Table::tableSizeUpdate() } emit tableSizeChanged (size, deleted, modified); - - // not really related to tableSizeUpdate() but placed here for convenience rather than - // creating a bunch of extra connections & slot - mProxyModel->refreshFilter(); } void CSVWorld::Table::selectionSizeUpdate() @@ -718,12 +745,13 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const return idToDrag; } -void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, int end) +void CSVWorld::Table::rowAdded(const std::string &id) { tableSizeUpdate(); if(mJumpToAddedRecord) { - selectRow(end); + int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + selectRow(mProxyModel->getModelIndex(id, idColumn).row()); if(mUnselectAfterJump) clearSelection(); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index adacd3a9d..95a25075d 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -93,6 +93,8 @@ namespace CSVWorld std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + std::vector getSelectedIds() const; + virtual std::vector getDraggedRecords() const; signals: @@ -112,6 +114,10 @@ namespace CSVWorld void closeRequest(); + void extendedDeleteConfigRequest(const std::vector &selectedIds); + + void extendedRevertConfigRequest(const std::vector &selectedIds); + private slots: void editCell(); @@ -128,6 +134,10 @@ namespace CSVWorld void previewRecord(); + void executeExtendedDelete(); + + void executeExtendedRevert(); + public slots: void tableSizeUpdate(); @@ -140,7 +150,7 @@ namespace CSVWorld void updateUserSetting (const QString &name, const QStringList &list); - void rowsInsertedEvent(const QModelIndex& parent, int start, int end); + void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index dc3a6cc76..eed522227 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -1,4 +1,3 @@ - #include "tablebottombox.hpp" #include @@ -6,9 +5,25 @@ #include #include #include +#include +#include #include "creator.hpp" +void CSVWorld::TableBottomBox::updateSize() +{ + // Make sure that the size of the bottom box is determined by the currently visible widget + for (int i = 0; i < mLayout->count(); ++i) + { + QSizePolicy::Policy verPolicy = QSizePolicy::Ignored; + if (mLayout->widget(i) == mLayout->currentWidget()) + { + verPolicy = QSizePolicy::Expanding; + } + mLayout->widget(i)->setSizePolicy(QSizePolicy::Expanding, verPolicy); + } +} + void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) @@ -35,15 +50,33 @@ void CSVWorld::TableBottomBox::updateStatus() } } + if (mHasPosition) + { + if (!first) + stream << " -- "; + + stream << "(" << mRow << ", " << mColumn << ")"; + } + mStatus->setText (QString::fromUtf8 (stream.str().c_str())); } } +void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) +{ + mExtendedConfigurator->configure (mode, selectedIds); + mLayout->setCurrentWidget (mExtendedConfigurator); + mEditMode = EditMode_ExtendedConfig; + setVisible (true); + mExtendedConfigurator->setFocus(); +} + CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mCreating (false) +: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; @@ -52,6 +85,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mLayout = new QStackedLayout; mLayout->setContentsMargins (0, 0, 0, 0); + connect (mLayout, SIGNAL (currentChanged (int)), this, SLOT (currentWidgetChanged (int))); mStatus = new QLabel; @@ -67,19 +101,28 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto if (mCreator) { + mCreator->installEventFilter(this); mLayout->addWidget (mCreator); - connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); + connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); connect (mCreator, SIGNAL (requestFocus (const std::string&)), this, SIGNAL (requestFocus (const std::string&))); } + + mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); + mExtendedConfigurator->installEventFilter(this); + mLayout->addWidget (mExtendedConfigurator); + connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); + + updateSize(); } void CSVWorld::TableBottomBox::setEditLock (bool locked) { if (mCreator) mCreator->setEditLock (locked); + mExtendedConfigurator->setEditLock (locked); } CSVWorld::TableBottomBox::~TableBottomBox() @@ -87,11 +130,25 @@ CSVWorld::TableBottomBox::~TableBottomBox() delete mCreator; } +bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) + { + requestDone(); + return true; + } + } + return QWidget::eventFilter(object, event); +} + void CSVWorld::TableBottomBox::setStatusBar (bool show) { if (show!=mShowStatusBar) { - setVisible (show || mCreating); + setVisible (show || (mEditMode != EditMode_None)); mShowStatusBar = show; @@ -105,7 +162,7 @@ bool CSVWorld::TableBottomBox::canCreateAndDelete() const return mCreator; } -void CSVWorld::TableBottomBox::createRequestDone() +void CSVWorld::TableBottomBox::requestDone() { if (!mShowStatusBar) setVisible (false); @@ -113,8 +170,12 @@ void CSVWorld::TableBottomBox::createRequestDone() updateStatus(); mLayout->setCurrentWidget (mStatusBar); + mEditMode = EditMode_None; +} - mCreating = false; +void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) +{ + updateSize(); } void CSVWorld::TableBottomBox::selectionSizeChanged (int size) @@ -152,24 +213,48 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi updateStatus(); } +void CSVWorld::TableBottomBox::positionChanged (int row, int column) +{ + mRow = row; + mColumn = column; + mHasPosition = true; + updateStatus(); +} + +void CSVWorld::TableBottomBox::noMorePosition() +{ + mHasPosition = false; + updateStatus(); +} + void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } -void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, - const CSMWorld::UniversalId::Type type) +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; + mEditMode = EditMode_Creation; mCreator->focus(); } + +void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); +} + +void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector &selectedIds) +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert, selectedIds); +} diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index a7d009c42..781cccc9e 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -4,10 +4,11 @@ #include #include +#include "extendedcommandconfigurator.hpp" + class QLabel; class QStackedLayout; class QStatusBar; -class QUndoStack; namespace CSMDoc { @@ -23,13 +24,21 @@ namespace CSVWorld { Q_OBJECT + enum EditMode { EditMode_None, EditMode_Creation, EditMode_ExtendedConfig }; + bool mShowStatusBar; QLabel *mStatus; QStatusBar *mStatusBar; int mStatusCount[4]; + + EditMode mEditMode; Creator *mCreator; - bool mCreating; + ExtendedCommandConfigurator *mExtendedConfigurator; + QStackedLayout *mLayout; + bool mHasPosition; + int mRow; + int mColumn; private: @@ -37,17 +46,24 @@ namespace CSVWorld TableBottomBox (const TableBottomBox&); TableBottomBox& operator= (const TableBottomBox&); + void updateSize(); + void updateStatus(); + void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds); + public: - TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, + TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget *parent = 0); virtual ~TableBottomBox(); + virtual bool eventFilter(QObject *object, QEvent *event); + void setEditLock (bool locked); void setStatusBar (bool show); @@ -65,9 +81,11 @@ namespace CSVWorld private slots: - void createRequestDone(); + void requestDone(); ///< \note This slot being called does not imply success. + void currentWidgetChanged(int index); + public slots: void selectionSizeChanged (int size); @@ -77,9 +95,16 @@ namespace CSVWorld /// \param deleted Number of deleted records /// \param modified Number of added and modified records + void positionChanged (int row, int column); + + void noMorePosition(); + void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); + + void extendedDeleteConfigRequest(const std::vector &selectedIds); + void extendedRevertConfigRequest(const std::vector &selectedIds); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 75671a50c..81b269993 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,4 +1,3 @@ - #include "tablesubview.hpp" #include @@ -23,8 +22,6 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = new TableBottomBox (creatorFactory, document, id, this), 0); @@ -71,6 +68,11 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + + connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), + mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); + connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), + mBottom, SLOT(extendedRevertConfigRequest(const std::vector &))); } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); @@ -166,4 +168,3 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) } return false; } - diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index f21658581..b87d6b4f4 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -1,4 +1,3 @@ - #include "util.hpp" #include @@ -13,6 +12,7 @@ #include #include #include +#include #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" @@ -173,7 +173,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO // TODO: Find a better solution? if (display == CSMWorld::ColumnBase::Display_Boolean) { - return QStyledItemDelegate::createEditor(parent, option, index); + return QItemEditorFactory::defaultFactory()->createEditor(QVariant::Bool, parent); } // For tables the pop-up of the color editor should appear immediately after the editor creation // (the third parameter of ColorEditor's constructor) diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 90a686a67..a63e6028c 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -1,4 +1,3 @@ - #include "vartypedelegate.hpp" #include diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cd1ac31ec..fa05576f5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -23,6 +23,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation + renderbin ) add_openmw_dir (mwinput @@ -64,7 +65,7 @@ add_openmw_dir (mwworld containerstore actiontalk actiontake manualref player cellfunctors failedaction cells localscripts customdata inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat - esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor + store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager ) @@ -74,15 +75,15 @@ add_openmw_dir (mwphysics add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door - ingredient creaturelevlist itemlevlist light lockpick misc probe repair static + ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor ) add_openmw_dir (mwmechanics - mechanicsmanagerimp stat creaturestats magiceffects movement + mechanicsmanagerimp stat creaturestats magiceffects movement actorutil drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate + character actors objects aistate coordinateconverter ) add_openmw_dir (mwstate @@ -178,4 +179,5 @@ if (MSVC) if (CMAKE_CL_64) set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") endif (CMAKE_CL_64) + add_definitions("-D_USE_MATH_DEFINES") endif (MSVC) diff --git a/apps/openmw/android_commandLine.cpp b/apps/openmw/android_commandLine.cpp index ebfff28ca..7e7f368e0 100644 --- a/apps/openmw/android_commandLine.cpp +++ b/apps/openmw/android_commandLine.cpp @@ -7,21 +7,21 @@ int argcData; extern "C" void releaseArgv(); void releaseArgv() { - delete[] argvData; + delete[] argvData; } JNIEXPORT void JNICALL Java_ui_activity_GameActivity_commandLine(JNIEnv *env, - jobject obj, jint argc, jobjectArray stringArray) { - jboolean iscopy; - argcData = (int) argc; - argvData = new const char *[argcData + 1]; - argvData[0] = "openmw"; - for (int i = 1; i < argcData + 1; i++) { - jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, - i - 1); - argvData[i] = (env)->GetStringUTFChars(string, &iscopy); - (env)->DeleteLocalRef(string); - } - (env)->DeleteLocalRef(stringArray); + jobject obj, jint argc, jobjectArray stringArray) { + jboolean iscopy; + argcData = (int) argc; + argvData = new const char *[argcData + 1]; + argvData[0] = "openmw"; + for (int i = 1; i < argcData + 1; i++) { + jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, + i - 1); + argvData[i] = (env)->GetStringUTFChars(string, &iscopy); + (env)->DeleteLocalRef(string); + } + (env)->DeleteLocalRef(stringArray); } diff --git a/apps/openmw/android_commandLine.h b/apps/openmw/android_commandLine.h index 21d1064c6..5ca79c2d0 100644 --- a/apps/openmw/android_commandLine.h +++ b/apps/openmw/android_commandLine.h @@ -1,5 +1,4 @@ - /* DO NOT EDIT THIS FILE - it is machine generated */ #include #ifndef _Included_ui_activity_GameActivity_commandLine diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 1b2839519..47b77a8b3 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -16,22 +16,22 @@ extern const char **argvData; void releaseArgv(); int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, - jobject obj) { + jobject obj) { - SDL_Android_Init(env, cls); + SDL_Android_Init(env, cls); - SDL_SetMainReady(); + SDL_SetMainReady(); - /* Run the application code! */ + /* Run the application code! */ - int status; + int status; - status = main(argcData+1, argvData); - releaseArgv(); - /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ - /* exit(status); */ + status = main(argcData+1, argvData); + releaseArgv(); + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ - return status; + return status; } #endif /* __ANDROID__ */ diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 8f25d041c..373d78746 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -37,8 +37,8 @@ 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"; -#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ -# define PATH_MAX 256 +#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ +# define PATH_MAX 256 #endif static char argv0[PATH_MAX]; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index dc2cb8f37..79c8c4cc9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -27,7 +27,7 @@ #include #include -#include +#include #include "mwinput/inputmanagerimp.hpp" @@ -66,7 +66,7 @@ namespace void OMW::Engine::executeLocalScripts() { - MWWorld::LocalScripts& localScripts = MWBase::Environment::get().getWorld()->getLocalScripts(); + MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); localScripts.startIteration(); @@ -76,7 +76,7 @@ void OMW::Engine::executeLocalScripts() MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); - MWBase::Environment::get().getScriptManager()->run (script.first, interpreterContext); + mEnvironment.getScriptManager()->run (script.first, interpreterContext); } localScripts.setIgnore (MWWorld::Ptr()); @@ -90,85 +90,88 @@ void OMW::Engine::frame(float frametime) mEnvironment.setFrameDuration (frametime); // update input - MWBase::Environment::get().getInputManager()->update(frametime, false); + mEnvironment.getInputManager()->update(frametime, false); - // When the window is minimized, pause everything. Currently this *has* to be here to work around a MyGUI bug. - // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures. - //if (!mOgre->getWindow()->isActive() || !mOgre->getWindow()->isVisible()) - // return true; + // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. + // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2), + // and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21) + if (!mEnvironment.getInputManager()->isWindowVisible()) + return; // sound if (mUseSound) - MWBase::Environment::get().getSoundManager()->update(frametime); + mEnvironment.getSoundManager()->update(frametime); // Main menu opened? Then scripts are also paused. - bool paused = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); + bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); // update game state - MWBase::Environment::get().getStateManager()->update (frametime); + mEnvironment.getStateManager()->update (frametime); - bool guiActive = MWBase::Environment::get().getWindowManager()->isGuiMode(); + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); osg::Timer_t beforeScriptTick = osg::Timer::instance()->tick(); - if (MWBase::Environment::get().getStateManager()->getState()== + if (mEnvironment.getStateManager()->getState()== MWBase::StateManager::State_Running) { if (!paused) { - if (MWBase::Environment::get().getWorld()->getScriptsEnabled()) + if (mEnvironment.getWorld()->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + mEnvironment.getScriptManager()->getGlobalScripts().run(); } - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + mEnvironment.getWorld()->markCellAsUnchanged(); } if (!guiActive) - MWBase::Environment::get().getWorld()->advanceTime( - frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); + { + double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0; + mEnvironment.getWorld()->advanceTime(hours, true); + } } osg::Timer_t afterScriptTick = osg::Timer::instance()->tick(); // update actors osg::Timer_t beforeMechanicsTick = osg::Timer::instance()->tick(); - if (MWBase::Environment::get().getStateManager()->getState()!= + if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - MWBase::Environment::get().getMechanicsManager()->update(frametime, + mEnvironment.getMechanicsManager()->update(frametime, guiActive); } osg::Timer_t afterMechanicsTick = osg::Timer::instance()->tick(); - if (MWBase::Environment::get().getStateManager()->getState()== + if (mEnvironment.getStateManager()->getState()== MWBase::StateManager::State_Running) { MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); if(!guiActive && player.getClass().getCreatureStats(player).isDead()) - MWBase::Environment::get().getStateManager()->endGame(); + mEnvironment.getStateManager()->endGame(); } // update world osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();; - if (MWBase::Environment::get().getStateManager()->getState()!= + if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - MWBase::Environment::get().getWorld()->update(frametime, guiActive); + mEnvironment.getWorld()->update(frametime, guiActive); } osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick(); // update GUI - MWBase::Environment::get().getWindowManager()->onFrame(frametime); - if (MWBase::Environment::get().getStateManager()->getState()!= + mEnvironment.getWindowManager()->onFrame(frametime); + if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { #if 0 - MWBase::Environment::get().getWindowManager()->wmUpdateFps(fps); + mEnvironment.getWindowManager()->wmUpdateFps(fps); #endif - MWBase::Environment::get().getWindowManager()->update(); + mEnvironment.getWindowManager()->update(); } int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); @@ -414,7 +417,7 @@ void OMW::Engine::setWindowIcon() { boost::filesystem::ifstream windowIconStream; std::string windowIcon = (mResDir / "mygui" / "openmw.png").string(); - windowIconStream.open(windowIcon); + windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); if (windowIconStream.fail()) std::cerr << "Failed to open " << windowIcon << std::endl; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); @@ -425,7 +428,7 @@ void OMW::Engine::setWindowIcon() } osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); if (!result.success()) - std::cerr << "Failed to read " << windowIcon << ": " << result.message() << std::endl; + std::cerr << "Failed to read " << windowIcon << ": " << result.message() << " code " << result.status() << std::endl; else { osg::ref_ptr image = result.getImage(); @@ -493,7 +496,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) rootNode->addChild(guiRoot); MWGui::WindowManager* window = new MWGui::WindowManager(mViewer, guiRoot, mResourceSystem.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, - mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap); + mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap, + Version::getOpenmwVersionDescription(mResDir.string())); mEnvironment.setWindowManager (window); // Create sound system @@ -510,7 +514,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mFileCollections, mContentFiles, mEncoder, mFallbackMap, mActivationDistanceOverride, mCellName, mStartupScript)); - MWBase::Environment::get().getWorld()->setupPlayer(); + mEnvironment.getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); window->initUI(); @@ -527,7 +531,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); - mEnvironment.setScriptManager (new MWScript::ScriptManager (MWBase::Environment::get().getWorld()->getStore(), + mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), mVerboseScripts, *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector())); @@ -542,7 +546,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // scripts if (mCompileAll) { - std::pair result = MWBase::Environment::get().getScriptManager()->compileAll(); + std::pair result = mEnvironment.getScriptManager()->compileAll(); if (result.first) std::cout << "compiled " << result.second << " of " << result.first << " scripts (" @@ -601,7 +605,7 @@ public: osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); if (!result.success()) { - std::cerr << "Can't write screenshot: " << result.message() << std::endl; + std::cerr << "Can't write screenshot: " << result.message() << " code " << result.status() << std::endl; } } @@ -645,43 +649,41 @@ void OMW::Engine::go() prepareEngine (settings); - // Play some good 'ol tunes - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - if (!mSaveGameFile.empty()) { - MWBase::Environment::get().getStateManager()->loadGame(mSaveGameFile); + mEnvironment.getStateManager()->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); try { // Is there an ini setting for this filename or something? - MWBase::Environment::get().getSoundManager()->streamMusic("Special/morrowind title.mp3"); + mEnvironment.getSoundManager()->streamMusic("Special/morrowind title.mp3"); std::string logo = mFallbackMap["Movies_Morrowind_Logo"]; if (!logo.empty()) - MWBase::Environment::get().getWindowManager()->playVideo(logo, true); + mEnvironment.getWindowManager()->playVideo(logo, true); } catch (...) {} } else { - MWBase::Environment::get().getStateManager()->newGame (!mNewGame); + mEnvironment.getStateManager()->newGame (!mNewGame); } // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; - while (!mViewer->done() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) + float framerateLimit = Settings::Manager::getFloat("framerate limit", "Video"); + while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { double dt = frameTimer.time_s(); frameTimer.setStartTick(); dt = std::min(dt, 0.2); - bool guiActive = MWBase::Environment::get().getWindowManager()->isGuiMode(); + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); if (!guiActive) simulationTime += dt; @@ -689,9 +691,27 @@ void OMW::Engine::go() frame(dt); - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); + if (!mEnvironment.getInputManager()->isWindowVisible()) + { + OpenThreads::Thread::microSleep(5000); + continue; + } + else + { + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + } + + if (framerateLimit > 0.f) + { + double thisFrameTime = frameTimer.time_s(); + double minFrameTime = 1.0 / framerateLimit; + if (thisFrameTime < minFrameTime) + { + OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime)); + } + } } // Save user settings @@ -702,15 +722,15 @@ void OMW::Engine::go() void OMW::Engine::activate() { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + if (mEnvironment.getWindowManager()->isGuiMode()) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0 - || player.getClass().getCreatureStats(player).getKnockedDown()) + MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); + const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); + if (playerStats.isParalyzed() || playerStats.getKnockedDown()) return; - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); + MWWorld::Ptr ptr = mEnvironment.getWorld()->getFacedObject(); if (ptr.isEmpty()) return; @@ -726,7 +746,7 @@ void OMW::Engine::activate() return; } - MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); + mEnvironment.getWorld()->activate(ptr, mEnvironment.getWorld()->getPlayerPtr()); } void OMW::Engine::screenshot() diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index dc58daed9..3d631c743 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -14,7 +14,9 @@ #if defined(_WIN32) // For OutputDebugString +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include // makes __argc and __argv available on windows #include @@ -197,18 +199,14 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - std::cout << "OpenMW version " << OPENMW_VERSION; - std::string rev = OPENMW_VERSION_COMMITHASH; - std::string tag = OPENMW_VERSION_TAGHASH; - if (!rev.empty() && !tag.empty()) - { - rev = rev.substr(0, 10); - std::cout << " (revision " << rev << ")"; - } - std::cout << std::endl; - if (variables.count ("version")) + { + cfgMgr.readConfiguration(variables, desc, true); + + Version::Version v = Version::getOpenmwVersion(variables["resources"].as()); + std::cout << v.describe() << std::endl; return false; + } cfgMgr.readConfiguration(variables, desc); diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index a90eec5bf..4efa7c273 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -1,4 +1,3 @@ - #include "environment.hpp" #include diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 79477d883..75c55e028 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -25,6 +25,8 @@ namespace MWBase virtual ~InputManager() {} + virtual bool isWindowVisible() = 0; + virtual void update(float dt, bool disableControls, bool disableEvents=false) = 0; virtual void changeInputMode(bool guiMode) = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 1d3619d3d..63ad1d32f 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -23,6 +23,7 @@ namespace MWWorld { class Ptr; class CellStore; + class CellRef; } namespace Loading @@ -128,6 +129,11 @@ namespace MWBase OffenseType type, int arg=0, bool victimAware=false) = 0; /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + + /// Notify that actor was killed, add a murder bounty if applicable + /// @note No-op for non-player attackers + virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, @@ -154,12 +160,13 @@ namespace MWBase 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; + virtual bool 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 + /// \return Success or error virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; ///< Skip the animation for the given MW-reference for one frame. Calls to this function for @@ -211,6 +218,8 @@ namespace MWBase /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; + + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim) = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f8bf157c2..fb7eca4a3 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -148,6 +148,9 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; + /// Make the player use an item, while updating GUI state accordingly + virtual void useItem(const MWWorld::Ptr& item) = 0; + virtual void updateSpellWindow() = 0; virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6e5029cc3..1622e1537 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -185,7 +185,7 @@ namespace MWBase virtual void disable (const MWWorld::Ptr& ptr) = 0; - virtual void advanceTime (double hours) = 0; + virtual void advanceTime (double hours, bool incremental = false) = 0; ///< Advance in-game time. virtual void setHour (double hour) = 0; @@ -256,6 +256,7 @@ namespace MWBase virtual void fixPosition (const MWWorld::Ptr& actor) = 0; ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; @@ -540,6 +541,10 @@ namespace MWBase virtual void resetActors() = 0; virtual bool isWalkingOnWater (const MWWorld::Ptr& actor) = 0; + + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 4cf33ceb8..3a0f1b951 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,4 +1,3 @@ - #include "activator.hpp" #include diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp new file mode 100644 index 000000000..d58047d06 --- /dev/null +++ b/apps/openmw/mwclass/actor.cpp @@ -0,0 +1,87 @@ +#include "actor.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/movement.hpp" +#include "../mwmechanics/magiceffects.hpp" + +#include "../mwphysics/physicssystem.hpp" + +#include "../mwworld/inventorystore.hpp" + +namespace MWClass +{ + Actor::Actor() {} + + Actor::~Actor() {} + + void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const + { + MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); + } + + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + if (!model.empty()) + { + physics.addActor(ptr, model); + if (getCreatureStats(ptr).isDead()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); + } + MWBase::Environment::get().getMechanicsManager()->add(ptr); + } + + void Actor::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; + } + } + + bool Actor::hasToolTip(const MWWorld::Ptr& ptr) const + { + return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); + } + + osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const + { + MWMechanics::Movement &movement = getMovementSettings(ptr); + osg::Vec3f vec(movement.mRotation[0], movement.mRotation[1], movement.mRotation[2]); + movement.mRotation[0] = 0.0f; + movement.mRotation[1] = 0.0f; + movement.mRotation[2] = 0.0f; + return vec; + } + + float Actor::getEncumbrance(const MWWorld::Ptr& ptr) const + { + float weight = getContainerStore(ptr).getWeight(); + const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects(); + weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); + weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); + return (weight < 0) ? 0.0f : weight; + } +} diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp new file mode 100644 index 000000000..3f795dff9 --- /dev/null +++ b/apps/openmw/mwclass/actor.hpp @@ -0,0 +1,47 @@ +#ifndef GAME_MWCLASS_MOBILE_H +#define GAME_MWCLASS_MOBILE_H + +#include "../mwworld/class.hpp" + +namespace ESM +{ + struct GameSetting; +} + +namespace MWClass +{ + /// \brief Class holding functionality common to Creature and NPC + class Actor : public MWWorld::Class + { + protected: + + Actor(); + + public: + virtual ~Actor(); + + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; + ///< Adjust position to stand on ground. Must be called post model load + /// @param force do this even if the ptr is flying + + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; + + virtual void block(const MWWorld::Ptr &ptr) const; + + virtual bool hasToolTip(const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const; + ///< Return desired rotations, as euler angles. + + virtual float getEncumbrance(const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + + // not implemented + Actor(const Actor&); + Actor& operator= (const Actor&); + }; +} + +#endif diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 6f11a36c7..f93556ef9 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -1,4 +1,3 @@ - #include "apparatus.hpp" #include diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 04c98e437..324dd32ee 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -1,4 +1,3 @@ - #include "armor.hpp" #include @@ -21,6 +20,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwgui/tooltips.hpp" @@ -245,7 +245,7 @@ namespace MWClass typeText = "#{sHeavy}"; text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(getEffectiveArmorRating(ptr, - MWBase::Environment::get().getWorld()->getPlayerPtr())); + MWMechanics::getPlayer())); int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index e9538a6cb..c303b23af 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -1,4 +1,3 @@ - #include "classes.hpp" #include "activator.hpp" diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 8964b65e0..cea30d561 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -1,4 +1,3 @@ - #include "clothing.hpp" #include diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 862ae6c5d..f785797c1 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -1,4 +1,3 @@ - #include "container.hpp" #include @@ -281,9 +280,12 @@ namespace MWClass ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } + bool Container::canLock(const MWWorld::Ptr &ptr) const + { + return true; + } - MWWorld::Ptr - Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -291,8 +293,7 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } - void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const + void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { const ESM::ContainerState& state2 = dynamic_cast (state); @@ -307,8 +308,7 @@ namespace MWClass readState (state2.mInventory); } - void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const + void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const { ESM::ContainerState& state2 = dynamic_cast (state); diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 3268d45d1..3541937d1 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -57,6 +57,8 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + virtual bool canLock(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. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index f312a41c7..450889eb2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,4 +1,3 @@ - #include "creature.hpp" #include @@ -37,6 +36,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/actorutil.hpp" namespace { @@ -58,6 +58,11 @@ namespace cloned->mContainerStore = mContainerStore->clone(); return cloned; } + + bool isFlagBitSet(const MWWorld::Ptr &ptr, ESM::Creature::Flags bitMask) + { + return (ptr.get()->mBase->mFlags & bitMask) != 0; + } } namespace MWClass @@ -129,7 +134,8 @@ namespace MWClass } // inventory - if (ref->mBase->mFlags & ESM::Creature::Weapon) + bool hasInventory = hasInventoryStore(ptr); + if (hasInventory) data->mContainerStore = new MWWorld::InventoryStore(); else data->mContainerStore = new MWWorld::ContainerStore(); @@ -143,7 +149,7 @@ namespace MWClass getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr)); - if (ref->mBase->mFlags & ESM::Creature::Weapon) + if (hasInventory) getInventoryStore(ptr).autoEquip(ptr); } } @@ -156,28 +162,10 @@ namespace MWClass return ref->mBase->mId; } - void Creature::adjustPosition(const MWWorld::Ptr& ptr, bool force) const - { - MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); - } - void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = ptr.get(); - MWRender::Objects& objects = renderingInterface.getObjects(); - objects.insertCreature(ptr, model, (ref->mBase->mFlags & ESM::Creature::Weapon) != 0); - } - - void Creature::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - if(!model.empty()) - { - physics.addActor(ptr, model); - if (getCreatureStats(ptr).isDead()) - MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); - } - MWBase::Environment::get().getMechanicsManager()->add(ptr); + objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } std::string Creature::getModel(const MWWorld::Ptr &ptr) const @@ -345,7 +333,7 @@ namespace MWClass if(!object.isEmpty()) getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); - if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const std::string &script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -394,8 +382,10 @@ namespace MWClass damage = scaleDamage(damage, attacker, ptr); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); - float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; - setActorHealth(ptr, health, attacker); + + MWMechanics::DynamicStat health(getCreatureStats(ptr).getHealth()); + health.setCurrent(health.getCurrent() - damage); + getCreatureStats(ptr).setHealth(health); } else { @@ -406,50 +396,6 @@ namespace MWClass } } - 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)) - { - 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 Creature::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const - { - MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); - bool wasDead = crstats.isDead(); - - MWMechanics::DynamicStat stat(crstats.getHealth()); - stat.setCurrent(health); - crstats.setHealth(stat); - - if(!wasDead && crstats.isDead()) - { - // actor was just killed - } - else if(wasDead && !crstats.isDead()) - { - // actor was just resurrected - } - } - - boost::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { @@ -480,9 +426,7 @@ namespace MWClass MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); - - if (ref->mBase->mFlags & ESM::Creature::Weapon) + if (hasInventoryStore(ptr)) return dynamic_cast(getContainerStore(ptr)); else throw std::runtime_error("this creature has no inventory store"); @@ -490,9 +434,7 @@ namespace MWClass bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); - - return (ref->mBase->mFlags & ESM::Creature::Weapon) != 0; + return isFlagBitSet(ptr, ESM::Creature::Weapon); } std::string Creature::getScript (const MWWorld::Ptr& ptr) const @@ -504,10 +446,7 @@ namespace MWClass bool Creature::isEssential (const MWWorld::Ptr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return (ref->mBase->mFlags & ESM::Creature::Essential) != 0; + return isFlagBitSet(ptr, ESM::Creature::Essential); } void Creature::registerSelf() @@ -517,11 +456,6 @@ namespace MWClass registerClass (typeid (ESM::Creature).name(), instance); } - bool Creature::hasToolTip (const MWWorld::Ptr& ptr) const - { - return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); - } - float Creature::getSpeed(const MWWorld::Ptr &ptr) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); @@ -580,16 +514,6 @@ namespace MWClass return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; } - osg::Vec3f Creature::getRotationVector (const MWWorld::Ptr& ptr) const - { - MWMechanics::Movement &movement = getMovementSettings(ptr); - osg::Vec3f vec(movement.mRotation[0], movement.mRotation[1], movement.mRotation[2]); - movement.mRotation[0] = 0.0f; - movement.mRotation[1] = 0.0f; - movement.mRotation[2] = 0.0f; - return vec; - } - MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = @@ -618,23 +542,6 @@ namespace MWClass return static_cast(stats.getAttribute(0).getModified() * 5); } - float Creature::getEncumbrance (const MWWorld::Ptr& ptr) const - { - float weight = getContainerStore (ptr).getWeight(); - - const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - - weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).getMagnitude(); - - weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).getMagnitude(); - - if (weight<0) - weight = 0; - - return weight; - } - - int Creature::getServices(const MWWorld::Ptr &actor) const { MWWorld::LiveCellRef* ref = actor.get(); @@ -691,34 +598,22 @@ namespace MWClass bool Creature::isBipedal(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mFlags & ESM::Creature::Bipedal; + return isFlagBitSet(ptr, ESM::Creature::Bipedal); } bool Creature::canFly(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return (ref->mBase->mFlags & ESM::Creature::Flies) != 0; + return isFlagBitSet(ptr, ESM::Creature::Flies); } bool Creature::canSwim(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mFlags & ESM::Creature::Swims || ref->mBase->mFlags & ESM::Creature::Bipedal; + return isFlagBitSet(ptr, static_cast(ESM::Creature::Swims | ESM::Creature::Bipedal)); } bool Creature::canWalk(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mFlags & ESM::Creature::Walks || ref->mBase->mFlags & ESM::Creature::Bipedal; + return isFlagBitSet(ptr, static_cast(ESM::Creature::Walks | ESM::Creature::Bipedal)); } int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) @@ -781,11 +676,11 @@ namespace MWClass int Creature::getBloodTexture(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + int flags = ptr.get()->mBase->mFlags; - if (ref->mBase->mFlags & ESM::Creature::Skeleton) + if (flags & ESM::Creature::Skeleton) return 1; - if (ref->mBase->mFlags & ESM::Creature::Metal) + if (flags & ESM::Creature::Metal) return 2; return 0; } @@ -805,9 +700,7 @@ namespace MWClass // Create a CustomData, but don't fill it from ESM records (not needed) std::auto_ptr data (new CreatureCustomData); - MWWorld::LiveCellRef *ref = ptr.get(); - - if (ref->mBase->mFlags & ESM::Creature::Weapon) + if (hasInventoryStore(ptr)) data->mContainerStore = new MWWorld::InventoryStore(); else data->mContainerStore = new MWWorld::ContainerStore(); @@ -850,7 +743,7 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr &ptr) const { - if (ptr.get()->mBase->mFlags & ESM::Creature::Respawn) + if (isFlagBitSet(ptr, ESM::Creature::Respawn)) { // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. // This also means we cannot respawn dynamically placed references with no content file connection. diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 740552a60..c4ea09255 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H -#include "../mwworld/class.hpp" +#include "actor.hpp" namespace ESM { @@ -10,7 +10,7 @@ namespace ESM namespace MWClass { - class Creature : public MWWorld::Class + class Creature : public Actor { void ensureCustomData (const MWWorld::Ptr& ptr) const; @@ -47,19 +47,10 @@ namespace MWClass virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - - virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; - ///< Adjust position to stand on ground. Must be called post model load - /// @param force do this even if the ptr is flying - virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; - ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. @@ -68,12 +59,8 @@ namespace MWClass virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, 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; - virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation @@ -94,10 +81,6 @@ namespace MWClass ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. - virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; - ///< Returns total weight of objects inside this object (including modifications from magic - /// effects). Throws an exception, if the object can't hold other objects. - virtual float getArmorRating (const MWWorld::Ptr& ptr) const; ///< @return combined armor rating of this actor @@ -113,9 +96,6 @@ namespace MWClass virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. - virtual osg::Vec3f getRotationVector (const MWWorld::Ptr& ptr) const; - ///< Return desired rotations, as euler angles. - float getSpeed (const MWWorld::Ptr& ptr) const; static void registerSelf(); diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index dbc4b6af7..433e5fcea 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,4 +1,3 @@ - #include "creaturelevlist.hpp" #include diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 5a8c736d9..18c381e13 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,4 +1,3 @@ - #include "door.hpp" #include @@ -27,6 +26,8 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwmechanics/actorutil.hpp" + namespace { struct DoorCustomData : public MWWorld::CustomData @@ -127,7 +128,7 @@ namespace MWClass if (needKey && hasKey) { - if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); unlock(ptr); //Call the function here. because that makes sense. // using a key disarms the trap @@ -207,6 +208,11 @@ namespace MWClass ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } + bool Door::canLock(const MWWorld::Ptr &ptr) const + { + return true; + } + std::string Door::getScript (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 9cfb46509..db5a7f32a 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -47,6 +47,8 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + virtual bool canLock(const MWWorld::Ptr &ptr) const; + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index fb409cb55..c9e6e70f2 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -1,4 +1,3 @@ - #include "ingredient.hpp" #include diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index d31080bb2..a70f31115 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -1,4 +1,3 @@ - #include "itemlevlist.hpp" #include diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 1e882b568..f1dd18acc 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,8 +1,8 @@ - #include "light.hpp" #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -170,6 +170,8 @@ namespace MWClass std::string text; + if (Settings::Manager::getBool("show effect duration","Game")) + text += "\n#{sDuration}: " + MWGui::ToolTips::toString(ptr.getClass().getRemainingUsageTime(ptr)); if (ref->mBase->mData.mWeight != 0) { text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 8f22c3fa1..5cffdf13a 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,4 +1,3 @@ - #include "lockpick.hpp" #include diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index b7c39b50a..98b4faab9 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,4 +1,3 @@ - #include "misc.hpp" #include @@ -25,20 +24,16 @@ #include -namespace -{ -bool isGold (const MWWorld::Ptr& ptr) -{ - return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); -} -} - namespace MWClass { + bool Miscellaneous::isGold (const MWWorld::Ptr& ptr) const + { + return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); + } std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const { return ptr.get()->mBase->mId; diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 66699f9df..394c9ffc0 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -62,6 +62,8 @@ namespace MWClass virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; virtual bool isKey (const MWWorld::Ptr &ptr) const; + + virtual bool isGold (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c7b407fb8..9fa2cc603 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,4 +1,3 @@ - #include "npc.hpp" #include @@ -25,6 +24,7 @@ #include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/character.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -402,24 +402,11 @@ namespace MWClass return ref->mBase->mId; } - void Npc::adjustPosition(const MWWorld::Ptr& ptr, bool force) const - { - MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); - } - void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { renderingInterface.getObjects().insertNPC(ptr); } - void Npc::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - physics.addActor(ptr, model); - MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (getCreatureStats(ptr).isDead()) - MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); - } - bool Npc::isPersistent(const MWWorld::Ptr &actor) const { MWWorld::LiveCellRef* ref = actor.get(); @@ -504,7 +491,7 @@ namespace MWClass if(otherstats.isDead()) // Can't hit dead actors return; - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::HandToHand; @@ -543,7 +530,7 @@ namespace MWClass { MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr == MWMechanics::getPlayer()) { skillUsageSucceeded(ptr, weapskill, 0); @@ -609,7 +596,7 @@ namespace MWClass if(!object.isEmpty()) getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); - if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const std::string &script = ptr.getClass().getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -701,7 +688,7 @@ namespace MWClass if (armorhealth == 0) armor = *inv.unequipItem(armor, ptr); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); switch(armor.getClass().getEquipmentSkill(armor)) @@ -717,7 +704,7 @@ namespace MWClass break; } } - else if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + else if(ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); } } @@ -730,11 +717,12 @@ namespace MWClass if(damage > 0.0f) { sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(); } - float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; - setActorHealth(ptr, health, attacker); + MWMechanics::DynamicStat health(getCreatureStats(ptr).getHealth()); + health.setCurrent(health.getCurrent() - damage); + getCreatureStats(ptr).setHealth(health); } else { @@ -751,64 +739,15 @@ namespace MWClass attacker.getClass().getNpcStats(attacker).addWerewolfKill(); } - // Simple check for who attacked first: if the player attacked first, a crimeId should be set - // Doesn't handle possible edge case where no one reported the assault, but in such a case, - // for bystanders it is not possible to tell who attacked first, anyway. - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (attacker == player && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker); } } - 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); - bool wasDead = crstats.isDead(); - - MWMechanics::DynamicStat stat(crstats.getHealth()); - stat.setCurrent(health); - crstats.setHealth(stat); - - if(!wasDead && crstats.isDead()) - { - // actor was just killed - } - else if(wasDead && !crstats.isDead()) - { - // actor was just resurrected - } - } - - boost::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { // player got activated by another NPC - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr == MWMechanics::getPlayer()) return boost::shared_ptr(new MWWorld::ActionTalk(actor)); // Werewolfs can't activate NPCs @@ -961,16 +900,6 @@ namespace MWClass return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; } - osg::Vec3f Npc::getRotationVector (const MWWorld::Ptr& ptr) const - { - MWMechanics::Movement &movement = getMovementSettings(ptr); - osg::Vec3f vec(movement.mRotation[0], movement.mRotation[1], movement.mRotation[2]); - movement.mRotation[0] = 0.0f; - movement.mRotation[1] = 0.0f; - movement.mRotation[2] = 0.0f; - return vec; - } - bool Npc::isEssential (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = @@ -985,11 +914,6 @@ namespace MWClass registerClass (typeid (ESM::NPC).name(), instance); } - bool Npc::hasToolTip (const MWWorld::Ptr& ptr) const - { - return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); - } - MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -1020,21 +944,9 @@ namespace MWClass float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const { - const MWMechanics::NpcStats &stats = getNpcStats(ptr); - // According to UESP, inventory weight is ignored in werewolf form. Does that include // feather and burden effects? - float weight = 0.0f; - if(!stats.isWerewolf()) - { - weight = getContainerStore(ptr).getWeight(); - weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).getMagnitude(); - weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).getMagnitude(); - if(weight < 0.0f) - weight = 0.0f; - } - - return weight; + return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr); } bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index f032ae77c..d919131db 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWCLASS_NPC_H #define GAME_MWCLASS_NPC_H -#include "../mwworld/class.hpp" +#include "actor.hpp" namespace ESM { @@ -10,7 +10,7 @@ namespace ESM namespace MWClass { - class Npc : public MWWorld::Class + class Npc : public Actor { void ensureCustomData (const MWWorld::Ptr& ptr) const; @@ -51,12 +51,6 @@ namespace MWClass virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - - virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; - ///< Adjust position to stand on ground. Must be called post model load - /// @param force do this even if the ptr is flying - virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -70,9 +64,6 @@ namespace MWClass virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; - ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. @@ -85,10 +76,6 @@ namespace MWClass 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, const MWWorld::Ptr& actor) const; ///< Generate action for activation @@ -105,9 +92,6 @@ namespace MWClass virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. - virtual osg::Vec3f getRotationVector (const MWWorld::Ptr& ptr) const; - ///< Return desired rotations, as euler angles. - virtual float getCapacity (const MWWorld::Ptr& ptr) const; ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 647f83f67..cf6b0919b 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -1,4 +1,3 @@ - #include "potion.hpp" #include diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index cb43ccce6..ff717c506 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,4 +1,3 @@ - #include "probe.hpp" #include diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 0bc64a99e..e6baea2e0 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -1,4 +1,3 @@ - #include "repair.hpp" #include diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 6438046de..9755df28e 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,4 +1,3 @@ - #include "static.hpp" #include diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 8c3d7fb10..da4c7deb2 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,4 +1,3 @@ - #include "weapon.hpp" #include diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 042267ebe..993dde6e4 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "dialoguemanagerimp.hpp" #include @@ -42,6 +41,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "filter.hpp" #include "hypertextparser.hpp" @@ -533,7 +533,7 @@ namespace MWDialogue else if (curDisp + mTemporaryDispositionChange > 100) mTemporaryDispositionChange = 100 - curDisp; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); if (success) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index adb7d3892..e3a773b05 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include @@ -18,6 +17,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" +#include "../mwmechanics/actorutil.hpp" #include "selectwrapper.hpp" @@ -98,7 +98,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()->getPlayerPtr(); + const MWWorld::Ptr player = MWMechanics::getPlayer(); // check player faction if (!info.mPcFaction.empty()) @@ -203,6 +203,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c return false; // script does not have a variable of this name. int index = localDefs.getIndex (name); + if (index < 0) + return false; // shouldn't happen, we checked that variable has a type above, so must exist const MWScript::Locals& locals = mActor.getRefData().getLocals(); @@ -218,7 +220,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / player.getClass().getCreatureStats (player).getHealth().getModified(); @@ -228,7 +230,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcDynamicStat: { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); float value = player.getClass().getCreatureStats (player). getDynamic (select.getArgument()).getCurrent(); @@ -252,7 +254,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); switch (select.getFunction()) { @@ -428,7 +430,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); switch (select.getFunction()) { @@ -531,7 +533,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_ShouldAttack: return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, - MWBase::Environment::get().getWorld()->getPlayerPtr()); + MWMechanics::getPlayer()); case SelectWrapper::Function_Werewolf: diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 9f07f7b6f..2f5f02b01 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 99dab0cf8..e6ffe22ab 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -1,4 +1,3 @@ - #include "journalimp.hpp" #include diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index a9e39b379..846597886 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,4 +1,3 @@ - #include "quest.hpp" #include diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index fa0fbfe13..a4eba30ae 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -1,4 +1,3 @@ - #include "selectwrapper.hpp" #include diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index c1a45f841..eb7fbdc1d 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -1,4 +1,3 @@ - #include "topic.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 768ad82e4..d12c22e06 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -1,7 +1,5 @@ #include "alchemywindow.hpp" -#include - #include #include "../mwbase/environment.hpp" @@ -11,6 +9,7 @@ #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -66,9 +65,6 @@ namespace MWGui void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) { - std::string name = mNameEdit->getCaption(); - boost::algorithm::trim(name); - MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); if (result == MWMechanics::Alchemy::Result_NoName) @@ -117,16 +113,16 @@ namespace MWGui MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); if (ingred.getRefData().getCount() == 0) removeIngredient(mIngredients[i]); - } + } update(); } void AlchemyWindow::open() { - mAlchemy->setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); + mAlchemy->setAlchemist (MWMechanics::getPlayer()); - InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr()); + InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer()); mSortModel = new SortFilterItemModel(model); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mItemView->setModel (mSortModel); @@ -135,9 +131,6 @@ namespace MWGui mNameEdit->setCaption(""); int index = 0; - - 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 1122a4069..de8614004 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 44a988523..f3cefb787 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -9,6 +9,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/actiontake.hpp" #include "formatting.hpp" @@ -123,7 +125,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()->getPlayerPtr()); + take.execute (MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 73b950a6a..d2ce35509 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -7,6 +7,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" @@ -51,7 +52,7 @@ namespace void updatePlayerHealth() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); npcStats.updateHealth(); } @@ -132,134 +133,141 @@ namespace MWGui void CharacterCreation::spawnDialog(const char id) { - switch (id) + try { - case GM_Name: - MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); - mNameDialog = 0; - mNameDialog = new TextInputDialog(); - mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); - mNameDialog->setTextInput(mPlayerName); - mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); - mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); - mNameDialog->setVisible(true); - break; + switch (id) + { + case GM_Name: + MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); + mNameDialog = 0; + mNameDialog = new TextInputDialog(); + mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); + mNameDialog->setTextInput(mPlayerName); + mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); + mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); + mNameDialog->setVisible(true); + break; - case GM_Race: - MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = 0; - mRaceDialog = new RaceDialog(mViewer, mResourceSystem); - mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); - mRaceDialog->setRaceId(mPlayerRaceId); - mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); - mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack); - mRaceDialog->setVisible(true); - if (mCreationStage < CSE_NameChosen) - mCreationStage = CSE_NameChosen; - break; + case GM_Race: + MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); + mRaceDialog = 0; + mRaceDialog = new RaceDialog(mViewer, mResourceSystem); + mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); + mRaceDialog->setRaceId(mPlayerRaceId); + mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); + mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack); + mRaceDialog->setVisible(true); + if (mCreationStage < CSE_NameChosen) + mCreationStage = CSE_NameChosen; + break; - case GM_Class: - MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); - mClassChoiceDialog = 0; - mClassChoiceDialog = new ClassChoiceDialog(); - mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); - mClassChoiceDialog->setVisible(true); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; + case GM_Class: + MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); + mClassChoiceDialog = 0; + mClassChoiceDialog = new ClassChoiceDialog(); + mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); + mClassChoiceDialog->setVisible(true); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; - case GM_ClassPick: - MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = 0; - mPickClassDialog = new PickClassDialog(); - mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); - mPickClassDialog->setClassId(mPlayerClass.mName); - mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); - mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); - mPickClassDialog->setVisible(true); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; + case GM_ClassPick: + MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); + mPickClassDialog = 0; + mPickClassDialog = new PickClassDialog(); + mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); + mPickClassDialog->setClassId(mPlayerClass.mName); + mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); + mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); + mPickClassDialog->setVisible(true); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; - case GM_Birth: - MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = 0; - mBirthSignDialog = new BirthDialog(); - mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); - mBirthSignDialog->setBirthId(mPlayerBirthSignId); - mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); - mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); - mBirthSignDialog->setVisible(true); - if (mCreationStage < CSE_ClassChosen) - mCreationStage = CSE_ClassChosen; - break; + case GM_Birth: + MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); + mBirthSignDialog = 0; + mBirthSignDialog = new BirthDialog(); + mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); + mBirthSignDialog->setBirthId(mPlayerBirthSignId); + mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); + mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); + mBirthSignDialog->setVisible(true); + if (mCreationStage < CSE_ClassChosen) + mCreationStage = CSE_ClassChosen; + break; - case GM_ClassCreate: - if (!mCreateClassDialog) - { - mCreateClassDialog = new CreateClassDialog(); - mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); - mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); - } - mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); - mCreateClassDialog->setVisible(true); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; - case GM_ClassGenerate: - mGenerateClassStep = 0; - mGenerateClass = ""; - mGenerateClassSpecializations[0] = 0; - mGenerateClassSpecializations[1] = 0; - mGenerateClassSpecializations[2] = 0; - showClassQuestionDialog(); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; - case GM_Review: - MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = 0; - mReviewDialog = new ReviewDialog(); - mReviewDialog->setPlayerName(mPlayerName); - mReviewDialog->setRace(mPlayerRaceId); - mReviewDialog->setClass(mPlayerClass); - mReviewDialog->setBirthSign(mPlayerBirthSignId); - - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::CreatureStats& stats = player.getClass().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(); - it != attributes.end(); ++it) + case GM_ClassCreate: + if (!mCreateClassDialog) { - mReviewDialog->setAttribute(static_cast (it->first), it->second); + mCreateClassDialog = new CreateClassDialog(); + mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); + mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); } - } + mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); + mCreateClassDialog->setVisible(true); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; + case GM_ClassGenerate: + mGenerateClassStep = 0; + mGenerateClass = ""; + mGenerateClassSpecializations[0] = 0; + mGenerateClassSpecializations[1] = 0; + mGenerateClassSpecializations[2] = 0; + showClassQuestionDialog(); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; + case GM_Review: + MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); + mReviewDialog = 0; + mReviewDialog = new ReviewDialog(); + mReviewDialog->setPlayerName(mPlayerName); + mReviewDialog->setRace(mPlayerRaceId); + mReviewDialog->setClass(mPlayerClass); + mReviewDialog->setBirthSign(mPlayerBirthSignId); - { - 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); - } - mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills()); - } + MWWorld::Ptr player = MWMechanics::getPlayer(); + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); - mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); - mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); - mReviewDialog->setVisible(true); - if (mCreationStage < CSE_BirthSignChosen) - mCreationStage = CSE_BirthSignChosen; - break; + 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(); + it != attributes.end(); ++it) + { + mReviewDialog->setAttribute(static_cast (it->first), it->second); + } + } + + { + 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); + } + mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills()); + } + + mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); + mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); + mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); + mReviewDialog->setVisible(true); + if (mCreationStage < CSE_BirthSignChosen) + mCreationStage = CSE_BirthSignChosen; + break; + } + } + catch (std::exception& e) + { + std::cerr << "Failed to create chargen window: " << e.what() << std::endl; } } @@ -305,7 +313,7 @@ namespace MWGui }; } - void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) + void CharacterCreation::selectPickedClass() { if (mPickClassDialog) { @@ -325,20 +333,18 @@ namespace MWGui } updatePlayerHealth(); + } + + void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) + { + selectPickedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } void CharacterCreation::onPickClassDialogBack() { - if (mPickClassDialog) - { - const std::string classId = mPickClassDialog->getClassId(); - if (!classId.empty()) - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId); - MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = 0; - } + selectPickedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); @@ -383,29 +389,7 @@ namespace MWGui handleDialogDone(CSE_NameChosen, GM_Race); } - void CharacterCreation::onRaceDialogBack() - { - if (mRaceDialog) - { - const ESM::NPC &data = mRaceDialog->getResult(); - mPlayerRaceId = data.mRace; - if (!mPlayerRaceId.empty()) { - MWBase::Environment::get().getMechanicsManager()->setPlayerRace( - data.mRace, - data.isMale(), - data.mHead, - data.mHair - ); - } - MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = 0; - } - - MWBase::Environment::get().getWindowManager()->popGuiMode(); - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); - } - - void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) + void CharacterCreation::selectRace() { if (mRaceDialog) { @@ -426,11 +410,24 @@ namespace MWGui } updatePlayerHealth(); + } + + void CharacterCreation::onRaceDialogBack() + { + selectRace(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); + } + + void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) + { + selectRace(); handleDialogDone(CSE_RaceChosen, GM_Class); } - void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) + void CharacterCreation::selectBirthSign() { if (mBirthSignDialog) { @@ -442,24 +439,24 @@ namespace MWGui } updatePlayerHealth(); + } + + void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) + { + selectBirthSign(); handleDialogDone(CSE_BirthSignChosen, GM_Review); } void CharacterCreation::onBirthSignDialogBack() { - if (mBirthSignDialog) - { - MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mBirthSignDialog->getBirthId()); - MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = 0; - } + selectBirthSign(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } - void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) + void CharacterCreation::selectCreatedClass() { if (mCreateClassDialog) { @@ -488,19 +485,23 @@ namespace MWGui mPlayerClass = klass; MWBase::Environment::get().getWindowManager()->setPlayerClass(klass); - // Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later + // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); } - updatePlayerHealth(); + } + + void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) + { + selectCreatedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } void CharacterCreation::onCreateClassDialogBack() { - // Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later - mCreateClassDialog->setVisible(false); + // not done in MW, but we do it for consistency with the other dialogs + selectCreatedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); @@ -624,18 +625,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps(mGenerateClassStep).mSound); } - void CharacterCreation::onGenerateClassBack() - { - MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); - mGenerateClassResultDialog = 0; - - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); - - MWBase::Environment::get().getWindowManager()->popGuiMode(); - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); - } - - void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) + void CharacterCreation::selectGeneratedClass() { MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); mGenerateClassResultDialog = 0; @@ -649,6 +639,19 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass); updatePlayerHealth(); + } + + void CharacterCreation::onGenerateClassBack() + { + selectGeneratedClass(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); + } + + void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) + { + selectGeneratedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index f6e7c6c92..7fb67caf6 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -83,6 +83,7 @@ namespace MWGui //Race dialog void onRaceDialogDone(WindowBase* parWindow); void onRaceDialogBack(); + void selectRace(); //Class dialogs void onClassChoice(int _index); @@ -94,10 +95,14 @@ namespace MWGui void onClassQuestionChosen(int _index); void onGenerateClassBack(); void onGenerateClassDone(WindowBase* parWindow); + void selectGeneratedClass(); + void selectCreatedClass(); + void selectPickedClass(); //Birthsign dialog void onBirthSignDialogDone(WindowBase* parWindow); void onBirthSignDialogBack(); + void selectBirthSign(); //Review dialog void onReviewDialogDone(WindowBase* parWindow); diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 983ef5017..0ff0b648e 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -1,6 +1,5 @@ #include "companionitemmodel.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" namespace diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index b61d46400..fc4a98489 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -3,11 +3,8 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwworld/class.hpp" #include "messagebox.hpp" diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 2f874119d..5d71fc445 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -9,15 +9,14 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwmechanics/pickpocket.hpp" #include "../mwmechanics/creaturestats.hpp" #include "countdialog.hpp" -#include "tradewindow.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" @@ -139,7 +138,7 @@ namespace MWGui if (mPtr.getTypeName() == typeid(ESM::NPC).name() && !loot) { // we are stealing stuff - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); mModel = new PickpocketItemModel(player, new InventoryItemModel(container), !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); } @@ -185,7 +184,7 @@ namespace MWGui && !mPickpocketDetected ) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::Pickpocket pickpocket(player, mPtr); if (pickpocket.finish()) { @@ -262,7 +261,7 @@ namespace MWGui bool ContainerWindow::onTakeItem(const ItemStack &item, int count) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); // TODO: move to ItemModels if (dynamic_cast(mModel) && !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()) diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index a6dab66c1..37ea3470d 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -18,9 +18,9 @@ namespace float accumulated_time=0,parent_time = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); int i,j; int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); - for (i=0;iGet_Current_Parent_Name())+" (total running time: "+MyGUI::utility::toString(parent_time,3)+" ms) ---\n"; os << s; @@ -35,7 +35,7 @@ namespace accumulated_time += current_total_time; float fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; - for (j=0;jGet_Current_Name()+" ("+MyGUI::utility::toString(fraction,2)+" %) :: "+MyGUI::utility::toString(ms,3)+" ms / frame ("+MyGUI::utility::toString(pit->Get_Current_Total_Calls())+" calls)\n"; os << s; @@ -47,7 +47,7 @@ namespace { os << "what's wrong\n"; } - for (i=0;i SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; s = "Unaccounted: ("+MyGUI::utility::toString(unaccounted,3)+" %) :: "+MyGUI::utility::toString(parent_time - accumulated_time,3)+" ms\n"; os << s; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 0cb0475c9..d32588631 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -16,12 +16,13 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "widgets.hpp" #include "bookpage.hpp" @@ -30,7 +31,7 @@ namespace { - MyGUI::Colour getTextColour (const std::string& type) + MyGUI::Colour getDialogueTextColour (const std::string& type) { return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); } @@ -89,7 +90,7 @@ namespace MWGui WindowModal::open(); center(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mBribe10Button->setEnabled (playerGold >= 10); @@ -114,7 +115,7 @@ namespace MWGui void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { - BookTypesetter::Style* title = typesetter->createStyle("", getTextColour("header")); + BookTypesetter::Style* title = typesetter->createStyle("", getDialogueTextColour("header")); typesetter->sectionBreak(9); if (mTitle != "") typesetter->write(title, to_utf8_span(mTitle.c_str())); @@ -158,14 +159,14 @@ namespace MWGui if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) { - BookTypesetter::Style* style = typesetter->createStyle("", getTextColour("normal")); + BookTypesetter::Style* style = typesetter->createStyle("", getDialogueTextColour("normal")); size_t formatted = 0; // points to the first character that is not laid out yet for (std::map::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it) { intptr_t topicId = it->second; - const MyGUI::Colour linkHot (getTextColour("link_over")); - const MyGUI::Colour linkNormal (getTextColour("link")); - const MyGUI::Colour linkActive (getTextColour("link_pressed")); + const MyGUI::Colour linkHot(getDialogueTextColour("link_over")); + const MyGUI::Colour linkNormal(getDialogueTextColour("link")); + const MyGUI::Colour linkActive(getDialogueTextColour("link_pressed")); BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); if (formatted < it->first.first) typesetter->write(style, formatted, it->first.first); @@ -198,11 +199,11 @@ namespace MWGui void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const { - BookTypesetter::Style* style = typesetter->createStyle("", getTextColour("normal")); + BookTypesetter::Style* style = typesetter->createStyle("", getDialogueTextColour("normal")); - const MyGUI::Colour linkHot (getTextColour("link_over")); - const MyGUI::Colour linkNormal (getTextColour("link")); - const MyGUI::Colour linkActive (getTextColour("link_pressed")); + const MyGUI::Colour linkHot(getDialogueTextColour("link_over")); + const MyGUI::Colour linkNormal(getDialogueTextColour("link")); + const MyGUI::Colour linkActive(getDialogueTextColour("link_pressed")); if (topicId) style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); @@ -216,7 +217,7 @@ namespace MWGui void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { - BookTypesetter::Style* title = typesetter->createStyle("", getTextColour("notify")); + BookTypesetter::Style* title = typesetter->createStyle("", getDialogueTextColour("notify")); typesetter->sectionBreak(9); typesetter->write(title, to_utf8_span(mText.c_str())); } @@ -485,9 +486,9 @@ namespace MWGui typesetter->sectionBreak(9); // choices - const MyGUI::Colour linkHot (getTextColour("answer_over")); - const MyGUI::Colour linkNormal (getTextColour("answer")); - const MyGUI::Colour linkActive (getTextColour("answer_pressed")); + const MyGUI::Colour linkHot(getDialogueTextColour("answer_over")); + const MyGUI::Colour linkNormal(getDialogueTextColour("answer")); + const MyGUI::Colour linkActive(getDialogueTextColour("answer_pressed")); for (std::vector >::iterator it = mChoices.begin(); it != mChoices.end(); ++it) { Choice* link = new Choice(it->second); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 43f2493a9..c182a0a52 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include "../mwbase/environment.hpp" @@ -19,6 +18,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "itemselection.hpp" #include "itemwidget.hpp" @@ -161,7 +162,7 @@ namespace MWGui void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(player); @@ -209,7 +210,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()->getPlayerPtr()); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); } else @@ -264,7 +265,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()->getPlayerPtr()); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); @@ -325,7 +326,7 @@ namespace MWGui mEnchanting.setNewItemName(mName->getCaption()); mEnchanting.setEffect(mEffectList); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 76a7248ee..a93bb47e9 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -10,7 +10,6 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -19,6 +18,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "inventorywindow.hpp" #include "spellicons.hpp" @@ -260,7 +260,7 @@ namespace MWGui { // drop item into the gameworld MWBase::Environment::get().getWorld()->breakInvisibility( - MWBase::Environment::get().getWorld()->getPlayerPtr()); + MWMechanics::getPlayer()); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); @@ -339,7 +339,7 @@ namespace MWGui void HUD::onWeaponClicked(MyGUI::Widget* _sender) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); @@ -351,7 +351,7 @@ namespace MWGui void HUD::onMagicClicked(MyGUI::Widget* _sender) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); @@ -518,7 +518,19 @@ namespace MWGui { mCrosshair->setVisible (visible); } - + + void HUD::setCrosshairOwned(bool owned) + { + if(owned) + { + mCrosshair->changeWidgetSkin("HUD_Crosshair_Owned"); + } + else + { + mCrosshair->changeWidgetSkin("HUD_Crosshair"); + } + } + void HUD::setHmsVisible(bool visible) { mHealth->setVisible(visible); diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 72fc06f6a..629613c98 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -47,6 +47,7 @@ namespace MWGui void unsetSelectedWeapon(); void setCrosshairVisible(bool visible); + void setCrosshairOwned(bool owned); void onFrame(float dt); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index e8354b740..b80850ba1 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -4,8 +4,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/creaturestats.hpp" - namespace MWGui { diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 392a4a84a..f5a5f023f 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -16,21 +16,21 @@ #include -#include - #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" #include "../mwscript/interpretercontext.hpp" -#include "../mwbase/scriptmanager.hpp" #include "../mwrender/characterpreview.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" @@ -65,7 +65,7 @@ namespace MWGui , mGuiMode(GM_Inventory) , mLastXSize(0) , mLastYSize(0) - , mPreview(new MWRender::InventoryPreview(viewer, resourceSystem, MWBase::Environment::get().getWorld()->getPlayerPtr())) + , mPreview(new MWRender::InventoryPreview(viewer, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) { mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); @@ -121,7 +121,12 @@ namespace MWGui { mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); - mSortModel = new SortFilterItemModel(mTradeModel); + + if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings + mSortModel->setSourceModel(mTradeModel); + else + mSortModel = new SortFilterItemModel(mTradeModel); + mItemView->setModel(mSortModel); mPreview->updatePtr(mPtr); @@ -330,7 +335,7 @@ namespace MWGui void InventoryWindow::open() { - mPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mPtr = MWMechanics::getPlayer(); updateEncumbranceBar(); @@ -436,6 +441,20 @@ namespace MWGui { const std::string& script = ptr.getClass().getScript(ptr); + MWWorld::Ptr player = MWMechanics::getPlayer(); + + // early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case + if (!ptr.getClass().getEquipmentSlots(ptr).first.empty()) + { + std::pair canEquip = ptr.getClass().canBeEquipped(ptr, player); + if (canEquip.first == 0) + { + MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); + updateItemView(); + return; + } + } + // 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, @@ -455,7 +474,7 @@ namespace MWGui { boost::shared_ptr action = ptr.getClass().use(ptr); - action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); + action->execute (player); mSkippedToEquip = MWWorld::Ptr(); } @@ -532,7 +551,7 @@ namespace MWGui void InventoryWindow::updateEncumbranceBar() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); float capacity = player.getClass().getCapacity(player); float encumbrance = player.getClass().getEncumbrance(player); @@ -566,7 +585,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->updateSpellWindow(); MWBase::Environment::get().getMechanicsManager()->updateMagicEffects( - MWBase::Environment::get().getWorld()->getPlayerPtr()); + MWMechanics::getPlayer()); dirtyPreview(); } @@ -597,7 +616,7 @@ namespace MWGui int count = object.getRefData().getCount(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, object, MWWorld::Ptr(), count); @@ -627,7 +646,7 @@ namespace MWGui { ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering - SortFilterItemModel model(new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + SortFilterItemModel model(new InventoryItemModel(MWMechanics::getPlayer())); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 9fce6e84d..a1e36bce6 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -120,6 +120,11 @@ namespace MWGui } + ProxyItemModel::ProxyItemModel() + : mSourceModel(NULL) + { + } + ProxyItemModel::~ProxyItemModel() { delete mSourceModel; @@ -164,4 +169,18 @@ namespace MWGui return mSourceModel->getIndex(item); } + void ProxyItemModel::setSourceModel(ItemModel *sourceModel) + { + if (mSourceModel == sourceModel) + return; + + if (mSourceModel) + { + delete mSourceModel; + mSourceModel = NULL; + } + + mSourceModel = sourceModel; + } + } diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 53c7018e2..2019c1042 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -80,11 +80,15 @@ namespace MWGui class ProxyItemModel : public ItemModel { public: + ProxyItemModel(); virtual ~ProxyItemModel(); virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); + /// @note Takes ownership of the passed pointer. + void setSourceModel(ItemModel* sourceModel); + ModelIndex mapToSource (ModelIndex index); ModelIndex mapFromSource (ModelIndex index); protected: diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index df44eb33c..2cdfcada4 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -30,8 +30,12 @@ ItemView::~ItemView() void ItemView::setModel(ItemModel *model) { + if (mModel == model) + return; + delete mModel; mModel = model; + update(); } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index e4a3a3147..7eaf5eb30 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/store.hpp" @@ -57,7 +58,7 @@ namespace MWGui if (mFadeTimeRemaining <= 0) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->teleportToClosestMarker(player, "prisonmarker"); setVisible(true); @@ -76,7 +77,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Jail); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); for (int i=0; i @@ -182,6 +183,7 @@ namespace mQuestMode = false; mAllQuests = false; + mOptionsMode = false; } void adjustButton (char const * name, bool optional = false) @@ -244,6 +246,7 @@ namespace void setBookMode () { + mOptionsMode = false; setVisible (OptionsBTN, true); setVisible (OptionsOverlay, false); @@ -253,6 +256,8 @@ namespace void setOptionsMode () { + mOptionsMode = true; + setVisible (OptionsBTN, false); setVisible (OptionsOverlay, true); @@ -351,6 +356,8 @@ namespace setVisible (OptionsOverlay, false); setVisible (OptionsBTN, true); setVisible (JournalBTN, true); + + mOptionsMode = false; } void notifyTopicSelected (const std::string& topic, int id) @@ -378,6 +385,8 @@ namespace setVisible (OptionsOverlay, false); setVisible (OptionsBTN, true); setVisible (JournalBTN, true); + + mOptionsMode = false; } void notifyOptions(MyGUI::Widget* _sender) @@ -508,6 +517,8 @@ namespace void notifyNextPage(MyGUI::Widget* _sender) { + if (mOptionsMode) + return; if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; @@ -523,6 +534,8 @@ namespace void notifyPrevPage(MyGUI::Widget* _sender) { + if (mOptionsMode) + return; if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c392372ff..c832a7879 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWGui { @@ -203,7 +204,7 @@ namespace MWGui void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); if (mSpentAttributes.size() < mCoinCount) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 7e733686d..9b99ad7bf 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -17,9 +17,7 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/statemanager.hpp" - #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index e24894e89..258f0dfb0 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #include #include @@ -14,12 +12,8 @@ #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" #include "confirmationdialog.hpp" #include "backgroundimage.hpp" @@ -28,7 +22,7 @@ namespace MWGui { - MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs) + MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription) : Layout("openmw_mainmenu.layout") , mWidth (w), mHeight (h) , mVFS(vfs), mButtonBox(0) @@ -38,20 +32,7 @@ namespace MWGui , mSaveGameDialog(NULL) { getWidget(mVersionText, "VersionText"); - std::stringstream sstream; - sstream << "OpenMW Version: " << OPENMW_VERSION; - - // adding info about git hash if available - std::string rev = OPENMW_VERSION_COMMITHASH; - std::string tag = OPENMW_VERSION_TAGHASH; - if (!rev.empty() && !tag.empty()) - { - rev = rev.substr(0,10); - sstream << "\nRevision: " << rev; - } - - std::string output = sstream.str(); - mVersionText->setCaption(output); + mVersionText->setCaption(versionDescription); mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index d01f67fbd..fe256dc8e 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -29,7 +29,7 @@ namespace MWGui public: - MainMenu(int w, int h, const VFS::Manager* vfs); + MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription); ~MainMenu(); void onResChange(int w, int h); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 442fbeb08..2df1c8f3c 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -27,7 +27,6 @@ #include "../mwrender/globalmap.hpp" #include "../mwrender/localmap.hpp" -#include "widgets.hpp" #include "confirmationdialog.hpp" #include "tooltips.hpp" @@ -38,8 +37,8 @@ namespace enum LocalMapWidgetDepth { - Local_CompassLayer = 0, - Local_MarkerAboveFogLayer = 1, + Local_MarkerAboveFogLayer = 0, + Local_CompassLayer = 1, Local_FogLayer = 2, Local_MarkerLayer = 3, Local_MapLayer = 4 @@ -92,31 +91,41 @@ namespace MWGui void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) { - mMarkers.push_back(marker); + mMarkers.insert(std::make_pair(marker.mCell, marker)); if (triggerEvent) eventMarkersChanged(); } void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) { - std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); - if (it != mMarkers.end()) - mMarkers.erase(it); - else - throw std::runtime_error("can't find marker to delete"); + std::pair range = mMarkers.equal_range(marker.mCell); - eventMarkersChanged(); + for (ContainerType::iterator it = range.first; it != range.second; ++it) + { + if (it->second == marker) + { + mMarkers.erase(it); + eventMarkersChanged(); + return; + } + } + throw std::runtime_error("can't find marker to delete"); } void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) { - std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); - if (it != mMarkers.end()) - it->mNote = newNote; - else - throw std::runtime_error("can't find marker to update"); + std::pair range = mMarkers.equal_range(marker.mCell); - eventMarkersChanged(); + for (ContainerType::iterator it = range.first; it != range.second; ++it) + { + if (it->second == marker) + { + it->second.mNote = newNote; + eventMarkersChanged(); + return; + } + } + throw std::runtime_error("can't find marker to update"); } void CustomMarkerCollection::clear() @@ -125,16 +134,21 @@ namespace MWGui eventMarkersChanged(); } - std::vector::const_iterator CustomMarkerCollection::begin() const + CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::begin() const { return mMarkers.begin(); } - std::vector::const_iterator CustomMarkerCollection::end() const + CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::end() const { return mMarkers.end(); } + CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId &cellId) const + { + return mMarkers.equal_range(cellId); + } + size_t CustomMarkerCollection::size() const { return mMarkers.size(); @@ -299,44 +313,43 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(*it); mCustomMarkerWidgets.clear(); - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (int dX = -1; dX <= 1; ++dX) { - const ESM::CustomMarker& marker = *it; - - if (marker.mCell.mPaged != !mInterior) - continue; - if (mInterior) + for (int dY =-1; dY <= 1; ++dY) { - if (marker.mCell.mWorldspace != mPrefix) - continue; - } - else - { - if (std::abs(marker.mCell.mIndex.mX - mCurX) > 1) - continue; - if (std::abs(marker.mCell.mIndex.mY - mCurY) > 1) - continue; - } + ESM::CellId cellId; + cellId.mPaged = !mInterior; + cellId.mWorldspace = (mInterior ? mPrefix : "sys::default"); + cellId.mIndex.mX = mCurX+dX; + cellId.mIndex.mY = mCurY+dY; - MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + { + const ESM::CustomMarker& marker = it->second; - MyGUI::IntCoord widgetCoord(widgetPos.left - 8, - widgetPos.top - 8, - 16, 16); - MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", - widgetCoord, MyGUI::Align::Default); - markerWidget->setDepth(Local_MarkerAboveFogLayer); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); - markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); - markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); - markerWidget->setUserData(marker); - markerWidget->setNeedMouseFocus(true); - customMarkerCreated(markerWidget); - mCustomMarkerWidgets.push_back(markerWidget); + MarkerUserData markerPos (mLocalMapRender); + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); + + MyGUI::IntCoord widgetCoord(widgetPos.left - 8, + widgetPos.top - 8, + 16, 16); + MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", + widgetCoord, MyGUI::Align::Default); + markerWidget->setDepth(Local_MarkerAboveFogLayer); + markerWidget->setUserString("ToolTipType", "Layout"); + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); + markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); + markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); + markerWidget->setUserData(marker); + markerWidget->setNeedMouseFocus(true); + customMarkerCreated(markerWidget); + mCustomMarkerWidgets.push_back(markerWidget); + } + } } + redraw(); } @@ -411,11 +424,9 @@ namespace MWGui MWBase::World::DoorMarker marker = *it; std::vector destNotes; - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) - { - if (it->mCell == marker.dest) - destNotes.push_back(it->mNote); - } + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + destNotes.push_back(it->second.mNote); MarkerUserData data (mLocalMapRender); data.notes = destNotes; @@ -771,15 +782,19 @@ namespace MWGui MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", widgetCoord, MyGUI::Align::Default); + + markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); + + setGlobalMapMarkerTooltip(markerWidget, x, y); + + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + markerWidget->setNeedMouseFocus(true); markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", name); markerWidget->setDepth(Global_MarkerLayer); markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); - mGlobalMapMarkers.push_back(markerWidget); + mGlobalMapMarkers[std::make_pair(x,y)] = markerWidget; } } @@ -804,6 +819,45 @@ namespace MWGui NoDrop::onFrame(dt); } + void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y) + { + ESM::CellId cellId; + cellId.mIndex.mX = x; + cellId.mIndex.mY = y; + cellId.mWorldspace = "sys::default"; + cellId.mPaged = true; + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); + std::vector destNotes; + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + destNotes.push_back(it->second.mNote); + + if (!destNotes.empty()) + { + MarkerUserData data (NULL); + data.notes = destNotes; + data.caption = markerWidget->getUserString("Caption_TextOneLine"); + markerWidget->setUserData(data); + markerWidget->setUserString("ToolTipType", "MapMarker"); + } + else + { + markerWidget->setUserString("ToolTipType", "Layout"); + } + } + + void MapWindow::updateCustomMarkers() + { + LocalMapBase::updateCustomMarkers(); + + for (std::map, MyGUI::Widget*>::iterator widgetIt = mGlobalMapMarkers.begin(); widgetIt != mGlobalMapMarkers.end(); ++widgetIt) + { + int x = widgetIt->first.first; + int y = widgetIt->first.second; + MyGUI::Widget* markerWidget = widgetIt->second; + setGlobalMapMarkerTooltip(markerWidget, x, y); + } + } + void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; @@ -901,8 +955,8 @@ namespace MWGui mGlobalMapRender->clear(); mChanged = true; - for (std::vector::iterator it = mGlobalMapMarkers.begin(); it != mGlobalMapMarkers.end(); ++it) - MyGUI::Gui::getInstance().destroyWidget(*it); + for (std::map, MyGUI::Widget*>::iterator it = mGlobalMapMarkers.begin(); it != mGlobalMapMarkers.end(); ++it) + MyGUI::Gui::getInstance().destroyWidget(it->second); mGlobalMapMarkers.clear(); } @@ -1022,6 +1076,8 @@ namespace MWGui bool LocalMapBase::MarkerUserData::isPositionExplored() const { + if (!mLocalMapRender) + return true; return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY, interior); } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 388103b5d..a41d5d527 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -42,14 +42,20 @@ namespace MWGui size_t size() const; - std::vector::const_iterator begin() const; - std::vector::const_iterator end() const; + typedef std::multimap ContainerType; + + typedef std::pair RangeType; + + ContainerType::const_iterator begin() const; + ContainerType::const_iterator end() const; + + RangeType getMarkers(const ESM::CellId& cellId) const; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; EventHandle_Void eventMarkersChanged; private: - std::vector mMarkers; + ContainerType mMarkers; }; class LocalMapBase @@ -120,7 +126,7 @@ namespace MWGui std::vector mMagicMarkerWidgets; std::vector mCustomMarkerWidgets; - void updateCustomMarkers(); + virtual void updateCustomMarkers(); void applyFogOfWar(); @@ -184,7 +190,8 @@ namespace MWGui void renderGlobalMap(Loading::Listener* loadingListener); - // adds the marker to the global map + /// adds the marker to the global map + /// @param name The ESM::Cell::mName void addVisitedLocation(const std::string& name, int x, int y); // reveals this cell's map on the global map @@ -197,6 +204,8 @@ namespace MWGui void onFrame(float dt); + virtual void updateCustomMarkers(); + /// Clear all savegame-specific data void clear(); @@ -215,6 +224,7 @@ namespace MWGui void onNoteDoubleClicked(MyGUI::Widget* sender); void onChangeScrollWindowCoord(MyGUI::Widget* sender); void globalMapUpdatePlayer(); + void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y); MyGUI::ScrollView* mGlobalMap; std::auto_ptr mGlobalMapTexture; @@ -242,7 +252,7 @@ namespace MWGui MWRender::GlobalMap* mGlobalMapRender; - std::vector mGlobalMapMarkers; + std::map, MyGUI::Widget*> mGlobalMapMarkers; EditNoteDialog mEditNoteDialog; ESM::CustomMarker mEditingMarker; diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 862b719d4..481e1ceb4 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -13,6 +13,7 @@ #include "../mwbase/soundmanager.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -40,7 +41,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); @@ -125,7 +126,7 @@ void MerchantRepair::exit() void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int price = MyGUI::utility::parseInt(sender->getUserString("Price")); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index a0550ae25..ab0d02f95 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -1,8 +1,8 @@ #include "pickpocketitemmodel.hpp" #include +#include -#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" namespace MWGui diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 8c919e8bd..f2ae8dd83 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -15,18 +15,14 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" -#include "../mwgui/inventorywindow.hpp" - -#include "windowmanagerimp.hpp" #include "itemselection.hpp" - #include "spellview.hpp" - - #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" @@ -150,7 +146,7 @@ namespace MWGui mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel); } mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); mAssignDialog->setVisible (false); @@ -265,7 +261,7 @@ namespace MWGui QuickKeyType type = mAssigned[index-1]; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); if (type == Type_Item || type == Type_MagicItem) @@ -313,7 +309,7 @@ namespace MWGui else if (type == Type_Item) { MWWorld::Ptr item = *button->getUserData(); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + MWBase::Environment::get().getWindowManager()->useItem(item); MWWorld::ContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand if (rightHand != store.end() && item == *rightHand) @@ -339,7 +335,7 @@ namespace MWGui // equip, if it can be equipped if (!item.getClass().getEquipmentSlots(item).first.empty()) { - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped if (!store.isEquipped(item)) @@ -470,13 +466,14 @@ namespace MWGui switch (keyType) { case Type_Magic: - onAssignMagic(id); + if (MWBase::Environment::get().getWorld()->getStore().get().search(id)) + onAssignMagic(id); break; case Type_Item: case Type_MagicItem: { // Find the item by id - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); MWWorld::Ptr item; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) @@ -546,7 +543,7 @@ namespace MWGui { WindowModal::open(); - mMagicList->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mMagicList->setModel(new SpellModel(MWMechanics::getPlayer())); mMagicList->resetScrollbars(); } diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 76961af5d..1dac7138f 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -7,8 +7,6 @@ #include -#include - #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -19,6 +17,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "widgets.hpp" #include "itemwidget.hpp" @@ -94,7 +93,7 @@ void Recharge::updateView() int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) @@ -149,7 +148,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) MWWorld::Ptr item = *sender->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 2ea0db64a..76bb4f53f 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -3,6 +3,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwmechanics/actorutil.hpp" + namespace MWGui { ReferenceInterface::ReferenceInterface() @@ -16,7 +18,7 @@ namespace MWGui void ReferenceInterface::checkReferenceAvailable() { - MWWorld::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore* playerCell = MWMechanics::getPlayer().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 534226aeb..49d5735a4 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -9,6 +9,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" @@ -94,7 +96,7 @@ void Repair::updateRepairView() int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::ContainerStore& store = player.getClass().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/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index d490d49cd..816fc0fa8 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -1,5 +1,8 @@ #include "savegamedialog.hpp" +#include +#include + #include #include #include @@ -309,6 +312,22 @@ namespace MWGui onSlotSelected(mSaveList, MyGUI::ITEM_NONE); } + std::string formatTimeplayed(const double timeInSeconds) + { + int timePlayed = (int)floor(timeInSeconds); + int days = timePlayed / 60 / 60 / 24; + int hours = (timePlayed / 60 / 60) % 24; + int minutes = (timePlayed / 60) % 60; + int seconds = timePlayed % 60; + + std::stringstream stream; + stream << std::setfill('0') << std::setw(2) << days << ":"; + stream << std::setfill('0') << std::setw(2) << hours << ":"; + stream << std::setfill('0') << std::setw(2) << minutes << ":"; + stream << std::setfill('0') << std::setw(2) << seconds; + return stream.str(); + } + void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); @@ -347,9 +366,8 @@ namespace MWGui char buffer[size]; if (std::strftime(buffer, size, "%x %X", timeinfo) > 0) text << buffer << "\n"; - text << "Level " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; - text << mCurrentSlot->mProfile.mPlayerCell << "\n"; - // text << "Time played: " << slot->mProfile.mTimePlayed << "\n"; + text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; + text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n"; int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour); bool pm = hour >= 12; @@ -361,6 +379,11 @@ namespace MWGui << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); + if (Settings::Manager::getBool("timeplayed","Saves")) + { + text << "\n" << "Time played: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); + } + mInfoText->setCaptionWithReplacing(text.str()); @@ -378,7 +401,7 @@ namespace MWGui osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(instream); if (!result.success()) { - std::cerr << "Failed to read savegame screenshot: " << result.message() << std::endl; + std::cerr << "Failed to read savegame screenshot: " << result.message() << " code " << result.status() << std::endl; return; } diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 01ce7767e..ccc07174d 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -10,6 +10,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/actiontake.hpp" #include "formatting.hpp" @@ -103,7 +105,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()->getPlayerPtr()); + take.execute (MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index df5aa1a40..3ab2a6ce3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -233,6 +233,7 @@ namespace MWGui if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); } + highlightCurrentResolution(); std::string tf = Settings::Manager::getString("texture filtering", "General"); mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); @@ -299,8 +300,28 @@ namespace MWGui } void SettingsWindow::onResolutionCancel() + { + highlightCurrentResolution(); + } + + void SettingsWindow::highlightCurrentResolution() { mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); + + int currentX = Settings::Manager::getInt("resolution x", "Video"); + int currentY = Settings::Manager::getInt("resolution y", "Video"); + + for (size_t i=0; igetItemCount(); ++i) + { + int resX, resY; + parseResolution (resX, resY, mResolutionList->getItemNameAt(i)); + + if (resX == currentX && resY == currentY) + { + mResolutionList->setIndexSelected(i); + break; + } + } } void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 45d489284..79487c54b 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -59,6 +59,7 @@ namespace MWGui void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); void onResolutionAccept(); void onResolutionCancel(); + void highlightCurrentResolution(); void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 183ab07ff..1d086f2ee 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -71,7 +71,6 @@ namespace MWGui SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel) : mCategory(Category_All) , mFilter(0) - , mShowEquipped(true) , mSortByType(true) { mSourceModel = sourceModel; @@ -91,9 +90,6 @@ namespace MWGui { MWWorld::Ptr base = item.mBase; - if (item.mType == ItemStack::Type_Equipped && !mShowEquipped) - return false; - int category = 0; if (base.getTypeName() == typeid(ESM::Armor).name() || base.getTypeName() == typeid(ESM::Clothing).name()) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 1b68bdd4f..064f62e77 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -24,7 +24,6 @@ namespace MWGui void setCategory (int category); void setFilter (int filter); - void setShowEquipped (bool show) { mShowEquipped = show; } /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } @@ -49,7 +48,6 @@ namespace MWGui int mCategory; int mFilter; - bool mShowEquipped; bool mSortByType; }; diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index ae7b7588a..8c4520662 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -12,10 +12,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWGui { @@ -49,7 +49,7 @@ namespace MWGui int price = static_cast(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(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); // TODO: refactor to use MyGUI::ListBox @@ -126,7 +126,7 @@ namespace MWGui bool SpellBuyingWindow::playerHasSpell(const std::string &id) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); return player.getClass().getCreatureStats(player).getSpells().hasSpell(id); } @@ -134,7 +134,7 @@ namespace MWGui { int price = *_sender->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; @@ -159,7 +159,7 @@ namespace MWGui void SpellBuyingWindow::updateLabels() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 57563be22..ff5a27abe 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -19,6 +19,7 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" #include "class.hpp" @@ -376,7 +377,7 @@ namespace MWGui return; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) @@ -474,7 +475,7 @@ namespace MWGui mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); - float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayerPtr()); + float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWMechanics::getPlayer()); mSuccessChance->setCaption(MyGUI::utility::toString(int(chance))); } @@ -507,7 +508,7 @@ namespace MWGui { // get the list of magic effects that are known to the player - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index db0453623..27896381e 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -16,6 +17,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" @@ -43,7 +45,7 @@ namespace MWGui { // TODO: Tracking add/remove/expire would be better than force updating every frame - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); @@ -133,6 +135,24 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") ); } } + if (effectIt->mRemainingTime > -1 && + Settings::Manager::getBool("show effect duration","Game")) { + sourcesDescription += " #{sDuration}: "; + float duration = effectIt->mRemainingTime; + if (duration > 3600) { + int hour = duration / 3600; + duration -= hour*3600; + sourcesDescription += MWGui::ToolTips::toString(hour) + "h"; + } + if (duration > 60) { + int minute = duration / 60; + duration -= minute*60; + sourcesDescription += MWGui::ToolTips::toString(minute) + "m"; + } + if (duration > 0.1) { + sourcesDescription += MWGui::ToolTips::toString(duration) + "s"; + } + } } if (remainingDuration > 0.f) diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index d2ea67ea9..8422bb33f 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -15,9 +15,9 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "spellicons.hpp" -#include "inventorywindow.hpp" #include "confirmationdialog.hpp" #include "spellview.hpp" @@ -79,12 +79,12 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, false); - mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); } void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); // retrieve ContainerStoreIterator to the item @@ -103,7 +103,7 @@ namespace MWGui if (!alreadyEquipped && !item.getClass().getEquipmentSlots(item).first.empty()) { - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped if (!store.isEquipped(item)) return; @@ -159,7 +159,7 @@ namespace MWGui void SpellWindow::onSpellSelected(const std::string& spellId) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); @@ -169,7 +169,7 @@ namespace MWGui void SpellWindow::onDeleteSpellAccept() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); @@ -183,7 +183,7 @@ namespace MWGui void SpellWindow::cycle(bool next) { - mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); SpellModel::ModelIndex selected = 0; for (SpellModel::ModelIndex i = 0; igetModel()->getItemCount()); ++i) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index cb1bf6f37..efbbeb29a 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -15,6 +15,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" @@ -234,7 +235,7 @@ namespace MWGui NoDrop::onFrame(dt); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); // level progress @@ -383,7 +384,7 @@ namespace MWGui int base = stat.getBase(); int modified = stat.getModified(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -493,7 +494,7 @@ namespace MWGui if (!mFactions.empty()) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); const std::set &expelled = PCstats.getExpelled(); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 526cbaabe..7c7f951af 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -17,6 +17,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "mapwindow.hpp" #include "inventorywindow.hpp" @@ -38,6 +39,7 @@ namespace MWGui , mLastMouseY(0) , mEnabled(true) , mFullHelp(false) + , mShowOwned(0) { getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); @@ -55,6 +57,8 @@ namespace MWGui { mMainWidget->getChildAt(i)->setVisible(false); } + + mShowOwned = Settings::Manager::getInt("show owned", "Game"); } void ToolTips::setEnabled(bool enabled) @@ -231,7 +235,7 @@ namespace MWGui } if (MWMechanics::spellIncreasesSkill(spell)) // display school of spells that contribute to skill progress { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int school = MWMechanics::getSpellSchool(spell, player); info.text = "#{sSchool}: " + sSchoolNames[school]; } @@ -346,10 +350,41 @@ namespace MWGui return tooltipSize; } + + bool ToolTips::checkOwned() + { + if(!mFocusObject.isEmpty()) + { + const MWWorld::CellRef& cellref = mFocusObject.getCellRef(); + MWWorld::Ptr ptr = MWMechanics::getPlayer(); + MWWorld::Ptr victim; + + MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); + bool allowed = mm->isAllowedToUse(ptr, cellref, victim); + + return !allowed; + } + else + { + return false; + } + } MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) { mDynamicToolTipBox->setVisible(true); + + if(mShowOwned == 1 || mShowOwned == 3) + { + if(checkOwned()) + { + mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp_Owned"); + } + else + { + mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp"); + } + } std::string caption = info.caption; std::string image = info.icon; diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index d14993639..983b50fe2 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -1,4 +1,3 @@ - #ifndef MWGUI_TOOLTIPS_H #define MWGUI_TOOLTIPS_H @@ -87,7 +86,10 @@ namespace MWGui static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); static void createMagicEffectToolTip(MyGUI::Widget* widget, short id); - + + bool checkOwned(); + /// Returns True if taking mFocusObject would be crime + private: MyGUI::Widget* mDynamicToolTipBox; @@ -107,7 +109,7 @@ namespace MWGui static std::string sSchoolNames[6]; - int mHorizontalScrollIndex; + int mHorizontalScrollIndex; float mDelay; @@ -119,6 +121,8 @@ namespace MWGui bool mEnabled; bool mFullHelp; + + int mShowOwned; }; } #endif diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 1c5dc4632..6f45ed005 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -15,12 +15,12 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" @@ -283,7 +283,7 @@ namespace MWGui return; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); // check if the player can afford this @@ -491,7 +491,7 @@ namespace MWGui void TradeWindow::updateLabels() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6376ced57..30ad27e46 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -13,6 +13,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" @@ -68,7 +69,7 @@ namespace MWGui { mPtr = actor; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 6ea6301ad..8c55c3732 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -12,6 +12,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -144,7 +145,7 @@ namespace MWGui int price; iss >> price; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (playerGoldgetStore().get().find("fSleepRestMod")->getFloat(); - mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); - mInterruptCreatureList = region->mSleepList; + int interruptAtHoursRemaining = int(fSleepRestMod * hoursToWait); + if (interruptAtHoursRemaining != 0) + { + mInterruptAt = hoursToWait - interruptAtHoursRemaining; + mInterruptCreatureList = region->mSleepList; + } } } } @@ -199,7 +204,7 @@ namespace MWGui { stopWaiting(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); // trigger levelup if possible @@ -213,7 +218,7 @@ namespace MWGui void WaitDialog::setCanRest (bool canRest) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 158d5fd5e..695337cde 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -539,6 +539,9 @@ namespace MWGui , mRepeatStepTime(0.1f) , mIsIncreasing(true) { +#if MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3,2,2) + ScrollBar::setRepeatEnabled(false); +#endif } MWScrollBar::~MWScrollBar() @@ -602,7 +605,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -618,7 +621,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 195b6c384..43fb8901e 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -21,16 +21,16 @@ namespace MWGui // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; - ///Unhides the window + /// Notify that window has been made visible virtual void open() {} - ///Hides the window + /// Notify that window has been hidden virtual void close () {} - ///Gracefully exits the window + /// Gracefully exits the window virtual void exit() {} - ///Sets the visibility of the window + /// Sets the visibility of the window virtual void setVisible(bool visible); - ///Returns the visibility state of the window - virtual bool isVisible(); + /// Returns the visibility state of the window + bool isVisible(); void center(); }; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 9cf957522..90a5f7481 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -45,6 +45,7 @@ #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwrender/vismask.hpp" @@ -55,11 +56,10 @@ #include "../mwmechanics/stat.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwrender/localmap.hpp" -#include "../mwsound/soundmanagerimp.hpp" - #include "console.hpp" #include "journalwindow.hpp" #include "journalviewmodel.hpp" @@ -113,7 +113,7 @@ namespace MWGui WindowManager::WindowManager( osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem , const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap) + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription) : mResourceSystem(resourceSystem) , mViewer(viewer) , mConsoleOnlyScripts(consoleOnlyScripts) @@ -187,6 +187,8 @@ namespace MWGui , mRestAllowed(true) , mFPS(0.0f) , mFallbackMap(fallbackMap) + , mShowOwned(0) + , mVersionDescription(versionDescription) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale); @@ -235,8 +237,9 @@ namespace MWGui MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); + // Create all cursors in advance + createCursors(); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - mCursorManager->setEnabled(true); // hide mygui's pointer @@ -260,6 +263,8 @@ namespace MWGui MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); + + mShowOwned = Settings::Manager::getInt("show owned", "Game"); } void WindowManager::initUI() @@ -271,7 +276,7 @@ namespace MWGui mDragAndDrop = new DragAndDrop(); mRecharge = new Recharge(); - mMenu = new MainMenu(w, h, mResourceSystem->getVFS()); + mMenu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription); mLocalMapRender = new MWRender::LocalMap(mViewer); mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender); trackWindow(mMap, "map"); @@ -896,7 +901,10 @@ namespace MWGui void WindowManager::updateMap() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (!mLocalMapRender) + return; + + MWWorld::Ptr player = MWMechanics::getPlayer(); osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); osg::Quat playerOrientation (-player.getRefData().getPosition().rot[2], osg::Vec3(0,0,1)); @@ -986,7 +994,7 @@ namespace MWGui if (cell->getCell()->isExterior()) { if (!cell->getCell()->mName.empty()) - mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->getCell()->getGridX (), cell->getCell()->getGridY ()); + mMap->addVisitedLocation (name, cell->getCell()->getGridX (), cell->getCell()->getGridY ()); mMap->cellExplored (cell->getCell()->getGridX(), cell->getCell()->getGridY()); } @@ -1034,6 +1042,12 @@ namespace MWGui void WindowManager::setFocusObject(const MWWorld::Ptr& focus) { mToolTips->setFocusObject(focus); + + if(mHud && (mShowOwned == 2 || mShowOwned == 3)) + { + bool owned = mToolTips->checkOwned(); + mHud->setCrosshairOwned(owned); + } } void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) @@ -1081,11 +1095,22 @@ namespace MWGui void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { std::string tag(_tag); + + std::string MyGuiPrefix = "setting="; + size_t MyGuiPrefixLength = MyGuiPrefix.length(); std::string tokenToFind = "sCell="; size_t tokenLength = tokenToFind.length(); - - if (tag.compare(0, tokenLength, tokenToFind) == 0) + + if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0) + { + tag = tag.substr(MyGuiPrefixLength, tag.length()); + std::string settingSection = tag.substr(0, tag.find(",")); + std::string settingTag = tag.substr(tag.find(",")+1, tag.length()); + + _result = Settings::Manager::getString(settingTag, settingSection); + } + else if (tag.compare(0, tokenLength, tokenToFind) == 0) { _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); } @@ -1178,31 +1203,7 @@ namespace MWGui void WindowManager::onCursorChange(const std::string &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 - ResourceImageSetPointerFix* imgSetPtr = dynamic_cast(MyGUI::PointerManager::getInstance().getByName(name)); - if(imgSetPtr != NULL) - { - MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet(); - - std::string tex_name = imgSet->getIndexInfo(0,0).texture; - - osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP); - tex->setUnRefImageDataAfterApply(false); // FIXME? - - //everything looks good, send it to the cursor manager - if(tex.valid()) - { - Uint8 size_x = imgSetPtr->getSize().width; - Uint8 size_y = imgSetPtr->getSize().height; - Uint8 hotspot_x = imgSetPtr->getHotSpot().left; - Uint8 hotspot_y = imgSetPtr->getHotSpot().top; - int rotation = imgSetPtr->getRotation(); - - mCursorManager->receiveCursorInfo(name, rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y); - } - } + mCursorManager->cursorChanged(name); } void WindowManager::popGuiMode() @@ -1326,6 +1327,12 @@ namespace MWGui MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } + void WindowManager::useItem(const MWWorld::Ptr &item) + { + if (mInventoryWindow) + mInventoryWindow->useItem(item); + } + bool WindowManager::isAllowed (GuiWindow wnd) const { return (mAllowed & wnd) != 0; @@ -1547,7 +1554,7 @@ namespace MWGui { mInventoryWindow->updatePlayer(); - const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { setWerewolfOverlay(true); @@ -1669,10 +1676,10 @@ namespace MWGui writer.endRecord(ESM::REC_ASPL); } - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (CustomMarkerCollection::ContainerType::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) { writer.startRecord(ESM::REC_MARK); - (*it).save(writer); + it->second.save(writer); writer.endRecord(ESM::REC_MARK); } } @@ -1686,7 +1693,9 @@ namespace MWGui else if (type == ESM::REC_ASPL) { reader.getSubNameIs("ID__"); - mSelectedSpell = reader.getHString(); + std::string spell = reader.getHString(); + if (MWBase::Environment::get().getWorld()->getStore().get().search(spell)) + mSelectedSpell = spell; } else if (type == ESM::REC_MARK) { @@ -1960,6 +1969,33 @@ namespace MWGui return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); } + void WindowManager::createCursors() + { + MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); + while (enumerator.next()) + { + MyGUI::IResource* resource = enumerator.current().second; + ResourceImageSetPointerFix* imgSetPointer = dynamic_cast(resource); + if (!imgSetPointer) + continue; + std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; + + osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP); + + if(tex.valid()) + { + //everything looks good, send it to the cursor manager + Uint8 size_x = imgSetPointer->getSize().width; + Uint8 size_y = imgSetPointer->getSize().height; + Uint8 hotspot_x = imgSetPointer->getHotSpot().left; + Uint8 hotspot_y = imgSetPointer->getHotSpot().top; + int rotation = imgSetPointer->getRotation(); + + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y); + } + } + } + void WindowManager::createTextures() { { diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index c275a9f62..a1f76683e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -116,7 +116,7 @@ namespace MWGui WindowManager(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap); + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription); virtual ~WindowManager(); void initUI(); @@ -173,6 +173,9 @@ namespace MWGui virtual MWGui::ConfirmationDialog* getConfirmationDialog(); virtual MWGui::TradeWindow* getTradeWindow(); + /// Make the player use an item, while updating GUI state accordingly + virtual void useItem(const MWWorld::Ptr& item); + virtual void updateSpellWindow(); virtual void setConsoleSelectedObject(const MWWorld::Ptr& object); @@ -486,11 +489,16 @@ namespace MWGui float mFPS; std::map mFallbackMap; + + int mShowOwned; + + std::string mVersionDescription; /** * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Supported syntax: * #{GMSTName}: retrieves String value of the GMST called GMSTName + * #{setting=CATEGORY_NAME,SETTING_NAME}: retrieves String value of SETTING_NAME under category CATEGORY_NAME from settings.cfg * #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in others cell ID is == cell name) * #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, * in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" properties in skins. @@ -511,6 +519,7 @@ namespace MWGui void onClipboardRequested(const std::string& _type, std::string& _data); void createTextures(); + void createCursors(); void setMenuTransparency(float value); }; } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 09e0b638b..ad5fe35bd 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -28,6 +28,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" using namespace ICS; @@ -40,6 +41,7 @@ namespace MWInput const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab) : mWindow(window) + , mWindowVisible(true) , mViewer(viewer) , mJoystickLastUsed(false) , mPlayer(NULL) @@ -100,9 +102,9 @@ namespace MWInput mControlSwitch["playerviewswitch"] = true; mControlSwitch["vanitymode"] = true; - /* Joystick Init */ + /* Joystick Init */ - //Load controller mappings + // Load controller mappings #if SDL_VERSION_ATLEAST(2,0,2) if(controllerBindingsFile!="") { @@ -110,10 +112,10 @@ namespace MWInput } #endif - //Open all presently connected sticks - int numSticks = SDL_NumJoysticks(); - for(int i = 0; i < numSticks; i++) - { + // Open all presently connected sticks + int numSticks = SDL_NumJoysticks(); + for(int i = 0; i < numSticks; i++) + { if(SDL_IsGameController(i)) { SDL_ControllerDeviceEvent evt; @@ -124,7 +126,7 @@ namespace MWInput { //ICS_LOG(std::string("Unusable controller plugged in: ")+SDL_JoystickNameForIndex(i)); } - } + } float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); if (uiScale != 0.f) @@ -155,6 +157,11 @@ namespace MWInput delete mVideoWrapper; } + bool InputManager::isWindowVisible() + { + return mWindowVisible; + } + void InputManager::setPlayerControlsEnabled(bool enabled) { int nPlayerChannels = 17; @@ -370,6 +377,9 @@ namespace MWInput float zAxis = mInputBinder->getChannel(A_LookUpDown)->getValue()*2.0f-1.0f; const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + xAxis *= (1.5f - mInputBinder->getChannel(A_Use)->getValue()); + yAxis *= (1.5f - mInputBinder->getChannel(A_Use)->getValue()); + // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor mGuiCursorX += xAxis * dt * 1500.0f * mInvUiScalingFactor; @@ -846,7 +856,7 @@ namespace MWInput void InputManager::windowVisibilityChange(bool visible) { - //TODO: Pause game? + mWindowVisible = visible; } void InputManager::windowResized(int x, int y) @@ -1009,7 +1019,7 @@ namespace MWInput { if (!mControlSwitch["playercontrols"]) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { // Cannot use items or spells while in werewolf form @@ -1026,7 +1036,7 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { // Cannot use items or spells while in werewolf form diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index aec640736..62d0f413b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -83,6 +83,8 @@ namespace MWInput virtual ~InputManager(); + virtual bool isWindowVisible(); + /// Clear all savegame-specific data virtual void clear(); @@ -153,6 +155,7 @@ namespace MWInput private: SDL_Window* mWindow; + bool mWindowVisible; osg::ref_ptr mViewer; bool mJoystickLastUsed; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d9a8ce72f..1c26c7dd1 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1,28 +1,28 @@ #include "actors.hpp" #include +#include #include #include #include #include -#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/player.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwrender/animation.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" @@ -34,6 +34,7 @@ #include "actor.hpp" #include "summoning.hpp" #include "combat.hpp" +#include "actorutil.hpp" namespace { @@ -56,7 +57,7 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a action.execute(actor); MWWorld::ContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() + if (actor == MWMechanics::getPlayer() && rightHand != store.end() && newPtr == *rightHand) { MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); @@ -69,44 +70,6 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a } } -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; - int charge = item->getClass().getItemHealth(*item); - - if (charge == 0) - return false; - - // FIXME: charge should be a float, not int so that damage < 1 per frame can be applied. - // This was also a bug in the original engine. - charge -= - std::min(static_cast(disintegrate), - charge); - item->getCellRef().setCharge(charge); - - if (charge == 0) - { - // Will unequip the broken item and try to find a replacement - if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) - inv.autoEquip(ptr); - else - inv.unequipItem(*item, ptr); - } - - return true; - } - } - return false; -} - class CheckActorCommanded : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mActor; @@ -120,7 +83,7 @@ public: const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) && casterActorId == player.getClass().getCreatureStats(player).getActorId() @@ -241,7 +204,7 @@ namespace MWMechanics gem->getContainerStore()->unstack(*gem, caster); gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); - if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() @@ -331,6 +294,10 @@ namespace MWMechanics return; } + // no combat for totally static creatures (they have no movement or attack animations anyway) + if (!actor1.getClass().isMobile(actor1)) + return; + bool aggressive; if (againstPlayer) @@ -435,7 +402,7 @@ namespace MWMechanics int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); float base = 1.f; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == getPlayer()) base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->getFloat(); else base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->getFloat(); @@ -518,8 +485,12 @@ namespace MWMechanics bool wasDead = creatureStats.isDead(); - // FIXME: effect ticks should go into separate functions so they can be used with either - // magnitude (instant effect) or magnitude*duration + // tickable effects (i.e. effects having a lasting impact after expiry) + // these effects can be applied as "instant" (handled in spellcasting.cpp) or with a duration, handled here + for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); + } // attributes for(int i = 0;i < ESM::Attribute::Length;++i) @@ -529,9 +500,6 @@ namespace MWMechanics effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude())); - stat.damage(effects.get(EffectKey(ESM::MagicEffect::DamageAttribute, i)).getMagnitude() * duration); - stat.restore(effects.get(EffectKey(ESM::MagicEffect::RestoreAttribute, i)).getMagnitude() * duration); - creatureStats.setAttribute(i, stat); } @@ -545,7 +513,7 @@ namespace MWMechanics { spells.worsenCorprus(it->first); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); } } @@ -561,12 +529,6 @@ namespace MWMechanics // Fatigue can be decreased below zero meaning the actor will be knocked out i == 2); - - float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).getMagnitude(); - stat.setCurrent(stat.getCurrent() + currentDiff * duration, i == 2); - creatureStats.setDynamic(i, stat); } @@ -578,107 +540,28 @@ namespace MWMechanics if (!creature || ptr.get()->mBase->mData.mType == ESM::Creature::Creatures) { Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); - stat.setModifier(static_cast(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); + stat.setModifier(static_cast(effects.get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() + - effects.get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); + stat.setModifier(static_cast(effects.get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() + - effects.get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); 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(static_cast(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).getMagnitude())); + stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } - // Apply disintegration (reduces item health) - float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).getMagnitude(); - if (disintegrateWeapon > 0) - disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration); - float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).getMagnitude(); - 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 0.0f - || creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth).getMagnitude() > 0.0f) - receivedMagicDamage = true; - - // Apply damage ticks - int damageEffects[] = { - ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, - ESM::MagicEffect::SunDamage - }; - - DynamicStat 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); - - if (magnitude * damageScale > 0.0f) - receivedMagicDamage = true; - } - else - { - health.setCurrent(health.getCurrent() - magnitude * duration); - - if (magnitude > 0.0f) - receivedMagicDamage = true; - } - } - - if (receivedMagicDamage && ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - - creatureStats.setHealth(health); - if (!wasDead && creatureStats.isDead()) { // The actor was killed by a magic effect. Figure out if the player was responsible for it. const ActiveSpells& spells = creatureStats.getActiveSpells(); bool killedByPlayer = false; - bool murderedByPlayer = false; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ActiveSpells::ActiveSpellParams& spell = it->second; @@ -687,40 +570,36 @@ namespace MWMechanics { int effectId = effectIt->mEffectId; bool isDamageEffect = false; + + int damageEffects[] = { + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth + }; + for (unsigned int i=0; isearchPtrViaActorId(spell.mCasterActorId); if (isDamageEffect && caster == player) - { killedByPlayer = true; - // Simple check for who attacked first: if the player attacked first, a crimeId should be set - // Doesn't handle possible edge case where no one reported the assault, but in such a case, - // for bystanders it is not possible to tell who attacked first, anyway. - if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 - && ptr != player) - murderedByPlayer = true; - } } } - if (murderedByPlayer) - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); - if (killedByPlayer && player.getClass().getNpcStats(player).isWerewolf()) - player.getClass().getNpcStats(player).addWerewolfKill(); + if (killedByPlayer) + { + MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player); + if (player.getClass().getNpcStats(player).isWerewolf()) + player.getClass().getNpcStats(player).addWerewolfKill(); + } } // TODO: dirty flag for magic effects to avoid some unnecessary work below? // any value of calm > 0 will stop the actor from fighting - if ((creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) - || (creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) + if ((effects.get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) + || (effects.get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) { for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ) { @@ -753,7 +632,7 @@ namespace MWMechanics for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) { bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); - float magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude(); + float magnitude = effects.get(it->first).getMagnitude(); if (found != (magnitude > 0)) { std::string itemGmst = it->second; @@ -797,9 +676,6 @@ namespace MWMechanics skill.setModifier(static_cast(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude())); - - skill.damage(effects.get(EffectKey(ESM::MagicEffect::DamageSkill, i)).getMagnitude() * duration); - skill.restore(effects.get(EffectKey(ESM::MagicEffect::RestoreSkill, i)).getMagnitude() * duration); } } @@ -830,7 +706,9 @@ namespace MWMechanics { // If drowning, apply 3 points of damage per second static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->getFloat(); - ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - fSuffocationDamage*duration); + DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent() - fSuffocationDamage*duration); + stats.setHealth(health); // Play a drowning sound MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); @@ -850,7 +728,7 @@ namespace MWMechanics void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration) { - bool isPlayer = (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()); + bool isPlayer = (ptr == getPlayer()); MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator heldIter = @@ -947,7 +825,7 @@ namespace MWMechanics void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); if (ptr != player && ptr.getClass().isNpc()) { // get stats of witness @@ -1069,7 +947,7 @@ namespace MWMechanics if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); int hostilesCount = 0; // need to know this to play Battle music @@ -1161,13 +1039,12 @@ namespace MWMechanics > sqrProcessingDistance) continue; - if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( - ESM::MagicEffect::Paralyze).getMagnitude() > 0) + if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) iter->second->getCharacterController()->skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) - if (iter->first == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (iter->first == getPlayer()) { playerCharacter = iter->second->getCharacterController(); continue; @@ -1200,24 +1077,24 @@ namespace MWMechanics killDeadActors(); // check if we still have any player enemies to switch music - static bool isBattleMusic = false; + static int currentMusic = 0; - if (isBattleMusic && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && + if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - isBattleMusic = false; + currentMusic = 1; } - else if (!isBattleMusic && hostilesCount > 0) + else if (currentMusic != 2 && hostilesCount > 0) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - isBattleMusic = true; + currentMusic = 2; } static float sneakTimer = 0.f; // times update of sneak icon // if player is in sneak state see if anyone detects him - if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) + if (playerCharacter && playerCharacter->isSneaking()) { static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" @@ -1299,6 +1176,12 @@ namespace MWMechanics if (iter->second->getCharacterController()->kill()) { + // TODO: It's not known whether the soundgen tags scream, roar, and moan are reliable + // for NPCs since some of the npc death animation files are missing them. + + // Play dying words + MWBase::Environment::get().getDialogueManager()->say(iter->first, "hit"); + iter->first.getClass().getCreatureStats(iter->first).notifyDied(); ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; @@ -1370,11 +1253,18 @@ namespace MWMechanics iter->second->getCharacterController()->forceStateUpdate(); } - void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second->getCharacterController()->playGroup(groupName, mode, number); + { + return iter->second->getCharacterController()->playGroup(groupName, mode, number); + } + else + { + std::cerr<< "Error in Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId() << std::endl; + return false; + } } void Actors::skipAnimation(const MWWorld::Ptr& ptr) { @@ -1541,9 +1431,9 @@ namespace MWMechanics for (PtrActorMap::iterator it = map.begin(); it != map.end(); ++it) { MWWorld::Ptr ptr = it->first; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() + if (ptr == getPlayer() || !isConscious(ptr) - || ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + || ptr.getClass().getCreatureStats(ptr).isParalyzed()) continue; MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); seq.fastForward(ptr, it->second->getAiState()); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 4baaea28d..a16b80884 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -105,7 +105,7 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr &ptr); - void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + bool 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); diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp new file mode 100644 index 000000000..537f27197 --- /dev/null +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -0,0 +1,19 @@ +#include "actorutil.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/player.hpp" + +namespace MWMechanics +{ + MWWorld::Ptr getPlayer() + { + return MWBase::Environment::get().getWorld()->getPlayerPtr(); + } + + bool isPlayerInCombat() + { + return MWBase::Environment::get().getWorld()->getPlayer().isInCombat(); + } +} diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp new file mode 100644 index 000000000..510e41db3 --- /dev/null +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H +#define OPENMW_MWMECHANICS_ACTORUTIL_H + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + MWWorld::Ptr getPlayer(); + bool isPlayerInCombat(); +} + +#endif diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b0621c805..8761ca37f 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -8,7 +8,6 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" #include "steering.hpp" #include "movement.hpp" diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 457c9a95c..409f7b9c4 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -1,12 +1,12 @@ #include "aiavoiddoor.hpp" -#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "movement.hpp" +#include "actorutil.hpp" #include "steering.hpp" @@ -64,7 +64,7 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont std::vector actors; MWBase::Environment::get().getMechanicsManager()->getActorsInRange(pos.asVec3(),100,actors); for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { - if(*it != MWBase::Environment::get().getWorld()->getPlayerPtr()) { //Not the player + if(*it != getPlayer()) { //Not the player MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4eeea6f1f..6270779a4 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -5,22 +5,17 @@ #include #include "../mwworld/class.hpp" -#include "../mwworld/timestamp.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwrender/animation.hpp" - #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" -#include "character.hpp" // fixme: for getActiveWeapon +#include "character.hpp" #include "aicombataction.hpp" #include "combat.hpp" @@ -36,15 +31,15 @@ namespace float getZAngleToDir(const osg::Vec3f& dir) { - return osg::RadiansToDegrees(std::atan2(dir.x(), dir.y())); + return std::atan2(dir.x(), dir.y()); } - float getXAngleToDir(const osg::Vec3f& dir, float dirLen = 0.0f) + float getXAngleToDir(const osg::Vec3f& dir) { - float len = (dirLen > 0.0f)? dirLen : dir.length(); - return osg::RadiansToDegrees(-std::asin(dir.z() / len)); + return -std::asin(dir.z() / dir.length()); } + const float REACTION_INTERVAL = 0.25f; const float PATHFIND_Z_REACH = 50.0f; // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid @@ -61,7 +56,7 @@ namespace osg::Vec3f dir = to - from; dir.z() = 0; dir.normalize(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor + float verticalOffset = 200; // instead of '200' here we want the height of the actor osg::Vec3f _from = from + dir*offsetXY + osg::Vec3f(0,0,1) * verticalOffset; // cast up-down ray and find height in world space of hit @@ -114,6 +109,14 @@ namespace MWMechanics mForceNoShortcut(false), mLastActorPos(0,0,0), mMovement(){} + + void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); + void updateCombatMove(float duration); + void stopCombatMove(); + void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, + const ESM::Weapon* weapon, bool distantCombat); + void updateAttack(CharacterController& characterController); + void stopAttack(); }; AiCombat::AiCombat(const MWWorld::Ptr& actor) : @@ -181,10 +184,8 @@ namespace MWMechanics // get or create temporary storage AiCombatStorage& storage = state.get(); - - //General description - if(actor.getClass().getCreatureStats(actor).isDead()) + if (actor.getClass().getCreatureStats(actor).isDead()) return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); @@ -196,69 +197,36 @@ namespace MWMechanics || target.getClass().getCreatureStats(target).isDead()) return true; - const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - //Update every frame - bool& combatMove = storage.mCombatMove; - float& timerCombatMove = storage.mTimerCombatMove; - MWMechanics::Movement& movement = storage.mMovement; - if(combatMove) - { - timerCombatMove -= duration; - if( timerCombatMove <= 0) - { - timerCombatMove = 0; - movement.mPosition[1] = movement.mPosition[0] = 0; - combatMove = false; - } - } - - actorClass.getMovementSettings(actor) = movement; - actorClass.getMovementSettings(actor).mRotation[0] = 0; - actorClass.getMovementSettings(actor).mRotation[2] = 0; - - if(movement.mRotation[2] != 0) - { - if(zTurn(actor, osg::DegreesToRadians(movement.mRotation[2]))) movement.mRotation[2] = 0; - } - - if(movement.mRotation[0] != 0) - { - if(smoothTurn(actor, osg::DegreesToRadians(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; - } - - bool& attack = storage.mAttack; - bool& readyToAttack = storage.mReadyToAttack; - - if (attack && (characterController.getAttackStrength() >= storage.mStrength || characterController.readyToPrepareAttack())) - attack = false; - - characterController.setAttackingOrSpell(attack); - - float& actionCooldown = storage.mActionCooldown; - actionCooldown -= duration; + storage.updateCombatMove(duration); + updateActorsMovement(actor, duration, storage.mMovement); + storage.updateAttack(characterController); + storage.mActionCooldown -= duration; float& timerReact = storage.mTimerReact; - float tReaction = 0.25f; - if(timerReact < tReaction) + if(timerReact < REACTION_INTERVAL) { timerReact += duration; return false; } - - //Update with period = tReaction - - // Stop attacking if target is not seen - if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 - || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) + else { - movement.mPosition[1] = movement.mPosition[0] = 0; + timerReact = 0; + return reactionTimeActions(actor, characterController, storage, target); + } + } + + bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, + AiCombatStorage& storage, MWWorld::Ptr target) + { + MWMechanics::Movement& movement = storage.mMovement; + + if (isTargetMagicallyHidden(target)) + { + storage.stopAttack(); return false; // TODO: run away instead of doing nothing } - timerReact = 0; const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) @@ -266,8 +234,10 @@ namespace MWMechanics currentCell = actor.getCell(); } + const MWWorld::Class& actorClass = actor.getClass(); actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) return false; @@ -289,9 +259,10 @@ namespace MWMechanics float weapRange = 1.0f; // Get weapon characteristics + MWBase::World* world = MWBase::Environment::get().getWorld(); if (actorClass.hasInventoryStore(actor)) { - //Get weapon speed and range + //Get weapon range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); @@ -338,40 +309,9 @@ namespace MWMechanics } - float& strength = storage.mStrength; + bool& readyToAttack = storage.mReadyToAttack; // start new attack - if(readyToAttack && characterController.readyToStartAttack()) - { - if (storage.mAttackCooldown <= 0) - { - attack = true; // attack starts just now - characterController.setAttackingOrSpell(attack); - - if (!distantCombat) - chooseBestAttack(weapon, movement); - - strength = Misc::Rng::rollClosedProbability(); - - const MWWorld::ESMStore &store = world->getStore(); - - //say a provoking combat phrase - if (actor.getClass().isNpc()) - { - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - if (Misc::Rng::roll0to99() < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } - } - float baseDelay = store.get().find("fCombatDelayCreature")->getFloat(); - if (actor.getClass().isNpc()) - baseDelay = store.get().find("fCombatDelayNPC")->getFloat(); - storage.mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); - } - else - storage.mAttackCooldown -= tReaction; - } - + storage.startAttackIfReady(actor, characterController, weapon, distantCombat); /* * Some notes on meanings of variables: @@ -405,15 +345,16 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); - osg::Vec3f vDirToTarget = vTargetPos - vActorPos; - float distToTarget = vDirToTarget.length(); + + osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); + float distToTarget = (vTargetPos - vActorPos).length(); osg::Vec3f& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; bool isStuck = false; float speed = 0.0f; - if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) + if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * REACTION_INTERVAL / 2) isStuck = true; lastActorPos = vActorPos; @@ -422,25 +363,22 @@ namespace MWMechanics bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); - // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack - bool inLOS = distantCombat ? world->getLOS(actor, target) : true; - // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? - movement.mPosition[0] = 0; - movement.mPosition[1] = 0; - movement.mPosition[2] = 0; - readyToAttack = false; - characterController.setAttackingOrSpell(false); + storage.stopAttack(); return false; } + // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack + bool inLOS = distantCombat ? world->getLOS(actor, target) : true; + // (within attack dist) || (not quite attack dist while following) if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { + mPathFinder.clearPath(); //Melee and Close-up combat // getXAngleToDir determines vertical angle to target: @@ -450,46 +388,28 @@ namespace MWMechanics if (distantCombat) { osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, + storage.mStrength); lastTargetPos = vTargetPos; movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir(vAimDir); } else { - movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } // (not quite attack dist while following) if (followTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target + storage.stopCombatMove(); movement.mPosition[1] = 1; } else // (within attack dist) { - if(movement.mPosition[0] || movement.mPosition[1]) - { - timerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); - combatMove = true; - } - // only NPCs are smart enough to use dodge movements - else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) - { - //apply sideway movement (kind of dodging) with some probability - if (Misc::Rng::rollClosedProbability() < 0.25) - { - movement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; - timerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); - combatMove = true; - } - } - - if(distantCombat && distToTarget < rangeAttack/4) - { - movement.mPosition[1] = -1; - } + storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); readyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting @@ -510,18 +430,20 @@ namespace MWMechanics { if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed - float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vDirToTarget.x(), vDirToTarget.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); + float maxAvoidDist = REACTION_INTERVAL * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } // don't use pathgrid when actor can move in 3 dimensions - if(canMoveByZ) preferShortcut = true; + if (canMoveByZ) + { + preferShortcut = true; + movement.mRotation[0] = getXAngleToDir(vAimDir); + } if(preferShortcut) { - if (canMoveByZ) - movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); forceNoShortcut = false; shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); @@ -536,50 +458,62 @@ namespace MWMechanics followTarget = false; - buildNewPath(actor, target); //may fail to build a path, check before use + buildNewPath(actor, target); - //delete visited path node - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1]); - - // This works on the borders between the path grid and areas with no waypoints. - if(inLOS && mPathFinder.getPath().size() > 1) - { - // get point just before target - std::list::const_iterator pntIter = --mPathFinder.getPath().end(); - --pntIter; - osg::Vec3f vBeforeTarget(PathFinder::MakeOsgVec3(*pntIter)); - - // if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target - if(distToTarget <= (vTargetPos - vBeforeTarget).length()) - { - movement.mRotation[2] = getZAngleToDir(vDirToTarget); - preferShortcut = true; - } - } - - // if there is no new path, then go straight on target - if(!preferShortcut) - { - if(!mPathFinder.getPath().empty()) - movement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - else - movement.mRotation[2] = getZAngleToDir(vDirToTarget); - } + // should always return a path (even if it's just go straight on target.) + assert(mPathFinder.isPathConstructed()); } - movement.mPosition[1] = 1; if (readyToAttack) { // to stop possible sideway moving after target moved out of attack range - combatMove = true; - timerCombatMove = 0; + storage.stopCombatMove(); + readyToAttack = false; } - readyToAttack = false; + movement.mPosition[1] = 1; } return false; } + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& desiredMovement) + { + MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); + if (mPathFinder.isPathConstructed()) + { + const ESM::Position& pos = actor.getRefData().getPosition(); + if (mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) + { + actorMovementSettings.mPosition[1] = 0; + } + else + { + evadeObstacles(actor, duration, pos); + } + } + else + { + actorMovementSettings = desiredMovement; + rotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement); + } + } + + void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, + MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement) + { + actorMovementSettings.mRotation[axis] = 0; + float& targetAngleRadians = desiredMovement.mRotation[axis]; + if (targetAngleRadians != 0) + { + if (smoothTurn(actor, targetAngleRadians, axis)) + { + // actor now facing desired direction, no need to turn any more + targetAngleRadians = 0; + } + } + } + bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { if (!mPathFinder.getPath().empty()) @@ -640,6 +574,104 @@ namespace MWMechanics package.mPackage = combat.release(); sequence.mPackages.push_back(package); } + + void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack) + { + if (mMovement.mPosition[0] || mMovement.mPosition[1]) + { + mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); + mCombatMove = true; + } + // only NPCs are smart enough to use dodge movements + else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) + { + //apply sideway movement (kind of dodging) with some probability + if (Misc::Rng::rollClosedProbability() < 0.25) + { + mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); + mCombatMove = true; + } + } + + if (isDistantCombat && distToTarget < rangeAttack / 4) + { + mMovement.mPosition[1] = -1; + } + } + + void AiCombatStorage::updateCombatMove(float duration) + { + if (mCombatMove) + { + mTimerCombatMove -= duration; + if (mTimerCombatMove <= 0) + { + stopCombatMove(); + } + } + } + + void AiCombatStorage::stopCombatMove() + { + mTimerCombatMove = 0; + mMovement.mPosition[1] = mMovement.mPosition[0] = 0; + mCombatMove = false; + } + + void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, + const ESM::Weapon* weapon, bool distantCombat) + { + if (mReadyToAttack && characterController.readyToStartAttack()) + { + if (mAttackCooldown <= 0) + { + mAttack = true; // attack starts just now + characterController.setAttackingOrSpell(true); + + if (!distantCombat) + chooseBestAttack(weapon, mMovement); + + mStrength = Misc::Rng::rollClosedProbability(); + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + float baseDelay = store.get().find("fCombatDelayCreature")->getFloat(); + if (actor.getClass().isNpc()) + { + baseDelay = store.get().find("fCombatDelayNPC")->getFloat(); + + //say a provoking combat phrase + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + if (Misc::Rng::roll0to99() < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } + } + mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); + } + else + mAttackCooldown -= REACTION_INTERVAL; + } + } + + void AiCombatStorage::updateAttack(CharacterController& characterController) + { + if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack())) + { + mAttack = false; + } + characterController.setAttackingOrSpell(mAttack); + } + + void AiCombatStorage::stopAttack() + { + mMovement.mPosition[0] = 0; + mMovement.mPosition[1] = 0; + mMovement.mPosition[2] = 0; + mReadyToAttack = false; + mAttack = false; + } } @@ -678,16 +710,14 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; - float total = static_cast(slash + chop + thrust); - - float roll = Misc::Rng::rollClosedProbability(); - if(roll <= (slash/total)) + float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust); + if(roll <= slash) { movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } - else if(roll <= (slash + (thrust/total))) + else if(roll <= (slash + thrust)) { movement.mPosition[1] = 1; attackType = ESM::Weapon::AT_Thrust; @@ -731,9 +761,8 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); - osg::Vec3f vDirToTarget = vTargetPos - vActorPos; + osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = vDirToTarget.length(); osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; @@ -764,7 +793,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); else t_collision = 0; // speed of projectile is not enough to reach moving target - return vTargetPos + vTargetMoveDir * t_collision - vActorPos; + return vDirToTarget + vTargetMoveDir * t_collision; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 083f23384..93d630529 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -26,6 +26,8 @@ namespace MWMechanics { class Action; + struct AiCombatStorage; + /// \brief Causes the actor to fight another actor class AiCombat : public AiPackage { @@ -58,8 +60,14 @@ namespace MWMechanics int mTargetActorId; - void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, + AiCombatStorage& storage, MWWorld::Ptr target); + + /// Transfer desired movement (from AiCombatStorage) to Actor + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& movement); + void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, + MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); }; diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 593f9f173..f75fc22ad 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -6,7 +6,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index a92e9eedc..cd67c6058 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,8 +1,7 @@ #include "aifollow.hpp" -#include - #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 22c907588..1cd9649f7 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,7 +1,11 @@ - #include "aipackage.hpp" #include + +#include +#include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -11,10 +15,12 @@ #include "../mwworld/action.hpp" #include "steering.hpp" +#include "actorutil.hpp" +#include "coordinateconverter.hpp" MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : mTimer(0.26f), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild +MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild } @@ -23,50 +29,28 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po { //Update various Timers mTimer += duration; //Update timer - mStuckTimer += duration; //Update stuck timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell - const ESM::Cell *cell = actor.getCell()->getCell(); + //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 + //... units from player, and exterior cells are 8192 units long and wide. + //... But AI processing distance may increase in the future. + if (isNearInactiveCell(pos)) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - Movement &movement = actor.getClass().getMovementSettings(actor); - - //Ensure pursuer doesn't leave loaded cells - if(cell->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if(cell->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return false; } - //Start position - ESM::Pathgrid::Point start = pos.pos; - //*********************** /// Checks if you can't get to the end position at all, adds end position to end of path /// Rebuilds path every quarter of a second, in case the target has moved //*********************** if(mTimer > 0.25) { + const ESM::Cell *cell = actor.getCell()->getCell(); if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved + mPathFinder.buildSyncedPath(pos.pos, dest, actor.getCell(), true); //Rebuild path, in case the target has moved mPrevDest = dest; } @@ -86,43 +70,70 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //************************ if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? return true; - else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something + else { -/// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason - //if(mObstacleCheck.check(actor, duration)) { - if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care - // first check if we're walking into a door - MWWorld::Ptr door = getNearbyDoor(actor); - if(door != MWWorld::Ptr()) // NOTE: checks interior cells only - { - if(!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() && door.getClass().getDoorState(door) == 0) { //Open the door if untrapped - MWBase::Environment::get().getWorld()->activateDoor(door, 1); - } - } - else // probably walking into another NPC - { - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - // change the angle a bit, too - zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - } - } - else { //Not stuck, so reset things - mStuckTimer = 0; - mStuckPos = pos; - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward - } + evadeObstacles(actor, duration, pos); } - else { - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time - } - - zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - return false; } +void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos) +{ + zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + + MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); + if (mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + MWWorld::Ptr door = getNearbyDoor(actor); + if (door != MWWorld::Ptr()) // NOTE: checks interior cells only + { + if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() && door.getClass().getDoorState(door) == 0) { //Open the door if untrapped + MWBase::Environment::get().getWorld()->activateDoor(door, 1); + } + } + else // probably walking into another NPC + { + mObstacleCheck.takeEvasiveAction(movement); + } + } + else { //Not stuck, so reset things + movement.mPosition[1] = 1; //Just run forward + } +} + bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { return distance(mPrevDest, dest) > 10; } + +bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) +{ + const MagicEffects& magicEffects(target.getClass().getCreatureStats(target).getMagicEffects()); + return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) + || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); +} + +bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) +{ + const ESM::Cell* playerCell(getPlayer().getCell()->getCell()); + if (playerCell->isExterior()) + { + // get actor's distance from origin of center cell + osg::Vec3f actorOffset(actorPos.asVec3()); + CoordinateConverter(playerCell).toLocal(actorOffset); + + // currently assumes 3 x 3 grid for exterior cells, with player at center cell. + // ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells + // While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid. + const float distanceFromEdge = 200.0; + float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; + float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; + return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0]) + || (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]); + } + else + { + return false; + } +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index da43dc6da..3f227a49a 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -69,6 +69,8 @@ namespace MWMechanics /// Simulates the passing of time virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); + protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ @@ -76,15 +78,19 @@ namespace MWMechanics virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; float mTimer; - float mStuckTimer; - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; + + private: + bool isNearInactiveCell(const ESM::Position& actorPos); + }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index ac6b23ef6..055801fde 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,12 +1,12 @@ #include "aipursue.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -43,8 +43,7 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte ) return true; //Target doesn't exist - if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 - || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) + if (isTargetMagicallyHidden(target)) return true; if(target.getClass().getCreatureStats(target).isDead()) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index fb6450d16..a1c5ab14f 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -1,6 +1,7 @@ - #include "aisequence.hpp" +#include + #include "aipackage.hpp" #include "aistate.hpp" @@ -11,12 +12,10 @@ #include "aiactivate.hpp" #include "aicombat.hpp" #include "aipursue.hpp" +#include "actorutil.hpp" #include -#include "../mwworld/class.hpp" -#include "creaturestats.hpp" -#include "npcstats.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -152,7 +151,7 @@ bool AiSequence::isPackageDone() const void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(actor != getPlayer()) { if (!mPackages.empty()) { @@ -244,7 +243,7 @@ void AiSequence::clear() void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actor == getPlayer()) throw std::runtime_error("Can't add AI packages to player"); if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index f192bed63..1585a3007 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,6 +1,7 @@ #include "aitravel.hpp" #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index f32636b23..a3fdc69cc 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,5 +1,8 @@ #include "aiwander.hpp" +#include +#include + #include #include @@ -17,17 +20,25 @@ #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" +#include "coordinateconverter.hpp" +#include "actorutil.hpp" namespace MWMechanics { - static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed + static const int COUNT_BEFORE_RESET = 10; static const float DOOR_CHECK_INTERVAL = 1.5f; static const float REACTION_INTERVAL = 0.25f; static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_END = 10; + // to prevent overcrowding + static const int DESTINATION_TOLERANCE = 64; + + // distance must be long enough that NPC will need to move to get there. + static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2; + const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { std::string("idle2"), @@ -43,40 +54,35 @@ namespace MWMechanics /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. struct AiWanderStorage : AiTemporaryBase { - // the z rotation angle (degrees) we want to reach - // used every frame when mRotate is true + // the z rotation angle to reach + // when mTurnActorGivingGreetingToFacePlayer is true float mTargetAngleRadians; - bool mRotate; + bool mTurnActorGivingGreetingToFacePlayer; float mReaction; // update some actions infrequently - AiWander::GreetingState mSaidGreeting; int mGreetingTimer; const MWWorld::CellStore* mCell; // for detecting cell change // AiWander states - bool mChooseAction; - bool mIdleNow; - bool mMoveNow; - bool mWalking; + AiWander::WanderState mState; - unsigned short mPlayedIdle; + unsigned short mIdleAnimation; + std::vector mBadIdles; // Idle animations that when called cause errors PathFinder mPathFinder; AiWanderStorage(): mTargetAngleRadians(0), - mRotate(false), + mTurnActorGivingGreetingToFacePlayer(false), mReaction(0), mSaidGreeting(AiWander::Greet_None), mGreetingTimer(0), mCell(NULL), - mChooseAction(true), - mIdleNow(false), - mMoveNow(false), - mWalking(false), - mPlayedIdle(0) + mState(AiWander::Wander_ChooseAction), + mIdleAnimation(0), + mBadIdles() {}; }; @@ -109,7 +115,7 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStoredAvailableNodes = false; + mPopulateAvailableNodes = true; } @@ -183,7 +189,7 @@ namespace MWMechanics if(!currentCell || cellChange) { currentCell = actor.getCell(); - mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 + mPopulateAvailableNodes = true; } cStats.setDrawState(DrawState_Nothing); @@ -191,198 +197,31 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); - - bool& idleNow = storage.mIdleNow; - bool& moveNow = storage.mMoveNow; - bool& walking = storage.mWalking; - // Check if an idle actor is too close to a door - if so start walking - mDoorCheckDuration += duration; - if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) - { - mDoorCheckDuration = 0; // restart timer - if(mDistance && // actor is not intended to be stationary - idleNow && // but is in idle - !walking && // FIXME: some actors are idle while walking - proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only - { - idleNow = false; - moveNow = true; - mTrimCurrentNode = false; // just in case - } - } + doPerFrameActionsForState(actor, duration, storage, pos); - // Are we there yet? - bool& chooseAction = storage.mChooseAction; - if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], 64.f)) - { - stopWalking(actor, storage); - moveNow = false; - walking = false; - chooseAction = true; - mHasReturnPosition = false; - } - - - - if(walking) // have not yet reached the destination - { - // turn towards the next point in mPath - zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - - // Returns true if evasive action needs to be taken - if(mObstacleCheck.check(actor, duration)) - { - // first check if we're walking into a door - if(proximityToDoor(actor)) // NOTE: checks interior cells only - { - // remove allowed points then select another random destination - mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, storage.mPathFinder); - mObstacleCheck.clear(); - storage.mPathFinder.clearPath(); - walking = false; - moveNow = true; - } - else // probably walking into another NPC - { - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; - // change the angle a bit, too - zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - } - mStuckCount++; // TODO: maybe no longer needed - } -//#if 0 - // TODO: maybe no longer needed - if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset - { - //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; - mObstacleCheck.clear(); - - stopWalking(actor, storage); - moveNow = false; - walking = false; - chooseAction = true; - mStuckCount = 0; - } -//#endif - } - - - float& targetAngleRadians = storage.mTargetAngleRadians; - bool& rotate = storage.mRotate; - if (rotate) - { - // Reduce the turning animation glitch by using a *HUGE* value of - // epsilon... TODO: a proper fix might be in either the physics or the - // animation subsystem - if (zTurn(actor, targetAngleRadians, osg::DegreesToRadians(5.f))) - rotate = false; - } - - // Check if idle animation finished - short unsigned& playedIdle = storage.mPlayedIdle; - GreetingState& greetingState = storage.mSaidGreeting; - if(idleNow && !checkIdle(actor, playedIdle) && (greetingState == Greet_Done || greetingState == Greet_None)) - { - playedIdle = 0; - idleNow = false; - chooseAction = true; - } - - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if(chooseAction) - { - playedIdle = 0; - getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection - - if(!playedIdle && mDistance) - { - chooseAction = false; - moveNow = true; - } - else - { - // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: - MWWorld::TimeStamp currentTime = world->getTimeStamp(); - mStartTime = currentTime; - playIdle(actor, playedIdle); - chooseAction = false; - idleNow = true; - } - } - - // Play idle voiced dialogue entries randomly - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) - && MWBase::Environment::get().getSoundManager()->sayDone(actor)) - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->getFloat(); - - float roll = Misc::Rng::rollProbability() * 10000.0f; - - // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds - // due to the roll being an integer. - // Our implementation does not have these issues, so needs to be recalibrated. We chose to - // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. - float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f); - - // Only say Idle voices when player is in LOS - // A bit counterintuitive, likely vanilla did this to reduce the appearance of - // voices going through walls? - if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2() - < 3000*3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } + playIdleDialogueRandomly(actor); float& lastReaction = storage.mReaction; lastReaction += duration; - if(lastReaction < REACTION_INTERVAL) + if (REACTION_INTERVAL <= lastReaction) { - return false; + lastReaction = 0; + return reactionTimeActions(actor, storage, currentCell, cellChange, pos); } else - lastReaction = 0; + return false; + } - // NOTE: everything below get updated every REACTION_INTERVAL seconds - - if(mDuration) + bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) + { + if (isPackageCompleted(actor, storage)) { - // End package if duration is complete or mid-night hits: - MWWorld::TimeStamp currentTime = world->getTimeStamp(); - if(currentTime.getHour() >= mStartTime.getHour() + mDuration) - { - if(!mRepeat) - { - stopWalking(actor, storage); - return true; - } - else - mStartTime = currentTime; - } - else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()) - { - if(!mRepeat) - { - stopWalking(actor, storage); - return true; - } - else - mStartTime = currentTime; - } + return true; } // Initialization to discover & store allowed node points for this actor. - if(!mStoredAvailableNodes) + if (mPopulateAvailableNodes) { getAllowedNodes(actor, currentCell->getCell()); } @@ -398,145 +237,360 @@ namespace MWMechanics // For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere if (cellChange) mHasReturnPosition = false; - if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20) + if (mDistance == 0 && mHasReturnPosition + && (pos.asVec3() - mReturnPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE)) { - chooseAction = false; - idleNow = false; - - if (!storage.mPathFinder.isPathConstructed()) - { - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); - - // actor position is already in world co-ordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - // don't take shortcuts for wandering - storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); - - if(storage.mPathFinder.isPathConstructed()) - { - moveNow = false; - walking = true; - } - } + returnToStartLocation(actor, storage, pos); } // Allow interrupting a walking actor to trigger a greeting - if(idleNow || walking) + WanderState& wanderState = storage.mState; + if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) { - // Play a random voice greeting if the player gets too close - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - float helloDistance = static_cast(hello); - static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore() - .get().find("iGreetDistanceMultiplier")->getInt(); - - helloDistance *= iGreetDistanceMultiplier; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - float playerDistSqr = (playerPos - actorPos).length2(); - - int& greetingTimer = storage.mGreetingTimer; - if (greetingState == Greet_None) - { - if ((playerDistSqr <= helloDistance*helloDistance) && - !player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) - greetingTimer++; - - if (greetingTimer >= GREETING_SHOULD_START) - { - greetingState = Greet_InProgress; - MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); - greetingTimer = 0; - } - } - - if(greetingState == Greet_InProgress) - { - greetingTimer++; - - if(walking) - { - stopWalking(actor, storage); - moveNow = false; - walking = false; - mObstacleCheck.clear(); - idleNow = true; - getRandomIdle(playedIdle); - } - - if(!rotate) - { - osg::Vec3f dir = playerPos - actorPos; - - float faceAngleRadians = std::atan2(dir.x(), dir.y()); - targetAngleRadians = faceAngleRadians; - rotate = true; - } - - if (greetingTimer >= GREETING_SHOULD_END) - { - greetingState = Greet_Done; - greetingTimer = 0; - } - } - - if (greetingState == MWMechanics::AiWander::Greet_Done) - { - float resetDist = 2*helloDistance; - if (playerDistSqr >= resetDist*resetDist) - greetingState = Greet_None; - } + playGreetingIfPlayerGetsTooClose(actor, storage); } - if(moveNow && mDistance) + if ((wanderState == Wander_MoveNow) && mDistance) { // Construct a new path if there isn't one if(!storage.mPathFinder.isPathConstructed()) { - assert(mAllowedNodes.size()); - unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); - // NOTE: initially constructed with local (i.e. cell) co-ordinates - // convert dest to use world co-ordinates - ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); - if (currentCell->getCell()->isExterior()) + if (mAllowedNodes.size()) { - dest.mX += currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; - dest.mY += currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + setPathToAnAllowedNode(actor, storage, pos); } - - // actor position is already in world co-ordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - // don't take shortcuts for wandering - storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); - - if(storage.mPathFinder.isPathConstructed()) - { - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): - ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); - // check if mCurrentNode was taken out of mAllowedNodes - if(mTrimCurrentNode && mAllowedNodes.size() > 1) - mTrimCurrentNode = false; - else - mAllowedNodes.push_back(mCurrentNode); - mCurrentNode = temp; - - moveNow = false; - walking = true; - } - // Choose a different node and delete this one from possible nodes because it is uncreachable: - else - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); } } return false; // AiWander package not yet completed } + bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { + if (mDuration) + { + // End package if duration is complete or mid-night hits: + MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + if ((currentTime.getHour() >= mStartTime.getHour() + mDuration) || + (int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())) + { + if (!mRepeat) + { + stopWalking(actor, storage); + return true; + } + else + { + mStartTime = currentTime; + } + } + } + // if get here, not yet completed + return false; + } + + void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) + { + if (!storage.mPathFinder.isPathConstructed()) + { + ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); + + // actor position is already in world co-ordinates + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); + + // don't take shortcuts for wandering + storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); + + if (storage.mPathFinder.isPathConstructed()) + { + storage.mState = Wander_Walking; + } + } + } + + void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) + { + switch (storage.mState) + { + case Wander_IdleNow: + onIdleStatePerFrameActions(actor, duration, storage); + break; + + case Wander_Walking: + onWalkingStatePerFrameActions(actor, duration, storage, pos); + break; + + case Wander_ChooseAction: + onChooseActionStatePerFrameActions(actor, storage); + break; + + case Wander_MoveNow: + break; // nothing to do + + default: + // should never get here + assert(false); + break; + } + } + + void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) + { + // Check if an idle actor is too close to a door - if so start walking + mDoorCheckDuration += duration; + if (mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + { + mDoorCheckDuration = 0; // restart timer + if (mDistance && // actor is not intended to be stationary + proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only + { + storage.mState = Wander_MoveNow; + mTrimCurrentNode = false; // just in case + return; + } + } + + bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer; + if (rotate) + { + // Reduce the turning animation glitch by using a *HUGE* value of + // epsilon... TODO: a proper fix might be in either the physics or the + // animation subsystem + if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f))) + rotate = false; + } + + // Check if idle animation finished + GreetingState& greetingState = storage.mSaidGreeting; + if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) + { + storage.mState = Wander_ChooseAction; + } + } + + void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, + float duration, AiWanderStorage& storage, ESM::Position& pos) + { + // Are we there yet? + if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) + { + stopWalking(actor, storage); + storage.mState = Wander_ChooseAction; + mHasReturnPosition = false; + } + else + { + // have not yet reached the destination + evadeObstacles(actor, storage, duration, pos); + } + } + + void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { + + short unsigned& idleAnimation = storage.mIdleAnimation; + idleAnimation = getRandomIdle(); + + if (!idleAnimation && mDistance) + { + storage.mState = Wander_MoveNow; + return; + } + if(idleAnimation) + { + if(std::find(storage.mBadIdles.begin(), storage.mBadIdles.end(), idleAnimation)==storage.mBadIdles.end()) + { + if(!playIdle(actor, idleAnimation)) + { + storage.mBadIdles.push_back(idleAnimation); + storage.mState = Wander_ChooseAction; + return; + } + } + } + // Recreate vanilla (broken?) behavior of resetting start time of AIWander: + mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + storage.mState = Wander_IdleNow; + } + + void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) + { + // turn towards the next point in mPath + zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + + MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); + if (mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + if (proximityToDoor(actor)) // NOTE: checks interior cells only + { + // remove allowed points then select another random destination + mTrimCurrentNode = true; + trimAllowedNodes(mAllowedNodes, storage.mPathFinder); + mObstacleCheck.clear(); + storage.mPathFinder.clearPath(); + storage.mState = Wander_MoveNow; + } + else // probably walking into another NPC + { + // TODO: diagonal should have same animation as walk forward + // but doesn't seem to do that? + mObstacleCheck.takeEvasiveAction(movement); + } + mStuckCount++; // TODO: maybe no longer needed + } + else + { + movement.mPosition[1] = 1; + } + + // if stuck for sufficiently long, act like current location was the destination + if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; + mObstacleCheck.clear(); + + stopWalking(actor, storage); + storage.mState = Wander_ChooseAction; + mStuckCount = 0; + } + } + + void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor) + { + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) + && MWBase::Environment::get().getSoundManager()->sayDone(actor)) + { + MWWorld::Ptr player = getPlayer(); + + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + float roll = Misc::Rng::rollProbability() * 10000.0f; + + // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds + // due to the roll being an integer. + // Our implementation does not have these issues, so needs to be recalibrated. We chose to + // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. + float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f); + + // Only say Idle voices when player is in LOS + // A bit counterintuitive, likely vanilla did this to reduce the appearance of + // voices going through walls? + const ESM::Position& pos = actor.getRefData().getPosition(); + if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2() + < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead + && MWBase::Environment::get().getWorld()->getLOS(player, actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + } + + void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { + // Play a random voice greeting if the player gets too close + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + float helloDistance = static_cast(hello); + static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() + .get().find("iGreetDistanceMultiplier")->getInt(); + + helloDistance *= iGreetDistanceMultiplier; + + MWWorld::Ptr player = getPlayer(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + float playerDistSqr = (playerPos - actorPos).length2(); + + int& greetingTimer = storage.mGreetingTimer; + GreetingState& greetingState = storage.mSaidGreeting; + if (greetingState == Greet_None) + { + if ((playerDistSqr <= helloDistance*helloDistance) && + !player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) + greetingTimer++; + + if (greetingTimer >= GREETING_SHOULD_START) + { + greetingState = Greet_InProgress; + MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + greetingTimer = 0; + } + } + + if (greetingState == Greet_InProgress) + { + greetingTimer++; + + if (storage.mState == Wander_Walking) + { + stopWalking(actor, storage); + mObstacleCheck.clear(); + storage.mState = Wander_IdleNow; + } + + turnActorToFacePlayer(actorPos, playerPos, storage); + + if (greetingTimer >= GREETING_SHOULD_END) + { + greetingState = Greet_Done; + greetingTimer = 0; + } + } + + if (greetingState == MWMechanics::AiWander::Greet_Done) + { + float resetDist = 2 * helloDistance; + if (playerDistSqr >= resetDist*resetDist) + greetingState = Greet_None; + } + } + + void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage) + { + osg::Vec3f dir = playerPosition - actorPosition; + + float faceAngleRadians = std::atan2(dir.x(), dir.y()); + storage.mTargetAngleRadians = faceAngleRadians; + storage.mTurnActorGivingGreetingToFacePlayer = true; + } + + void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) + { + unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); + ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); + ToWorldCoordinates(dest, storage.mCell->getCell()); + + // actor position is already in world co-ordinates + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); + + // don't take shortcuts for wandering + storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); + + if (storage.mPathFinder.isPathConstructed()) + { + // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): + ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; + mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + // check if mCurrentNode was taken out of mAllowedNodes + if (mTrimCurrentNode && mAllowedNodes.size() > 1) + mTrimCurrentNode = false; + else + mAllowedNodes.push_back(mCurrentNode); + mCurrentNode = temp; + + storage.mState = Wander_Walking; + } + // Choose a different node and delete this one from possible nodes because it is uncreachable: + else + mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + } + + void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) + { + CoordinateConverter(cell).toWorld(point); + } + void AiWander::trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder) { @@ -574,12 +628,17 @@ namespace MWMechanics actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } - void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) + bool AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) { if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) { const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); + return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); + } + else + { + std::cerr<< "Attempted to play out of range idle animation \""< idleRoll) { - playedIdle = counter+2; + selectedAnimation = counter + GroupIndex_MinIdle; idleRoll = randSelect; } } + return selectedAnimation; } void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) @@ -629,7 +690,7 @@ namespace MWMechanics if (mDistance == 0) return; - if (!mStoredAvailableNodes) + if (mPopulateAvailableNodes) getAllowedNodes(actor, actor.getCell()->getCell()); if (mAllowedNodes.empty()) @@ -640,22 +701,21 @@ namespace MWMechanics int index = Misc::Rng::rollDice(mAllowedNodes.size()); ESM::Pathgrid::Point dest = mAllowedNodes[index]; - // apply a slight offset to prevent overcrowding - dest.mX += static_cast(Misc::Rng::rollProbability() * 128 - 64); - dest.mY += static_cast(Misc::Rng::rollProbability() * 128 - 64); - - if (actor.getCell()->isExterior()) - { - dest.mX += actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE; - dest.mY += actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE; - } + dest.mX += OffsetToPreventOvercrowding(); + dest.mY += OffsetToPreventOvercrowding(); + ToWorldCoordinates(dest, actor.getCell()->getCell()); MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); // may have changed cell - mStoredAvailableNodes = false; + mPopulateAvailableNodes = true; + } + + int AiWander::OffsetToPreventOvercrowding() + { + return static_cast(DESTINATION_TOLERANCE * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) @@ -684,44 +744,82 @@ namespace MWMechanics // ... pathgrids don't usually include water, so swimmers ignore them if (mDistance && !actor.getClass().isPureWaterCreature(actor)) { - float cellXOffset = 0; - float cellYOffset = 0; - if(cell->isExterior()) - { - cellXOffset = static_cast(cell->mData.mX * ESM::Land::REAL_SIZE); - cellYOffset = static_cast(cell->mData.mY * ESM::Land::REAL_SIZE); - } - - // convert npcPos to local (i.e. cell) co-ordinates + // get NPC's position in local (i.e. cell) co-ordinates osg::Vec3f npcPos(mInitialActorPosition); - npcPos[0] = npcPos[0] - cellXOffset; - npcPos[1] = npcPos[1] - cellYOffset; + CoordinateConverter(cell).toLocal(npcPos); // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // NOTE: mPoints and mAllowedNodes are in local co-ordinates + int pointIndex = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance) + { mAllowedNodes.push_back(pathgrid->mPoints[counter]); + pointIndex = counter; + } + } + if (mAllowedNodes.size() == 1) + { + AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex); } if(!mAllowedNodes.empty()) { - osg::Vec3f firstNodePos(PathFinder::MakeOsgVec3(mAllowedNodes[0])); - float closestNode = (npcPos - firstNodePos).length2(); - unsigned int index = 0; - for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) - { - osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); - float tempDist = (npcPos - nodePos).length2(); - if(tempDist < closestNode) - index = counterThree; - } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); + SetCurrentNodeToClosestAllowedNode(npcPos); } - mStoredAvailableNodes = true; // set only if successful in finding allowed nodes } + + mPopulateAvailableNodes = false; + } + + // When only one path grid point in wander distance, + // additional points for NPC to wander to are: + // 1. NPC's initial location + // 2. Partway along the path between the point and its connected points. + void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex) + { + mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); + for (std::vector::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it) + { + if (it->mV0 == pointIndex) + { + AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]); + } + } + } + + void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end) + { + osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start); + osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart; + float length = delta.length(); + delta.normalize(); + + int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); + + // must not travel longer than distance between waypoints or NPC goes past waypoint + distance = std::min(distance, static_cast(length)); + delta *= distance; + mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); + } + + void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos) + { + float distanceToClosestNode = std::numeric_limits::max(); + unsigned int index = 0; + for (unsigned int counterThree = 0; counterThree < mAllowedNodes.size(); counterThree++) + { + osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); + float tempDist = (npcPos - nodePos).length2(); + if (tempDist < distanceToClosestNode) + { + index = counterThree; + distanceToClosestNode = tempDist; + } + } + mCurrentNode = mAllowedNodes[index]; + mAllowedNodes.erase(mAllowedNodes.begin() + index); } void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 75b223094..b0fabfce3 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -63,14 +63,37 @@ namespace MWMechanics Greet_InProgress, Greet_Done }; + + enum WanderState { + Wander_ChooseAction, + Wander_IdleNow, + Wander_MoveNow, + Wander_Walking + }; private: // NOTE: mDistance and mDuration must be set already void init(); void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + + /// Have the given actor play an idle animation + /// @return Success or error + bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - void getRandomIdle(unsigned short& playedIdle); + short unsigned getRandomIdle(); + void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); + void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos); + void playIdleDialogueRandomly(const MWWorld::Ptr& actor); + void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); + void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); + void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); + void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); + void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); + bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); + bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); int mDistance; // how far the actor can wander from the spawn point int mDuration; @@ -88,8 +111,8 @@ namespace MWMechanics - // if false triggers calculating allowed nodes based on mDistance - bool mStoredAvailableNodes; + // do we need to calculate allowed nodes based on mDistance + bool mPopulateAvailableNodes; @@ -118,8 +141,19 @@ namespace MWMechanics GroupIndex_MaxIdle = 9 }; + /// convert point from local (i.e. cell) to world co-ordinates + void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); + + void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos); + + void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex); + + void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end); + /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; + + static int OffsetToPreventOvercrowding(); }; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index dd25e5a55..38d85a7cd 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -1,4 +1,3 @@ - #include "alchemy.hpp" #include diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index e4b143826..b798ff5dc 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -1,6 +1,7 @@ #include "autocalcspell.hpp" #include +#include #include "../mwworld/esmstore.hpp" @@ -181,7 +182,7 @@ namespace MWMechanics void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) { - float minChance = FLT_MAX; + float minChance = std::numeric_limits::max(); const ESM::EffectList& effects = spell->mEffects; for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 4b2ce9f4c..36c251053 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -27,6 +27,7 @@ #include "npcstats.hpp" #include "creaturestats.hpp" #include "security.hpp" +#include "actorutil.hpp" #include @@ -43,6 +44,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" namespace { @@ -236,7 +238,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i return prefix + toString(roll); } -void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force) +void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // hit recoils/knockdown animations handling if(mPtr.getClass().isActor()) @@ -246,31 +248,38 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); if(mHitState == CharState_None) { - if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 + if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) + && mAnimation->hasAnimation("knockout")) { mHitState = CharState_KnockOut; mCurrentHit = "knockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); } - else if(knockdown) + else if(knockdown && mAnimation->hasAnimation("knockdown")) { mHitState = CharState_KnockDown; mCurrentHit = "knockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_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); + std::string anim = chooseRandomGroup("hit"); + if (mAnimation->hasAnimation(anim)) + { + mHitState = CharState_Hit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } } - else if (block) + else if (block && mAnimation->hasAnimation("shield")) { mHitState = CharState_Block; mCurrentHit = "shield"; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); + MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); + priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); } // Cancel upper body animations @@ -303,7 +312,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat { mHitState = CharState_KnockDown; mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); } } @@ -311,40 +320,41 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (!mPtr.getClass().isBipedal(mPtr)) weap = sWeaponTypeListEnd; - if(force && mJumpState != JumpState_None) + if(force || jump != mJumpState) { - std::string jump; - MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All; + bool startAtLoop = (jump == mJumpState); + mJumpState = jump; + + std::string jumpAnimName; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; if(mJumpState != JumpState_None) { - jump = "jump"; + jumpAnimName = "jump"; if(weap != sWeaponTypeListEnd) { - jump += weap->shortgroup; - if(!mAnimation->hasAnimation(jump)) + jumpAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(jumpAnimName)) { - jumpgroup = MWRender::Animation::Group_LowerBody; - jump = "jump"; + jumpmask = MWRender::Animation::BlendMask_LowerBody; + jumpAnimName = "jump"; } } } if(mJumpState == JumpState_InAir) { - int mode = ((jump == mCurrentJump) ? 2 : 1); - mAnimation->disable(mCurrentJump); - mCurrentJump = jump; + mCurrentJump = jumpAnimName; if (mAnimation->hasAnimation("jump")) - mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, - 1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, + 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); } else { mAnimation->disable(mCurrentJump); mCurrentJump.clear(); if (mAnimation->hasAnimation("jump")) - mAnimation->play(jump, Priority_Jump, jumpgroup, true, + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -353,65 +363,65 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat { mMovementState = movement; - std::string movement; - MWRender::Animation::Group movegroup = MWRender::Animation::Group_All; + std::string movementAnimName; + MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); if(movestate != sMovementListEnd) { - movement = movestate->groupname; - if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos) + movementAnimName = movestate->groupname; + if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) { - movement += weap->shortgroup; - if(!mAnimation->hasAnimation(movement)) + movementAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(movementAnimName)) { - movegroup = MWRender::Animation::Group_LowerBody; - movement = movestate->groupname; + movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName = movestate->groupname; } } - if(!mAnimation->hasAnimation(movement)) + if(!mAnimation->hasAnimation(movementAnimName)) { - std::string::size_type swimpos = movement.find("swim"); + std::string::size_type swimpos = movementAnimName.find("swim"); if(swimpos == std::string::npos) { - std::string::size_type runpos = movement.find("run"); + std::string::size_type runpos = movementAnimName.find("run"); if (runpos != std::string::npos) { - movement.replace(runpos, runpos+3, "walk"); - if (!mAnimation->hasAnimation(movement)) - movement.clear(); + movementAnimName.replace(runpos, runpos+3, "walk"); + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); } else - movement.clear(); + movementAnimName.clear(); } else { - movegroup = MWRender::Animation::Group_LowerBody; - movement.erase(swimpos, 4); - if(!mAnimation->hasAnimation(movement)) - movement.clear(); + if (weap != sWeaponTypeListEnd) + movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName.erase(swimpos, 4); + if(!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); } } } /* If we're playing the same animation, restart from the loop start instead of the * beginning. */ - int mode = ((movement == mCurrentMovement) ? 2 : 1); + int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1); mMovementAnimationControlled = true; mAnimation->disable(mCurrentMovement); - mCurrentMovement = movement; + mCurrentMovement = movementAnimName; if(!mCurrentMovement.empty()) { - float vel, speedmult = 1.0f; - bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !MWBase::Environment::get().getWorld()->isFlying(mPtr); // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. std::string anim = mCurrentMovement; + mAdjustMovementAnimSpeed = true; if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) { @@ -419,36 +429,34 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); anim = stateinfo->groupname; - if (mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - speedmult = mMovementSpeed / vel; - else + mMovementAnimSpeed = mAnimation->getVelocity(anim); + if (mMovementAnimSpeed <= 1.0f) + { // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist // we will play without any scaling. // Makes the speed attribute of most water creatures totally useless. // And again, this can not be fixed without patching game data. - speedmult = 1.f; + mAdjustMovementAnimSpeed = false; + mMovementAnimSpeed = 1.f; + } } else { - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - { - speedmult = mMovementSpeed / vel; - } - else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) - speedmult = 1.f; // adjusted each frame - else if (mMovementSpeed > 0.0f) + mMovementAnimSpeed = mAnimation->getVelocity(anim); + + if (mMovementAnimSpeed <= 1.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); + mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); mMovementAnimationControlled = false; } } - mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, - speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, + 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } } @@ -456,7 +464,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed if ((mUpperBodyState != UpperCharState_Nothing - || mMovementState != CharState_None + || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight) || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) idle = CharState_None; @@ -466,12 +474,19 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mIdleState = idle; std::string idle; + MWRender::Animation::AnimPriority idlePriority (Priority_Default); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + { idle = "idleswim"; + idlePriority = Priority_SwimIdle; + } else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + { idle = "idlesneak"; + idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + } else if(mIdleState != CharState_None) { idle = "idle"; @@ -486,7 +501,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mAnimation->disable(mCurrentIdle); mCurrentIdle = idle; if(!mCurrentIdle.empty()) - mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, + mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } @@ -602,13 +617,13 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mCurrentJump = ""; mMovementAnimationControlled = true; - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startpoint, 0); } void CharacterController::playRandomDeath(float startpoint) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) { // The first-person animations do not include death, so we need to // force-switch to third person before playing the death animation. @@ -641,7 +656,6 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mAnimation(anim) , mIdleState(CharState_None) , mMovementState(CharState_None) - , mMovementSpeed(0.0f) , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) @@ -690,8 +704,9 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; else { - int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); - playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); + // Set the death state, but don't play it yet + // We will play it in the first frame, but only if no script set the skipAnim flag + mDeathState = static_cast(CharState_Death1 + mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation()); } } else @@ -704,7 +719,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if(mDeathState == CharState_None) - refreshCurrentAnims(mIdleState, mMovementState, true); + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); } @@ -868,10 +883,10 @@ void CharacterController::updateIdleStormState() mAnimation->getInfo("idlestorm", &complete); if (complete == 0) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, 1.0f, "start", "loop start", 0.0f, 0); else if (complete == 1) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, 1.0f, "loop start", "loop stop", 0.0f, ~0ul); } else @@ -882,7 +897,7 @@ void CharacterController::updateIdleStormState() { if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) { - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -989,7 +1004,7 @@ bool CharacterController::updateCreatureState() if (!mCurrentWeapon.empty()) { mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_All, true, + MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; @@ -1051,6 +1066,9 @@ bool CharacterController::updateWeaponState() } } + MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + bool forcestateupdate = false; if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut && mHitState != CharState_Hit) @@ -1063,8 +1081,8 @@ bool CharacterController::updateWeaponState() if(weaptype == WeapType_None) { getWeaponGroup(mWeaponType, weapgroup); - mAnimation->play(weapgroup, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperCharState_UnEquipingWeap; } @@ -1074,8 +1092,8 @@ bool CharacterController::updateWeaponState() mAnimation->showWeapons(false); mAnimation->setWeaponGroup(weapgroup); - mAnimation->play(weapgroup, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; @@ -1145,7 +1163,7 @@ bool CharacterController::updateWeaponState() bool animPlaying; if(mAttackingOrSpell) { - if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None) + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackType.clear(); @@ -1154,13 +1172,17 @@ bool CharacterController::updateWeaponState() // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation mAttackingOrSpell = false; + if (mPtr == getPlayer()) + { + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + } const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); // 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 == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); @@ -1191,8 +1213,8 @@ bool CharacterController::updateWeaponState() case 2: mAttackType = "target"; break; } - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, weapSpeed, mAttackType+" start", mAttackType+" stop", 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; @@ -1223,8 +1245,8 @@ bool CharacterController::updateWeaponState() else if(item.getTypeName() == typeid(ESM::Probe).name()) Security(mPtr).probeTrap(target, item, resultMessage, resultSound); } - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, 1.0f, "start", "stop", 0.0, 0); mUpperBodyState = UpperCharState_FollowStartToFollowStop; @@ -1240,7 +1262,7 @@ bool CharacterController::updateWeaponState() mAttackType = "shoot"; else { - if(isWeapon && mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr() && + if(isWeapon && mPtr == getPlayer() && Settings::Manager::getBool("best attack", "Game")) { MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -1250,8 +1272,8 @@ bool CharacterController::updateWeaponState() determineAttackType(); } - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" start", mAttackType+" min attack", 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; @@ -1301,8 +1323,8 @@ bool CharacterController::updateWeaponState() mAttackStrength = attackStrength; mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); @@ -1371,15 +1393,6 @@ bool CharacterController::updateWeaponState() 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; @@ -1397,8 +1410,8 @@ bool CharacterController::updateWeaponState() 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, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; case UpperCharState_MaxAttackToMinHit: @@ -1440,33 +1453,16 @@ bool CharacterController::updateWeaponState() { mAnimation->disable(mCurrentWeapon); if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, weapSpeed, start, stop, 0.0f, 0); else - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, weapSpeed, start, stop, 0.0f, 0); } } - //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || mUpperBodyState == 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->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody); - } - else - { - mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All); - } - } - if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -1475,7 +1471,7 @@ bool CharacterController::updateWeaponState() && updateCarriedLeftVisible(mWeaponType)) { - mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); } else if (mAnimation->isPlaying("torch")) @@ -1492,6 +1488,7 @@ void CharacterController::update(float duration) MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); osg::Vec3f movement(0.f, 0.f, 0.f); + float speed = 0.f; updateMagicEffects(); @@ -1505,7 +1502,7 @@ void CharacterController::update(float duration) mAnimQueue.pop_front(); mAnimation->play(mAnimQueue.front().first, Priority_Default, - MWRender::Animation::Group_All, false, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); } } @@ -1555,13 +1552,15 @@ void CharacterController::update(float duration) vec = osg::Vec3f(0.f, 0.f, 0.f); osg::Vec3f rot = cls.getRotationVector(mPtr); - mMovementSpeed = cls.getSpeed(mPtr); + speed = cls.getSpeed(mPtr); - vec.x() *= mMovementSpeed; - vec.y() *= mMovementSpeed; + vec.x() *= speed; + vec.y() *= speed; CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; + JumpingState jumpstate = JumpState_None; + bool forcestateupdate = false; mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; @@ -1569,7 +1568,7 @@ void CharacterController::update(float duration) // advance athletics - if(mHasMovedInXY && mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(mHasMovedInXY && mPtr == getPlayer()) { if(inwater) { @@ -1644,7 +1643,7 @@ void CharacterController::update(float duration) } forcestateupdate = (mJumpState != JumpState_InAir); - mJumpState = JumpState_InAir; + jumpstate = JumpState_InAir; static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); @@ -1670,7 +1669,7 @@ void CharacterController::update(float duration) } // advance acrobatics - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue @@ -1689,7 +1688,7 @@ void CharacterController::update(float duration) else if(mJumpState == JumpState_InAir) { forcestateupdate = true; - mJumpState = JumpState_Landing; + jumpstate = JumpState_Landing; vec.z() = 0.0f; float height = cls.getCreatureStats(mPtr).land(); @@ -1713,14 +1712,14 @@ void CharacterController::update(float duration) else { // report acrobatics progression - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } } else { - mJumpState = JumpState_None; + jumpstate = JumpState_None; vec.z() = 0.0f; inJump = false; @@ -1747,10 +1746,7 @@ void CharacterController::update(float duration) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - // Don't play turning animations during attack. It would break positioning of the arrow bone when releasing a shot. - // Actually, in vanilla the turning animation is not even played when merely having equipped the weapon, - // but I don't think we need to go as far as that. - else if(rot.z() != 0.0f && !inwater && !sneak && mUpperBodyState < UpperCharState_StartToMinAttack) + else if(rot.z() != 0.0f && !inwater && !sneak && !MWBase::Environment::get().getWorld()->isFirstPerson()) { if(rot.z() > 0.0f) movestate = CharState_TurnRight; @@ -1776,7 +1772,7 @@ void CharacterController::update(float duration) if(mAnimQueue.empty() || inwater || sneak) { - idlestate = (inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle)); + idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); } else if(mAnimQueue.size() > 1) { @@ -1786,19 +1782,22 @@ void CharacterController::update(float duration) mAnimQueue.pop_front(); mAnimation->play(mAnimQueue.front().first, Priority_Default, - MWRender::Animation::Group_All, false, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); } } - // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. - if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) - forcestateupdate = updateWeaponState() || forcestateupdate; - else - forcestateupdate = updateCreatureState() || forcestateupdate; - if (!mSkipAnim) - refreshCurrentAnims(idlestate, movestate, forcestateupdate); + { + // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. + if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) + forcestateupdate = updateWeaponState() || forcestateupdate; + else + forcestateupdate = updateCreatureState() || forcestateupdate; + + refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); + } + if (inJump) mMovementAnimationControlled = false; @@ -1807,6 +1806,11 @@ void CharacterController::update(float duration) if (duration > 0) mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); } + else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) + { + float speedmult = speed / mMovementAnimSpeed; + mAnimation->adjustSpeedMult(mCurrentMovement, speedmult); + } if (!mSkipAnim) { @@ -1835,6 +1839,13 @@ void CharacterController::update(float duration) } else if(cls.getCreatureStats(mPtr).isDead()) { + // initial start of death animation for actors that started the game as dead + // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + { + playDeath(1.f, mDeathState); + } + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } @@ -1845,7 +1856,7 @@ void CharacterController::update(float duration) moved = osg::Vec3f(0.f, 0.f, 0.f); // Ensure we're moving in generally the right direction... - if(mMovementSpeed > 0.f) + if(speed > 0.f) { float l = moved.length(); @@ -1877,10 +1888,13 @@ void CharacterController::update(float duration) } -void CharacterController::playGroup(const std::string &groupname, int mode, int count) +bool CharacterController::playGroup(const std::string &groupname, int mode, int count) { if(!mAnimation || !mAnimation->hasAnimation(groupname)) + { std::cerr<< "Animation "<play(groupname, Priority_Default, - MWRender::Animation::Group_All, false, 1.0f, + MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1); } else if(mode == 0) @@ -1905,6 +1919,7 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int mAnimQueue.push_back(std::make_pair(groupname, count-1)); } } + return true; } void CharacterController::skipAnim() @@ -1927,14 +1942,13 @@ void CharacterController::clearAnimQueue() mAnimQueue.clear(); } - void CharacterController::forceStateUpdate() { if(!mAnimation) return; clearAnimQueue(); - refreshCurrentAnims(mIdleState, mMovementState, true); + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); if(mDeathState != CharState_None) { playRandomDeath(); @@ -1947,7 +1961,7 @@ bool CharacterController::kill() { if( isDead() ) { - if( mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr() && !isAnimPlaying(mCurrentDeath) ) + if( mPtr == getPlayer() && !isAnimPlaying(mCurrentDeath) ) { //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); @@ -1963,7 +1977,7 @@ bool CharacterController::kill() mCurrentIdle.clear(); // Play Death Music if it was the player dying - if(mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(mPtr == getPlayer()) MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); return true; @@ -2004,7 +2018,7 @@ void CharacterController::updateMagicEffects() float alpha = 1.f; if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) alpha = 0.4f; else alpha = 0.f; @@ -2045,6 +2059,15 @@ bool CharacterController::isKnockedOut() const return mHitState == CharState_KnockOut; } +bool CharacterController::isSneaking() const +{ + return mIdleState == CharState_IdleSneak || + mMovementState == CharState_SneakForward || + mMovementState == CharState_SneakBack || + mMovementState == CharState_SneakLeft || + mMovementState == CharState_SneakRight; +} + void CharacterController::setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; @@ -2052,12 +2075,13 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell) bool CharacterController::readyToPrepareAttack() const { - return mHitState == CharState_None && mUpperBodyState <= UpperCharState_WeapEquiped; + return (mHitState == CharState_None || mHitState == CharState_Block) + && mUpperBodyState <= UpperCharState_WeapEquiped; } bool CharacterController::readyToStartAttack() const { - if (mHitState != CharState_None) + if (mHitState != CharState_None && mHitState != CharState_Block) return false; if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index b239b4a92..fee7b959c 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -28,10 +28,14 @@ class CreatureStats; enum Priority { Priority_Default, + Priority_WeaponLowerBody, + Priority_SneakIdleLowerBody, + Priority_SwimIdle, Priority_Jump, Priority_Movement, Priority_Hit, Priority_Weapon, + Priority_Block, Priority_Knockdown, Priority_Torch, Priority_Storm, @@ -149,7 +153,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener CharacterState mMovementState; std::string mCurrentMovement; - float mMovementSpeed; + float mMovementAnimSpeed; + bool mAdjustMovementAnimSpeed; bool mHasMovedInXY; bool mMovementAnimationControlled; @@ -185,7 +190,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void determineAttackType(); - void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); + void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void clearAnimQueue(); @@ -222,7 +227,7 @@ public: void update(float duration); - void playGroup(const std::string &groupname, int mode, int count); + bool playGroup(const std::string &groupname, int mode, int count); void skipAnim(); bool isAnimPlaying(const std::string &groupName); @@ -237,6 +242,7 @@ public: bool isReadyToBlock() const; bool isKnockedOut() const; + bool isSneaking() const; void setAttackingOrSpell(bool attackingOrSpell); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 097dcadc2..f11e6bcfd 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -19,6 +19,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwbase/windowmanager.hpp" +#include "actorutil.hpp" namespace { @@ -60,7 +61,7 @@ namespace MWMechanics if (blockerStats.getKnockedDown() // Used for both knockout or knockdown || blockerStats.getHitRecovery() - || blockerStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + || blockerStats.isParalyzed()) return false; if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker)) @@ -137,7 +138,7 @@ namespace MWMechanics blockerStats.setBlock(true); - if (blocker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (blocker == getPlayer()) blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); return true; @@ -147,9 +148,9 @@ namespace MWMechanics 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).getMagnitude() - - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude()); + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + float resistance = std::min(100.f, effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() + - effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude()); float multiplier = 1.f - resistance / 100.f; @@ -161,7 +162,7 @@ namespace MWMechanics && actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->getFloat(); - if (damage == 0 && attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (damage == 0 && attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } @@ -178,7 +179,7 @@ namespace MWMechanics return; } - if(attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::Marksman; @@ -207,7 +208,7 @@ namespace MWMechanics adjustWeaponDamage(damage, weapon, attacker); reduceWeaponCondition(damage, true, weapon, attacker); - if(attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(attacker == getPlayer()) attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); if (victim.getClass().getCreatureStats(victim).getKnockedDown()) @@ -222,7 +223,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory - if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr() + if (victim != getPlayer() && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); @@ -242,15 +243,15 @@ namespace MWMechanics const MWWorld::Store &gmst = world->getStore().get(); float defenseTerm = 0; - if (victim.getClass().getCreatureStats(victim).getFatigue().getCurrent() >= 0) + MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); + if (victimStats.getFatigue().getCurrent() >= 0) { - MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); // Maybe we should keep an aware state for actors updated every so often instead of testing every time bool unaware = (!victimStats.getAiSequence().isInCombat()) - && (attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + && (attacker == getPlayer()) && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim)); if (!(victimStats.getKnockedDown() || - victimStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0 + victimStats.isParalyzed() || unaware )) { defenseTerm = victimStats.getEvasion(); @@ -375,7 +376,7 @@ namespace MWMechanics damage *= minstrike + ((maxstrike-minstrike)*attackStrength); MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim); - healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + healthdmg = otherstats.isParalyzed() || otherstats.getKnockedDown(); bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()); if(isWerewolf) diff --git a/apps/openmw/mwmechanics/coordinateconverter.cpp b/apps/openmw/mwmechanics/coordinateconverter.cpp new file mode 100644 index 000000000..0a2d99f92 --- /dev/null +++ b/apps/openmw/mwmechanics/coordinateconverter.cpp @@ -0,0 +1,43 @@ +#include "coordinateconverter.hpp" + +#include +#include + +namespace MWMechanics +{ + CoordinateConverter::CoordinateConverter(const ESM::Cell* cell) + : mCellX(0), mCellY(0) + { + if (cell->isExterior()) + { + mCellX = cell->mData.mX * ESM::Land::REAL_SIZE; + mCellY = cell->mData.mY * ESM::Land::REAL_SIZE; + } + } + + void CoordinateConverter::toWorld(ESM::Pathgrid::Point& point) + { + point.mX += mCellX; + point.mY += mCellY; + } + + void CoordinateConverter::toWorld(osg::Vec3f& point) + { + point.x() += static_cast(mCellX); + point.y() += static_cast(mCellY); + } + + void CoordinateConverter::toLocal(osg::Vec3f& point) + { + point.x() -= static_cast(mCellX); + point.y() -= static_cast(mCellY); + } + + osg::Vec3f CoordinateConverter::toLocalVec3(const ESM::Pathgrid::Point& point) + { + return osg::Vec3f( + static_cast(point.mX - mCellX), + static_cast(point.mY - mCellY), + static_cast(point.mZ)); + } +} diff --git a/apps/openmw/mwmechanics/coordinateconverter.hpp b/apps/openmw/mwmechanics/coordinateconverter.hpp new file mode 100644 index 000000000..cd855e84a --- /dev/null +++ b/apps/openmw/mwmechanics/coordinateconverter.hpp @@ -0,0 +1,37 @@ +#ifndef GAME_MWMECHANICS_COORDINATECONVERTER_H +#define GAME_MWMECHANICS_COORDINATECONVERTER_H + +#include +#include + +namespace ESM +{ + struct Cell; +} + +namespace MWMechanics +{ + /// \brief convert coordinates between world and local cell + class CoordinateConverter + { + public: + CoordinateConverter(const ESM::Cell* cell); + + /// in-place conversion from local to world + void toWorld(ESM::Pathgrid::Point& point); + + /// in-place conversion from local to world + void toWorld(osg::Vec3f& point); + + /// in-place conversion from world to local + void toLocal(osg::Vec3f& point); + + osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point); + + private: + int mCellX; + int mCellY; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 48374c173..0638637fa 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -221,6 +221,11 @@ namespace MWMechanics setAiSetting(index, stat); } + bool CreatureStats::isParalyzed() const + { + return mMagicEffects.get(ESM::MagicEffect::Paralyze).getMagnitude() > 0; + } + bool CreatureStats::isDead() const { return mDead; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 5d22da7cc..46c5bab31 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -155,6 +155,8 @@ namespace MWMechanics float getFatigueTerm() const; ///< Return effective fatigue + bool isParalyzed() const; + bool isDead() const; void notifyDied(); diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index 05ab12ccd..950fe33cf 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -6,9 +6,11 @@ #include +#include "actorutil.hpp" + float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = MWMechanics::getPlayer(); // [-100, 100] int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index cf21f3806..27c19364f 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -9,8 +9,10 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/spells.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "spells.hpp" +#include "creaturestats.hpp" +#include "actorutil.hpp" + namespace MWMechanics { @@ -20,7 +22,7 @@ namespace MWMechanics /// @param carrier The disease carrier. inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) { - if (!carrier.getClass().isActor() || actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (!carrier.getClass().isActor() || actor != getPlayer()) return; float fDiseaseXferChance = diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 2cb963e28..102e3787a 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -11,6 +11,7 @@ #include "creaturestats.hpp" #include "npcstats.hpp" #include "spellcasting.hpp" +#include "actorutil.hpp" namespace MWMechanics { @@ -54,7 +55,7 @@ namespace MWMechanics bool Enchanting::create() { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); ESM::Enchantment enchantment; enchantment.mData.mCharge = getGemCharge(); @@ -217,7 +218,7 @@ namespace MWMechanics int Enchanting::getEffectiveCastCost() const { int baseCost = getBaseCastCost(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); return getEffectiveEnchantmentCastCost(static_cast(baseCost), player); } @@ -291,7 +292,7 @@ namespace MWMechanics void Enchanting::payForEnchantment() const { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index f2f0c7cab..edfef5358 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -11,7 +11,8 @@ #include "../mwworld/class.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "creaturestats.hpp" +#include "actorutil.hpp" namespace MWMechanics { @@ -21,7 +22,7 @@ namespace MWMechanics { const std::vector& items = levItem->mList; - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = getPlayer(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); failChance += levItem->mChanceNone; diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 0b19df0a8..021691d6a 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,4 +1,3 @@ - #include "magiceffects.hpp" #include diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fef99dc61..12927101d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -26,6 +26,7 @@ #include "spellcasting.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" +#include "actorutil.hpp" namespace { @@ -83,7 +84,7 @@ namespace MWMechanics { void MechanicsManager::buildPlayer() { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr ptr = getPlayer(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); @@ -361,7 +362,7 @@ namespace MWMechanics { // Uses ingame time, but scaled to real time duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); player.getClass().getInventoryStore(player).rechargeItems(duration); } @@ -489,7 +490,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()->getPlayerPtr(); + MWWorld::Ptr ptr = getPlayer(); mActors.removeActor(ptr); mActors.addActor(ptr, true); } @@ -586,7 +587,7 @@ namespace MWMechanics float x = static_cast(npcSkill.getBaseDisposition()); MWWorld::LiveCellRef* npc = ptr.get(); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr playerPtr = getPlayer(); MWWorld::LiveCellRef* player = playerPtr.get(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); @@ -663,7 +664,7 @@ namespace MWMechanics const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); // I suppose the temporary disposition change _has_ to be considered here, @@ -707,7 +708,7 @@ namespace MWMechanics MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); float npcRating1, npcRating2, npcRating3; @@ -843,12 +844,12 @@ namespace MWMechanics mActors.forceStateUpdate(ptr); } - void MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + bool MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { if(ptr.getClass().isActor()) - mActors.playAnimationGroup(ptr, groupName, mode, number); + return mActors.playAnimationGroup(ptr, groupName, mode, number); else - mObjects.playAnimationGroup(ptr, groupName, mode, number); + return mObjects.playAnimationGroup(ptr, groupName, mode, number); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { @@ -926,7 +927,7 @@ namespace MWMechanics return true; } - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + if(MWMechanics::isPlayerInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); return true; } @@ -1010,7 +1011,7 @@ namespace MWMechanics void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, int count) { - if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr != getPlayer()) return; MWWorld::Ptr victim; @@ -1049,12 +1050,25 @@ namespace MWMechanics commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } + + void getFollowers (const MWWorld::Ptr& actor, std::set& out) + { + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); + for(std::list::iterator it = followers.begin();it != followers.end();++it) + { + if (out.insert(*it).second) + { + getFollowers(*it, out); + } + } + } + bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg, bool victimAware) { // NOTE: victim may be empty // Only player can commit crime - if (player != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (player != getPlayer()) return false; // Find all the actors within the alarm radius @@ -1070,6 +1084,10 @@ namespace MWMechanics if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) neighbors.push_back(victim); + // get the player's followers / allies (works recursively) that will not report crimes + std::set playerFollowers; + getFollowers(player, playerFollowers); + // Did anyone see it? bool crimeSeen = false; for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) @@ -1085,11 +1103,6 @@ namespace MWMechanics // TODO: Add mod support for stealth executions! || (type == OT_Murder && *it != victim)) { - if (type == OT_Theft || type == OT_Pickpocket) - MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - else if (type == OT_Trespassing) - MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); - // Crime reporting only applies to NPCs if (!it->getClass().isNpc()) continue; @@ -1097,6 +1110,14 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; + if (playerFollowers.find(*it) != playerFollowers.end()) + continue; + + if (type == OT_Theft || type == OT_Pickpocket) + MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + else if (type == OT_Trespassing) + MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); + crimeSeen = true; } } @@ -1286,15 +1307,16 @@ namespace MWMechanics bool MechanicsManager::actorAttacked(const MWWorld::Ptr &ptr, const MWWorld::Ptr &attacker) { - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == getPlayer()) return false; std::list followers = getActorsFollowing(attacker); + MWMechanics::CreatureStats& targetStats = ptr.getClass().getCreatureStats(ptr); if (std::find(followers.begin(), followers.end(), ptr) != followers.end()) { - ptr.getClass().getCreatureStats(ptr).friendlyHit(); + targetStats.friendlyHit(); - if (ptr.getClass().getCreatureStats(ptr).getFriendlyHits() < 4) + if (targetStats.getFriendlyHits() < 4) { MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); return false; @@ -1302,7 +1324,7 @@ namespace MWMechanics } // Attacking an NPC that is already in combat with any other NPC is not a crime - AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + AiSequence& seq = targetStats.getAiSequence(); bool isFightingNpc = false; for (std::list::const_iterator it = seq.begin(); it != seq.end(); ++it) { @@ -1314,13 +1336,13 @@ namespace MWMechanics } } - if (ptr.getClass().isNpc() && !attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) + if (ptr.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(ptr, attacker) && !isFightingNpc) commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) - || attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) - && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + || attacker == getPlayer()) + && !seq.isInCombat(attacker)) { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. @@ -1330,6 +1352,27 @@ namespace MWMechanics return true; } + void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) + { + if (attacker.isEmpty() || attacker != getPlayer()) + return; + + if (victim == attacker) + return; // known to happen + + if (!victim.getClass().isNpc()) + return; // TODO: implement animal rights + + const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); + + // Simple check for who attacked first: if the player attacked first, a crimeId should be set + // Doesn't handle possible edge case where no one reported the assault, but in such a case, + // for bystanders it is not possible to tell who attacked first, anyway. + if (victimStats.getCrimeId() != -1) + commitCrime(attacker, victim, MWBase::MechanicsManager::OT_Murder); + + } + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) { if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled()) @@ -1408,7 +1451,7 @@ namespace MWMechanics if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) return; ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); - if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (target == getPlayer()) { // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) @@ -1505,7 +1548,7 @@ namespace MWMechanics if (ptr.getClass().isNpc() && target.getClass().isNpc()) { if (target.getClass().getNpcStats(target).isWerewolf() || - (target == MWBase::Environment::get().getWorld()->getPlayerPtr() && + (target == getPlayer() && MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) { const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().search("iWerewolfFightMod"); @@ -1518,7 +1561,7 @@ namespace MWMechanics void MechanicsManager::keepPlayerAlive() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); CreatureStats& stats = player.getClass().getCreatureStats(player); if (stats.isDead()) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 5cc2ce254..f0d1cc2a0 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -120,6 +120,11 @@ namespace MWMechanics OffenseType type, int arg=0, bool victimAware=false); /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + + /// Notify that actor was killed, add a murder bounty if applicable + /// @note No-op for non-player attackers + virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, @@ -132,7 +137,9 @@ namespace MWMechanics virtual void forceStateUpdate(const MWWorld::Ptr &ptr); - virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + /// Attempt to play an animation group + /// @return Success or error + virtual bool 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); @@ -175,13 +182,15 @@ namespace MWMechanics /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); + + /// @return is \a ptr allowed to take/use \a cellref or is it a crime? + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim); private: void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); - /// @return is \a ptr allowed to take/use \a cellref or is it a crime? - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim); + }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index b9aa8b301..10d603ff1 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,4 +1,3 @@ - #include "npcstats.hpp" #include diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 4949d9b12..94b552a38 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -1,5 +1,7 @@ #include "objects.hpp" +#include + #include "movement.hpp" #include "../mwbase/environment.hpp" @@ -77,11 +79,18 @@ void Objects::update(float duration, bool paused) } } -void Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) +bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) - iter->second->playGroup(groupName, mode, number); + { + return iter->second->playGroup(groupName, mode, number); + } + else + { + std::cerr<< "Error in Objects::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId() << std::endl; + return false; + } } void Objects::skipAnimation(const MWWorld::Ptr& ptr) { diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 6e22c0582..07e00675c 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -38,7 +38,7 @@ namespace MWMechanics void update(float duration, bool paused); ///< Update object animations - void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); void skipAnimation(const MWWorld::Ptr& ptr); void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& out); diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 3d2ab7d3d..53f12a982 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -1,9 +1,13 @@ #include "obstacle.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "movement.hpp" + namespace MWMechanics { // NOTE: determined empirically but probably need further tweaking @@ -11,6 +15,14 @@ namespace MWMechanics static const float DURATION_SAME_SPOT = 1.0f; static const float DURATION_TO_EVADE = 0.4f; + const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] = + { + { 1.0f, 0.0f }, // move to side + { 1.0f, -1.0f }, // move to side and backwards + { -1.0f, 0.0f }, // move to other side + { -1.0f, -1.0f } // move to side and backwards + }; + // Proximity check function for interior doors. Given that most interior cells // do not have many doors performance shouldn't be too much of an issue. // @@ -65,6 +77,7 @@ namespace MWMechanics , mStuckDuration(0) , mEvadeDuration(0) , mDistSameSpot(-1) // avoid calculating it each time + , mEvadeDirectionIndex(0) { } @@ -153,6 +166,7 @@ namespace MWMechanics /* FALL THROUGH */ case State_Evade: { + chooseEvasionDirection(samePosition); mEvadeDuration += duration; if(mEvadeDuration < DURATION_TO_EVADE) return true; @@ -167,4 +181,24 @@ namespace MWMechanics } return false; // no obstacles to evade (yet) } + + void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) + { + actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0]; + actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1]; + } + + void ObstacleCheck::chooseEvasionDirection(bool samePosition) + { + // change direction if attempt didn't work + if (samePosition && (0 < mEvadeDuration)) + { + ++mEvadeDirectionIndex; + if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS) + { + mEvadeDirectionIndex = 0; + } + } + } + } diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index e0ae9203d..ecff00a5c 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -8,9 +8,13 @@ namespace MWWorld namespace MWMechanics { + struct Movement; + /// NOTE: determined empirically based on in-game behaviour static const float MIN_DIST_TO_DOOR_SQUARED = 128*128; + static const int NUM_EVADE_DIRECTIONS = 4; + /// tests actor's proximity to a closed door by default bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr = MIN_DIST_TO_DOOR_SQUARED, @@ -36,12 +40,18 @@ namespace MWMechanics // should be taken bool check(const MWWorld::Ptr& actor, float duration); + // change direction to try to fix "stuck" actor + void takeEvasiveAction(MWMechanics::Movement& actorMovement); + private: // for checking if we're stuck (ignoring Z axis) float mPrevX; float mPrevY; + // directions to try moving in when get stuck + static const float evadeDirections[NUM_EVADE_DIRECTIONS][2]; + enum WalkState { State_Norm, @@ -53,6 +63,9 @@ namespace MWMechanics float mStuckDuration; // accumulate time here while in same spot float mEvadeDuration; float mDistSameSpot; // take account of actor's speed + int mEvadeDirectionIndex; + + void chooseEvasionDirection(bool samePosition); }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 3c1a40fe0..dbe20fdc0 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,10 +1,12 @@ #include "pathfinding.hpp" +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "coordinateconverter.hpp" namespace { @@ -25,8 +27,7 @@ namespace // int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) { - if(!grid || grid->mPoints.empty()) - return -1; + assert(grid && !grid->mPoints.empty()); float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; @@ -51,33 +52,39 @@ namespace const MWWorld::CellStore *cell, const osg::Vec3f pos, int start) { - if(!grid || grid->mPoints.empty()) - return std::pair (-1, false); + assert(grid && !grid->mPoints.empty()); - float distanceBetween = distanceSquared(grid->mPoints[0], pos); + float closestDistanceBetween = std::numeric_limits::max(); + float closestDistanceReachable = std::numeric_limits::max(); int closestIndex = 0; int closestReachableIndex = 0; // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help - for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) + for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++) { float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); - if(potentialDistBetween < distanceBetween) + if (potentialDistBetween < closestDistanceReachable) { // found a closer one - distanceBetween = potentialDistBetween; - closestIndex = counter; if (cell->isPointConnected(start, counter)) { + closestDistanceReachable = potentialDistBetween; closestReachableIndex = counter; } + if (potentialDistBetween < closestDistanceBetween) + { + closestDistanceBetween = potentialDistBetween; + closestIndex = counter; + } } } + + // post-condition: start and endpoint must be connected + assert(cell->isPointConnected(start, closestReachableIndex)); + // AiWander has logic that depends on whether a path was created, deleting // allowed nodes if not. Hence a path needs to be created even if the start // and the end points are the same. - //if(start == closestReachableIndex) - //closestReachableIndex = -1; // couldn't find anyting other than start return std::pair (closestReachableIndex, closestReachableIndex == closestIndex); @@ -87,14 +94,14 @@ namespace namespace MWMechanics { - float sqrDistanceIgnoreZ(ESM::Pathgrid::Point point, float x, float y) + float sqrDistanceIgnoreZ(const ESM::Pathgrid::Point& point, float x, float y) { x -= point.mX; y -= point.mY; return (x * x + y * y); } - float distance(ESM::Pathgrid::Point point, float x, float y, float z) + float distance(const ESM::Pathgrid::Point& point, float x, float y, float z) { x -= point.mX; y -= point.mY; @@ -102,7 +109,7 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } - float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) { float x = static_cast(a.mX - b.mX); float y = static_cast(a.mY - b.mY); @@ -139,8 +146,6 @@ namespace MWMechanics * pathgrid point (e.g. wander) then it may be worth while to call * pop_back() to remove the redundant entry. * - * mPathConstructed is set true if successful, false if not - * * NOTE: co-ordinates must be converted prior to calling getClosestPoint() * * | @@ -195,62 +200,68 @@ namespace MWMechanics } // NOTE: getClosestPoint expects local co-ordinates - float xCell = 0; - float yCell = 0; - if (mCell->isExterior()) - { - xCell = static_cast(mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE); - yCell = static_cast(mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE); - } + CoordinateConverter converter(mCell->getCell()); // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall - int startNode = getClosestPoint(mPathgrid, - osg::Vec3f(startPoint.mX - xCell, startPoint.mY - yCell, static_cast(startPoint.mZ))); - // Some cells don't have any pathgrids at all - if(startNode != -1) + osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint)); + int startNode = getClosestPoint(mPathgrid, startPointInLocalCoords); + + osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); + std::pair endNode = getClosestReachablePoint(mPathgrid, cell, + endPointInLocalCoords, + startNode); + + // if it's shorter for actor to travel from start to end, than to travel from either + // start or end to nearest pathgrid point, just travel from start to end. + float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); + float endTolastNodeLength2 = distanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); + float startTo1stNodeLength2 = distanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); + if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { - std::pair endNode = getClosestReachablePoint(mPathgrid, cell, - osg::Vec3f(endPoint.mX - xCell, endPoint.mY - yCell, static_cast(endPoint.mZ)), - startNode); + mPath.push_back(endPoint); + return; + } - // this shouldn't really happen, but just in case - if(endNode.first != -1) + // AiWander has logic that depends on whether a path was created, + // deleting allowed nodes if not. Hence a path needs to be created + // even if the start and the end points are the same. + // NOTE: aStarSearch will return an empty path if the start and end + // nodes are the same + if(startNode == endNode.first) + { + ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]); + converter.toWorld(temp); + mPath.push_back(temp); + } + else + { + mPath = mCell->aStarSearch(startNode, endNode.first); + + // convert supplied path to world co-ordinates + for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) { - // AiWander has logic that depends on whether a path was created, - // deleting allowed nodes if not. Hence a path needs to be created - // even if the start and the end points are the same. - // NOTE: aStarSearch will return an empty path if the start and end - // nodes are the same - if(startNode == endNode.first) - { - mPath.push_back(endPoint); - return; - } - - mPath = mCell->aStarSearch(startNode, endNode.first); - - if(!mPath.empty()) - { - // Add the destination (which may be different to the closest - // pathgrid point). However only add if endNode was the closest - // point to endPoint. - // - // This logic can fail in the opposite situate, e.g. endPoint may - // have been reachable but happened to be very close to an - // unreachable pathgrid point. - // - // The AI routines will have to deal with such situations. - if(endNode.second) - mPath.push_back(endPoint); - } + converter.toWorld(*iter); } } - return; + // If endNode found is NOT the closest PathGrid point to the endPoint, + // assume endPoint is not reachable from endNode. In which case, + // path ends at endNode. + // + // So only add the destination (which may be different to the closest + // pathgrid point) when endNode was the closest point to endPoint. + // + // This logic can fail in the opposite situate, e.g. endPoint may + // have been reachable but happened to be very close to an + // unreachable pathgrid point. + // + // The AI routines will have to deal with such situations. + if(endNode.second) + mPath.push_back(endPoint); } float PathFinder::getZAngleToNext(float x, float y) const @@ -264,7 +275,7 @@ namespace MWMechanics float directionX = nextPoint.mX - x; float directionY = nextPoint.mY - y; - return osg::RadiansToDegrees(std::atan2(directionX, directionY)); + return std::atan2(directionX, directionY); } bool PathFinder::checkPathCompleted(float x, float y, float tolerance) @@ -272,7 +283,7 @@ namespace MWMechanics if(mPath.empty()) return true; - ESM::Pathgrid::Point nextPoint = *mPath.begin(); + const ESM::Pathgrid::Point& nextPoint = *mPath.begin(); if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance) { mPath.pop_front(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 45d9dd973..1765a5cc5 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -12,13 +12,15 @@ namespace MWWorld namespace MWMechanics { - float distance(ESM::Pathgrid::Point point, float x, float y, float); - float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); + float distance(const ESM::Pathgrid::Point& point, float x, float y, float); + float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); class PathFinder { public: PathFinder(); + static const int PathTolerance = 32; + static float sgn(float val) { if(val > 0) @@ -35,10 +37,10 @@ namespace MWMechanics void clearPath(); - bool checkPathCompleted(float x, float y, float tolerance=32.f); + bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. - /// In degrees + /// In radians float getZAngleToNext(float x, float y) const; bool isPathConstructed() const diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 4e9bc8904..871baecdc 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -312,29 +312,15 @@ namespace MWMechanics if(current != goal) return path; // for some reason couldn't build a path - // reconstruct path to return, using world co-ordinates - float xCell = 0; - float yCell = 0; - if (mIsExterior) - { - xCell = static_cast(mPathgrid->mData.mX * ESM::Land::REAL_SIZE); - yCell = static_cast(mPathgrid->mData.mY * ESM::Land::REAL_SIZE); - } - + // reconstruct path to return, using local co-ordinates while(graphParent[current] != -1) { - ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; - pt.mX += static_cast(xCell); - pt.mY += static_cast(yCell); - path.push_front(pt); + path.push_front(mPathgrid->mPoints[current]); current = graphParent[current]; } // add first node to path explicitly - ESM::Pathgrid::Point pt = mPathgrid->mPoints[start]; - pt.mX += static_cast(xCell); - pt.mY += static_cast(yCell); - path.push_front(pt); + path.push_front(mPathgrid->mPoints[start]); return path; } } diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index fa429bbee..581ba3a84 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -14,15 +14,16 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "actorutil.hpp" namespace MWMechanics { void Repair::repair(const MWWorld::Ptr &itemToRepair) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); MWWorld::LiveCellRef *ref = mTool.get(); @@ -82,7 +83,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) // tool used up? if (mTool.getCellRef().getCharge() == 0) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(mTool, 1, player); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 9eab5bfef..97878db87 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -31,7 +31,7 @@ namespace MWMechanics void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, std::string& resultMessage, std::string& resultSound) { - if (!(lock.getCellRef().getLockLevel() > 0)) //If it's unlocked back out immediately + if (!(lock.getCellRef().getLockLevel() > 0) || !lock.getClass().canLock(lock)) //If it's unlocked back out immediately return; int lockStrength = lock.getCellRef().getLockLevel(); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a47da7bf4..6d6b9d950 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1,6 +1,7 @@ #include "spellcasting.hpp" #include +#include #include @@ -19,11 +20,14 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" + #include "../mwrender/animation.hpp" #include "magiceffects.hpp" #include "npcstats.hpp" #include "summoning.hpp" +#include "actorutil.hpp" namespace { @@ -65,57 +69,6 @@ namespace target.getClass().getCreatureStats(target).setDynamic(attribute, value); } - // TODO: refactor the effect tick functions in Actors so they can be reused here - void applyInstantEffectTick(MWMechanics::EffectKey effect, const MWWorld::Ptr& target, float magnitude) - { - int effectId = effect.mId; - if (effectId == ESM::MagicEffect::DamageHealth) - { - applyDynamicStatsEffect(0, target, magnitude * -1); - } - else if (effectId == ESM::MagicEffect::RestoreHealth) - { - applyDynamicStatsEffect(0, target, magnitude); - } - else if (effectId == ESM::MagicEffect::DamageFatigue) - { - applyDynamicStatsEffect(2, target, magnitude * -1); - } - else if (effectId == ESM::MagicEffect::RestoreFatigue) - { - applyDynamicStatsEffect(2, target, magnitude); - } - else if (effectId == ESM::MagicEffect::DamageMagicka) - { - applyDynamicStatsEffect(1, target, magnitude * -1); - } - else if (effectId == ESM::MagicEffect::RestoreMagicka) - { - applyDynamicStatsEffect(1, target, magnitude); - } - else if (effectId == ESM::MagicEffect::DamageAttribute || effectId == ESM::MagicEffect::RestoreAttribute) - { - int attribute = effect.mArg; - MWMechanics::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; - MWMechanics::SkillValue& value = target.getClass().getNpcStats(target).getSkill(skill); - if (effectId == ESM::MagicEffect::DamageSkill) - value.damage(magnitude); - else - value.restore(magnitude); - } - } - } namespace MWMechanics @@ -141,7 +94,7 @@ namespace MWMechanics if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()) return 0; - float y = FLT_MAX; + float y = std::numeric_limits::max(); float lowestSkill = 0; for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) @@ -182,7 +135,7 @@ namespace MWMechanics int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2f * actorWillpower + 0.1f * actorLuck) * stats.getFatigueTerm(); - if (MWBase::Environment::get().getWorld()->getGodModeState() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (MWBase::Environment::get().getWorld()->getGodModeState() && actor == getPlayer()) castChance = 100; if (!cap) @@ -381,14 +334,15 @@ namespace MWMechanics 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).getMagnitude() - : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).getMagnitude(); + int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? + ESM::MagicEffect::ResistCommonDisease + : ESM::MagicEffect::ResistBlightDisease; + float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude(); if (Misc::Rng::roll0to99() <= x) { // Fully resisted, show message - if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); return; } @@ -406,7 +360,7 @@ namespace MWMechanics if (target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); - bool castByPlayer = (!caster.isEmpty() && caster == MWBase::Environment::get().getWorld()->getPlayerPtr()); + bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); // 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 @@ -481,7 +435,7 @@ namespace MWMechanics if (magnitudeMult == 0) { // Fully resisted, show message - if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); @@ -530,9 +484,16 @@ namespace MWMechanics else { if (hasDuration && target.getClass().isActor()) - applyInstantEffectTick(EffectKey(*effectIt), target, magnitude); - else - applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); + { + bool wasDead = target.getClass().getCreatureStats(target).isDead(); + effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude); + bool isDead = target.getClass().getCreatureStats(target).isDead(); + + if (!wasDead && isDead) + MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); + } + else if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) + continue; } // Re-casting a summon effect will remove the creature from previous castings of that effect. @@ -601,19 +562,20 @@ namespace MWMechanics target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); } - void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) + bool 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 (target.getClass().canLock(target)) { if (effectId == ESM::MagicEffect::Lock) { if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude { - if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); - target.getCellRef().setLockLevel(static_cast(magnitude)); + target.getClass().lock(target, static_cast(magnitude)); } + return true; } else if (effectId == ESM::MagicEffect::Open) { @@ -625,50 +587,62 @@ namespace MWMechanics if (!caster.isEmpty() && caster.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); - if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); } - target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); + target.getClass().unlock(target); } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + return true; } } - else + else if (target.getClass().isActor()) { - if (effectId == ESM::MagicEffect::CurePoison) + switch (effectId) + { + case ESM::MagicEffect::CurePoison: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); - else if (effectId == ESM::MagicEffect::CureParalyzation) + return true; + case ESM::MagicEffect::CureParalyzation: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); - else if (effectId == ESM::MagicEffect::CureCommonDisease) + return true; + case ESM::MagicEffect::CureCommonDisease: target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); - else if (effectId == ESM::MagicEffect::CureBlightDisease) + return true; + case ESM::MagicEffect::CureBlightDisease: target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); - else if (effectId == ESM::MagicEffect::CureCorprusDisease) + return true; + case ESM::MagicEffect::CureCorprusDisease: target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); - else if (effectId == ESM::MagicEffect::Dispel) + return true; + case ESM::MagicEffect::Dispel: target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude); - else if (effectId == ESM::MagicEffect::RemoveCurse) + return true; + case ESM::MagicEffect::RemoveCurse: target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + return true; + } - if (target != MWBase::Environment::get().getWorld()->getPlayerPtr()) - return; - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) - return; + if (target != getPlayer()) + return false; if (effectId == ESM::MagicEffect::DivineIntervention) { MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker"); + return true; } else if (effectId == ESM::MagicEffect::AlmsiviIntervention) { MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker"); + return true; } else if (effectId == ESM::MagicEffect::Mark) { MWBase::Environment::get().getWorld()->getPlayer().markPosition( target.getCell(), target.getRefData().getPosition()); + return true; } else if (effectId == ESM::MagicEffect::Recall) { @@ -682,8 +656,10 @@ namespace MWMechanics markedPosition, false); action.execute(target); } + return true; } } + return false; } @@ -727,7 +703,7 @@ namespace MWMechanics if (item.getCellRef().getEnchantmentCharge() < castCost) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); // Failure sound @@ -751,14 +727,14 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) 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 == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) { mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); } @@ -825,7 +801,7 @@ namespace MWMechanics float successChance = getSpellSuccessChance(spell, mCaster); if (Misc::Rng::roll0to99() >= successChance) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); fail = true; } @@ -843,7 +819,7 @@ namespace MWMechanics } } - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr() && spellIncreasesSkill(spell)) + if (mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); @@ -960,4 +936,171 @@ namespace MWMechanics || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); } + + 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() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + int charge = item->getClass().getItemHealth(*item); + + if (charge == 0) + return false; + + // FIXME: charge should be a float, not int so that damage < 1 per frame can be applied. + // This was also a bug in the original engine. + charge -= + std::min(static_cast(disintegrate), + charge); + item->getCellRef().setCharge(charge); + + if (charge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr != getPlayer()) + inv.autoEquip(ptr); + else + inv.unequipItem(*item, ptr); + } + + return true; + } + } + return false; + } + + void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude) + { + DynamicStat stat = creatureStats.getDynamic(index); + stat.setCurrent(stat.getCurrent() + magnitude, index == 2); + creatureStats.setDynamic(index, stat); + } + + void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) + { + if (magnitude == 0.f) + return; + + bool receivedMagicDamage = false; + + switch (effectKey.mId) + { + case ESM::MagicEffect::DamageAttribute: + { + AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); + attr.damage(magnitude); + creatureStats.setAttribute(effectKey.mArg, attr); + break; + } + case ESM::MagicEffect::RestoreAttribute: + { + AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); + attr.restore(magnitude); + creatureStats.setAttribute(effectKey.mArg, attr); + break; + } + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); + break; + case ESM::MagicEffect::DamageHealth: + case ESM::MagicEffect::DamageMagicka: + case ESM::MagicEffect::DamageFatigue: + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); + break; + case ESM::MagicEffect::AbsorbHealth: + case ESM::MagicEffect::AbsorbMagicka: + case ESM::MagicEffect::AbsorbFatigue: + if (magnitude > 0.f) + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); + break; + + case ESM::MagicEffect::DisintegrateArmor: + { + // 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; iisExterior()) + break; + 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; + + adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); + if (magnitude * damageScale > 0.f) + receivedMagicDamage = true; + break; + } + + case ESM::MagicEffect::FireDamage: + case ESM::MagicEffect::ShockDamage: + case ESM::MagicEffect::FrostDamage: + case ESM::MagicEffect::Poison: + { + adjustDynamicStat(creatureStats, 0, -magnitude); + receivedMagicDamage = true; + break; + } + + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + { + if (!actor.getClass().isNpc()) + break; + NpcStats &npcStats = actor.getClass().getNpcStats(actor); + SkillValue& skill = npcStats.getSkill(effectKey.mArg); + if (effectKey.mId == ESM::MagicEffect::RestoreSkill) + skill.restore(magnitude); + else + skill.damage(magnitude); + break; + } + + } + + if (receivedMagicDamage && actor == getPlayer()) + MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + } + } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 2540b87db..5b48bd4a8 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -17,6 +17,7 @@ namespace MWMechanics { struct EffectKey; class MagicEffects; + class CreatureStats; ESM::Skill::SkillEnum spellSchoolToSkill(int school); @@ -60,6 +61,8 @@ namespace MWMechanics int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); + void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude); + class CastSpell { private: @@ -94,7 +97,8 @@ namespace MWMechanics const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); /// @note \a caster can be any type of object, or even an empty object. - void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); + /// @return was the target suitable for the effect? + bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index fe0f892db..6d7673a59 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,4 +1,3 @@ - #include "spells.hpp" #include diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index 219a23655..1ef46e1ae 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -14,14 +14,16 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f { float currentAngle (actor.getRefData().getPosition().rot[axis]); float diff (targetAngleRadians - currentAngle); - if (diff >= osg::DegreesToRadians(180.f)) + if (std::abs(diff) >= osg::DegreesToRadians(180.f)) { - // Turning the other way would be a better idea - diff = diff-osg::DegreesToRadians(360.f); - } - else if (diff <= osg::DegreesToRadians(-180.f)) - { - diff = osg::DegreesToRadians(360.f)-diff; + if (diff >= 0) + { + diff = diff - osg::DegreesToRadians(360.f); + } + else + { + diff = osg::DegreesToRadians(360.f) + diff; + } } float absDiff = std::abs(diff); diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 4b29dc1d9..632ab3611 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -1,4 +1,5 @@ #ifndef OPENMW_MECHANICS_STEERING_H +#define OPENMW_MECHANICS_STEERING_H #include diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2cb261851..e666161da 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -30,6 +30,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -460,7 +461,7 @@ namespace MWPhysics class HeightField { public: - HeightField(float* heights, int x, int y, float triSize, float sqrtVerts) + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts) { // find the minimum and maximum heights (needed for bullet) float minh = heights[0]; @@ -746,7 +747,7 @@ namespace MWPhysics { } - virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) { if (rayResult.m_collisionObject == mMe) return 1.f; @@ -927,7 +928,7 @@ namespace MWPhysics return MovementSolver::traceDown(ptr, found->second, mCollisionWorld, maxHeight); } - void PhysicsSystem::addHeightField (float* heights, int x, int y, float triSize, float sqrtVerts) + void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts) { HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts); mHeightFields[std::make_pair(x,y)] = heightfield; @@ -1091,7 +1092,7 @@ namespace MWPhysics bool PhysicsSystem::toggleCollisionMode() { - ActorMap::iterator found = mActors.find(MWBase::Environment::get().getWorld()->getPlayerPtr()); + ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c3b22c385..db8da2886 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -71,7 +71,7 @@ namespace MWPhysics void updatePosition (const MWWorld::Ptr& ptr); - void addHeightField (float* heights, int x, int y, float triSize, float sqrtVerts); + void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts); void removeHeightField (int x, int y); @@ -107,6 +107,7 @@ namespace MWPhysics bool isOnGround (const MWWorld::Ptr& actor); + /// Get physical half extents (scaled) of the given actor. osg::Vec3f getHalfExtents(const MWWorld::Ptr& actor); /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 91f459ff2..37ec4c213 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include @@ -208,6 +210,38 @@ namespace std::vector mToRemove; }; + class RemoveTriBipVisitor : public osg::NodeVisitor + { + public: + RemoveTriBipVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + virtual void apply(osg::Geode &node) + { + const std::string toFind = "tri bip"; + if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) + { + // Not safe to remove in apply(), since the visitor is still iterating the child list + mToRemove.push_back(&node); + } + } + + void remove() + { + for (std::vector::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + { + osg::Node* node = *it; + if (node->getNumParents()) + node->getParent(0)->removeChild(node); + } + } + + private: + std::vector mToRemove; + }; + } namespace MWRender @@ -219,7 +253,7 @@ namespace MWRender typedef std::map > ControllerMap; - ControllerMap mControllerMap[Animation::sNumGroups]; + ControllerMap mControllerMap[Animation::sNumBlendMasks]; const std::multimap& getTextKeys(); }; @@ -260,8 +294,9 @@ namespace MWRender , mTextKeyListener(NULL) , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) + , mAlpha(1.f) { - for(size_t i = 0;i < sNumGroups;i++) + for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i].reset(new AnimationTime); } @@ -299,9 +334,9 @@ namespace MWRender mResetAccumRootCallback->setAccumulate(mAccumulate); } - size_t Animation::detectAnimGroup(osg::Node* node) + size_t Animation::detectBlendMask(osg::Node* node) { - static const char sGroupRoots[sNumGroups][32] = { + static const char sBlendMaskRoots[sNumBlendMasks][32] = { "", /* Lower body / character root */ "Bip01 Spine1", /* Torso */ "Bip01 L Clavicle", /* Left arm */ @@ -311,9 +346,9 @@ namespace MWRender while(node != mObjectRoot) { const std::string &name = node->getName(); - for(size_t i = 1;i < sNumGroups;i++) + for(size_t i = 1;i < sNumBlendMasks;i++) { - if(name == sGroupRoots[i]) + if(name == sBlendMaskRoots[i]) return i; } @@ -361,13 +396,13 @@ namespace MWRender osg::Node* node = found->second; - size_t group = detectAnimGroup(node); + size_t blendMask = detectBlendMask(node); // clone the controller, because each Animation needs its own ControllerSource osg::ref_ptr cloned = osg::clone(it->second.get(), osg::CopyOp::DEEP_COPY_ALL); - cloned->setSource(mAnimationTimePtr[group]); + cloned->setSource(mAnimationTimePtr[blendMask]); - animsrc->mControllerMap[group].insert(std::make_pair(bonename, cloned)); + animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); } mAnimSources.push_back(animsrc); @@ -390,7 +425,7 @@ namespace MWRender { mStates.clear(); - for(size_t i = 0;i < sNumGroups;i++) + for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i]->setTimePtr(boost::shared_ptr()); mAccumCtrl = NULL; @@ -461,7 +496,7 @@ namespace MWRender mTextKeyListener->handleTextKey(groupname, key, map); } - void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, + void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback) { if(!mObjectRoot || mAnimSources.empty()) @@ -473,8 +508,6 @@ namespace MWRender return; } - priority = std::max(0, priority); - AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { @@ -505,7 +538,7 @@ namespace MWRender state.mLoopCount = loops; state.mPlaying = (state.getTime() < state.mStopTime); state.mPriority = priority; - state.mGroups = groups; + state.mBlendMask = blendMask; state.mAutoDisable = autodisable; mStates[groupname] = state; @@ -600,23 +633,20 @@ namespace MWRender state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint)); // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation - // (see handleTextKey). But if startpoint is already past these keys, we need to assign them now. - if(state.getTime() > state.mStartTime) + // (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now. + const std::string loopstarttag = groupname+": loop start"; + const std::string loopstoptag = groupname+": loop stop"; + + NifOsg::TextKeyMap::const_reverse_iterator key(groupend); + for (; key != startkey && key != keys.rend(); ++key) { - const std::string loopstarttag = groupname+": loop start"; - const std::string loopstoptag = groupname+": loop stop"; + if (key->first > state.getTime()) + continue; - NifOsg::TextKeyMap::const_reverse_iterator key(groupend); - for (; key != startkey && key != keys.rend(); ++key) - { - if (key->first > state.getTime()) - continue; - - if (key->second == loopstarttag) - state.mLoopStartTime = key->first; - else if (key->second == loopstoptag) - state.mLoopStopTime = key->first; - } + if (key->second == loopstarttag) + state.mLoopStartTime = key->first; + else if (key->second == loopstoptag) + state.mLoopStopTime = key->first; } return true; @@ -643,35 +673,35 @@ namespace MWRender mAccumCtrl = NULL; - for(size_t grp = 0;grp < sNumGroups;grp++) + for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++) { AnimStateMap::const_iterator active = mStates.end(); AnimStateMap::const_iterator state = mStates.begin(); for(;state != mStates.end();++state) { - if(!(state->second.mGroups&(1<second.mBlendMask&(1<second.mPriority < state->second.mPriority) + if(active == mStates.end() || active->second.mPriority[(BoneGroup)blendMask] < state->second.mPriority[(BoneGroup)blendMask]) active = state; } - mAnimationTimePtr[grp]->setTimePtr(active == mStates.end() ? boost::shared_ptr() : active->second.mTime); + mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? boost::shared_ptr() : active->second.mTime); - // add external controllers for the AnimSource active in this group + // add external controllers for the AnimSource active in this blend mask if (active != mStates.end()) { boost::shared_ptr animsrc = active->second.mSource; - for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[grp].begin(); it != animsrc->mControllerMap[grp].end(); ++it) + for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) { osg::ref_ptr node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource node->addUpdateCallback(it->second); mActiveControllers.insert(std::make_pair(node, it->second)); - if (grp == 0 && node == mAccumRoot) + if (blendMask == 0 && node == mAccumRoot) { mAccumCtrl = it->second; @@ -690,20 +720,6 @@ namespace MWRender addControllers(); } - void Animation::changeGroups(const std::string &groupname, int groups) - { - AnimStateMap::iterator stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) - { - if(stateiter->second.mGroups != groups) - { - stateiter->second.mGroups = groups; - resetActiveGroups(); - } - return; - } - } - void Animation::stopLooping(const std::string& groupname) { AnimStateMap::iterator stateiter = mStates.find(groupname); @@ -917,10 +933,12 @@ namespace MWRender return movement; } - void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly) + void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) { + osg::ref_ptr previousStateset; if (mObjectRoot) { + previousStateset = mObjectRoot->getStateSet(); mObjectRoot->getParent(0)->removeChild(mObjectRoot); } mObjectRoot = NULL; @@ -945,6 +963,9 @@ namespace MWRender mObjectRoot = newObjectRoot; } + if (previousStateset) + mObjectRoot->setStateSet(previousStateset); + if (baseonly) { RemoveDrawableVisitor removeDrawableVisitor; @@ -952,6 +973,13 @@ namespace MWRender removeDrawableVisitor.remove(); } + if (isCreature) + { + RemoveTriBipVisitor removeTriBipVisitor; + mObjectRoot->accept(removeTriBipVisitor); + removeTriBipVisitor.remove(); + } + NodeMapVisitor visitor; mObjectRoot->accept(visitor); mNodeMap = visitor.getNodeMap(); @@ -1208,9 +1236,10 @@ namespace MWRender { for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) { - if((stateiter->second.mPriority > MWMechanics::Priority_Movement - && stateiter->second.mPriority < MWMechanics::Priority_Torch) - || stateiter->second.mPriority == MWMechanics::Priority_Death) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death))) return false; } return true; @@ -1226,6 +1255,37 @@ namespace MWRender return found->second; } + void Animation::setAlpha(float alpha) + { + if (alpha == mAlpha) + return; + mAlpha = alpha; + + if (alpha != 1.f) + { + osg::StateSet* stateset (new osg::StateSet); + + osg::BlendFunc* blendfunc (new osg::BlendFunc); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // FIXME: overriding diffuse/ambient/emissive colors + osg::Material* material (new osg::Material); + material->setColorMode(osg::Material::OFF); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + stateset->setNestRenderBins(false); + mObjectRoot->setStateSet(stateset); + } + else + { + mObjectRoot->setStateSet(NULL); + } + } + void Animation::setLightEffect(float effect) { if (effect == 0) @@ -1326,7 +1386,7 @@ namespace MWRender { if (!model.empty()) { - setObjectRoot(model, false, false); + setObjectRoot(model, false, false, false); if (animated) addAnimSource(model); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index d23a62954..94b792c0d 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -67,16 +67,63 @@ typedef boost::shared_ptr PartHolderPtr; class Animation { public: - enum Group { - Group_LowerBody = 1<<0, + enum BoneGroup { + BoneGroup_LowerBody = 0, + BoneGroup_Torso, + BoneGroup_LeftArm, + BoneGroup_RightArm + }; - Group_Torso = 1<<1, - Group_LeftArm = 1<<2, - Group_RightArm = 1<<3, + enum BlendMask { + BlendMask_LowerBody = 1<<0, + BlendMask_Torso = 1<<1, + BlendMask_LeftArm = 1<<2, + BlendMask_RightArm = 1<<3, - Group_UpperBody = Group_Torso | Group_LeftArm | Group_RightArm, + BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, - Group_All = Group_LowerBody | Group_UpperBody + BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody + }; + /* This is the number of *discrete* blend masks. */ + static const size_t sNumBlendMasks = 4; + + /// Holds an animation priority value for each BoneGroup. + struct AnimPriority + { + /// Convenience constructor, initialises all priorities to the same value. + AnimPriority(int priority) + { + for (unsigned int i=0; i, osg::ref_ptr > ControllerMap; ControllerMap mActiveControllers; - boost::shared_ptr mAnimationTimePtr[sNumGroups]; + boost::shared_ptr mAnimationTimePtr[sNumBlendMasks]; // Stored in all lowercase for a case-insensitive lookup typedef std::map > NodeMap; @@ -209,11 +253,13 @@ protected: osg::ref_ptr mGlowLight; + float mAlpha; + /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); - size_t detectAnimGroup(osg::Node* node); + size_t detectBlendMask(osg::Node* node); /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ @@ -239,7 +285,7 @@ protected: * @param baseonly If true, then any meshes or particle systems in the model are ignored * (useful for NPCs, where only the skeleton is needed for the root, and the actual NPC parts are then assembled from separate files). */ - void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly); + void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); /* Adds the keyframe controllers in the specified model as a new animation source. Note that * the filename portion of the provided model name will be prepended with 'x', and the .nif @@ -304,7 +350,7 @@ public: * \param priority Priority of the animation. The animation will play on * bone groups that don't have another animation set of a * higher priority. - * \param groups Bone groups to play the animation on. + * \param blendMask Bone groups to play the animation on. * \param autodisable Automatically disable the animation when it stops * playing. * \param speedmult Speed multiplier for the animation. @@ -319,7 +365,7 @@ public: * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use * the "start" and "stop" keys for looping? */ - void play(const std::string &groupname, int priority, int groups, bool autodisable, + void play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback=false); @@ -358,7 +404,6 @@ public: * \param groupname Animation group to disable. */ void disable(const std::string &groupname); - void changeGroups(const std::string &groupname, int group); /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(const std::string &groupname) const; @@ -376,7 +421,8 @@ public: virtual void showCarriedLeft(bool show) {} virtual void setWeaponGroup(const std::string& group) {} virtual void setVampire(bool vampire) {} - virtual void setAlpha(float alpha) {} + /// A value < 1 makes the animation translucent, 1.f = fully opaque + void setAlpha(float alpha); virtual void setPitchFactor(float factor) {} virtual void attachArrow() {} virtual void releaseArrow(float attackStrength) {} diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 316c9308b..fb6573d65 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -14,10 +14,10 @@ namespace { -class UpdateCameraCallback : public osg::NodeCallback +class UpdateRenderCameraCallback : public osg::NodeCallback { public: - UpdateCameraCallback(MWRender::Camera* cam) + UpdateRenderCameraCallback(MWRender::Camera* cam) : mCamera(cam) { } @@ -67,7 +67,7 @@ namespace MWRender mMainCam.yaw = 0.f; mMainCam.offset = 400.f; - mUpdateCallback = new UpdateCameraCallback(this); + mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 32e51c4d6..93afeda25 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -17,6 +17,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "npcanimation.hpp" #include "vismask.hpp" @@ -241,13 +243,13 @@ namespace MWRender mAnimation->showCarriedLeft(showCarriedLeft); mCurrentAnimGroup = groupname; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_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() && showCarriedLeft) { if(!mAnimation->getInfo("torch")) - mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false, + mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else if(mAnimation->getInfo("torch")) @@ -295,7 +297,7 @@ namespace MWRender // -------------------------------------------------------------------------------------------------- RaceSelectionPreview::RaceSelectionPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem) - : CharacterPreview(viewer, resourceSystem, MWBase::Environment::get().getWorld()->getPlayerPtr(), + : CharacterPreview(viewer, resourceSystem, MWMechanics::getPlayer(), 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0,0,8)) , mBase (*mCharacter.get()->mBase) , mRef(&mBase) @@ -357,7 +359,7 @@ namespace MWRender void RaceSelectionPreview::onSetup () { - mAnimation->play("idle", 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->runAnimation(0.f); // attach camera to follow the head node diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 35294b1b5..f46736a39 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -24,7 +24,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, if(!model.empty()) { - setObjectRoot(model, false, false); + setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\xbase_anim.nif"); @@ -42,7 +42,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if(!model.empty()) { - setObjectRoot(model, true, false); + setObjectRoot(model, true, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\xbase_anim.nif"); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 890c8444a..3445e4189 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -59,10 +59,10 @@ namespace } - class CameraUpdateCallback : public osg::NodeCallback + class CameraUpdateGlobalCallback : public osg::NodeCallback { public: - CameraUpdateCallback(osg::Camera* cam, MWRender::GlobalMap* parent) + CameraUpdateGlobalCallback(osg::Camera* cam, MWRender::GlobalMap* parent) : mRendered(false) , mCamera(cam) , mParent(parent) @@ -156,6 +156,9 @@ namespace MWRender land->loadData(mask); } + const ESM::Land::LandData *landData = + land ? land->getLandData (ESM::Land::DATA_WNAM) : 0; + for (int cellY=0; cellY(float(cellX)/float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); - int texelX = (x-mMinX) * mCellSize + cellX; int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY); unsigned char r,g,b; float y = 0; - if (land && land->mDataTypes & ESM::Land::DATA_WNAM) - y = (land->mLandData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; + if (landData) + y = (landData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; else y = (SCHAR_MIN << 4) / 2048.f; if (y < 0) @@ -261,7 +263,7 @@ namespace MWRender else camera->setClearMask(GL_NONE); - camera->setUpdateCallback(new CameraUpdateCallback(camera, this)); + camera->setUpdateCallback(new CameraUpdateGlobalCallback(camera, this)); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->attach(osg::Camera::COLOR_BUFFER, mOverlayTexture); @@ -361,7 +363,7 @@ namespace MWRender osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); if (!result.success()) { - std::cerr << "Can't write map overlay: " << result.message() << std::endl; + std::cerr << "Can't write map overlay: " << result.message() << " code " << result.status() << std::endl; return; } @@ -411,7 +413,7 @@ namespace MWRender osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(istream); if (!result.success()) { - std::cerr << "Can't read map overlay: " << result.message() << std::endl; + std::cerr << "Can't read map overlay: " << result.message() << " code " << result.status() << std::endl; return; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 6ce54a4ba..fe685f97c 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -26,10 +27,10 @@ namespace { - class CameraUpdateCallback : public osg::NodeCallback + class CameraLocalUpdateCallback : public osg::NodeCallback { public: - CameraUpdateCallback(osg::Camera* cam, MWRender::LocalMap* parent) + CameraLocalUpdateCallback(osg::Camera* cam, MWRender::LocalMap* parent) : mRendered(false) , mCamera(cam) , mParent(parent) @@ -204,7 +205,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setStateSet(stateset); camera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); camera->setViewport(0, 0, mMapResolution, mMapResolution); - camera->setUpdateCallback(new CameraUpdateCallback(camera, this)); + camera->setUpdateCallback(new CameraLocalUpdateCallback(camera, this)); return camera; } @@ -644,7 +645,7 @@ void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(in); if (!result.success()) { - std::cerr << "Failed to read fog: " << result.message() << std::endl; + std::cerr << "Failed to read fog: " << result.message() << " code " << result.status() << std::endl; return; } @@ -676,7 +677,7 @@ void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mFogOfWarImage, ostream); if (!result.success()) { - std::cerr << "Unable to write fog: " << result.message() << std::endl; + std::cerr << "Unable to write fog: " << result.message() << " code " << result.status() << std::endl; return; } mFogOfWarImage->flipVertical(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6252d392b..99ee886e1 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include @@ -21,6 +19,7 @@ #include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -277,7 +276,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par mShowWeapons(false), mShowCarriedLeft(true), mNpcType(Type_Normal), - mAlpha(1.f), mSoundsDisabled(disableSounds) { mNpc = mPtr.get()->mBase; @@ -378,7 +376,7 @@ void NpcAnimation::updateNpcBase() : "meshes\\wolf\\skin.1st.nif"); smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); - setObjectRoot(smodel, true, true); + setObjectRoot(smodel, true, true, false); if(mViewMode != VM_FirstPerson) { @@ -421,7 +419,6 @@ void NpcAnimation::updateParts() if (!mObjectRoot.get()) return; - mAlpha = 1.f; const MWWorld::Class &cls = mPtr.getClass(); NpcType curType = Type_Normal; @@ -459,7 +456,7 @@ void NpcAnimation::updateParts() }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); - bool wasArrowAttached = 0;//(mAmmunition.get() != NULL); + bool wasArrowAttached = (mAmmunition.get() != NULL); MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) @@ -622,7 +619,7 @@ void NpcAnimation::updateParts() continue; } - if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + if ((!mNpc->isMale()) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) { // Allow opposite gender's parts as fallback if parts for our gender are missing BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); @@ -901,7 +898,6 @@ void NpcAnimation::showWeapons(bool showWeapon) { removeIndividualPart(ESM::PRT_Weapon); } - mAlpha = 1.f; } void NpcAnimation::showCarriedLeft(bool show) @@ -987,37 +983,6 @@ void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, boo } } -void NpcAnimation::setAlpha(float alpha) -{ - if (alpha == mAlpha) - return; - mAlpha = alpha; - - if (alpha != 1.f) - { - osg::StateSet* stateset (new osg::StateSet); - - osg::BlendFunc* blendfunc (new osg::BlendFunc); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // FIXME: overriding diffuse/ambient/emissive colors - osg::Material* material (new osg::Material); - material->setColorMode(osg::Material::OFF); - material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); - material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); - stateset->setNestRenderBins(false); - mObjectRoot->setStateSet(stateset); - } - else - { - mObjectRoot->setStateSet(NULL); - } -} - void NpcAnimation::enableHeadAnimation(bool enable) { mHeadAnimationTime->setEnabled(enable); @@ -1039,7 +1004,7 @@ void NpcAnimation::setVampire(bool vampire) return; if ((mNpcType == Type_Vampire) != vampire) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == MWMechanics::getPlayer()) MWBase::Environment::get().getWorld()->reattachPlayerCamera(); else rebuild(); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 6462c53c3..06f40f847 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -65,7 +65,6 @@ private: boost::shared_ptr mHeadAnimationTime; boost::shared_ptr mWeaponAnimationTime; - float mAlpha; bool mSoundsDisabled; void updateNpcBase(); @@ -134,9 +133,6 @@ public: /// Get the inventory slot that the given node path leads into, or -1 if not found. int getSlot(const osg::NodePath& path) const; - /// Make the NPC only partially visible - virtual void setAlpha(float alpha); - virtual void setVampire(bool vampire); /// Set a translation offset (in object root space) to apply to meshes when in first person mode. diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index 9fffd76d1..ae8bda1fb 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -17,6 +17,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/pathfinding.hpp" +#include "../mwmechanics/coordinateconverter.hpp" #include "vismask.hpp" @@ -207,11 +208,7 @@ void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) if (!pathgrid) return; osg::Vec3f cellPathGridPos(0, 0, 0); - if (store->getCell()->isExterior()) - { - cellPathGridPos.x() = static_cast(store->getCell()->mData.mX * ESM::Land::REAL_SIZE); - cellPathGridPos.y() = static_cast(store->getCell()->mData.mY * ESM::Land::REAL_SIZE); - } + MWMechanics::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; cellPathGrid->setPosition(cellPathGridPos); diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp new file mode 100644 index 000000000..63ccdd259 --- /dev/null +++ b/apps/openmw/mwrender/renderbin.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_MWRENDER_RENDERBIN_H +#define OPENMW_MWRENDER_RENDERBIN_H + +namespace MWRender +{ + + /// Defines the render bin numbers used in the OpenMW scene graph. The bin with the lowest number is rendered first. + /// Beware of RenderBin nesting, in most cases you will want to use setNestRenderBins(false). + enum RenderBins + { + RenderBin_Sky = -1, + RenderBin_Default = 0, + RenderBin_Water = 9, + RenderBin_OcclusionQuery = 10, + RenderBin_SunGlare = 11 + }; + +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c861119b5..f4b8aa451 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1,6 +1,7 @@ #include "renderingmanager.hpp" #include +#include #include #include @@ -379,8 +380,17 @@ namespace MWRender else { setFogColor(mFogColor); - mStateUpdater->setFogStart(mViewDistance * (1 - mFogDepth)); - mStateUpdater->setFogEnd(mViewDistance); + + if (mFogDepth == 0.f) + { + mStateUpdater->setFogStart(0.f); + mStateUpdater->setFogEnd(std::numeric_limits::max()); + } + else + { + mStateUpdater->setFogStart(mViewDistance * (1 - mFogDepth)); + mStateUpdater->setFogEnd(mViewDistance); + } } } @@ -437,19 +447,35 @@ namespace MWRender class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback { public: + NotifyDrawCompletedCallback() + : mDone(false) + { + } + virtual void operator () (osg::RenderInfo& renderInfo) const { + mMutex.lock(); + mDone = true; + mMutex.unlock(); mCondition.signal(); } + void waitTillDone() + { + mMutex.lock(); + if (mDone) + return; + mCondition.wait(&mMutex); + mMutex.unlock(); + } + mutable OpenThreads::Condition mCondition; + mutable OpenThreads::Mutex mMutex; + mutable bool mDone; }; void RenderingManager::screenshot(osg::Image *image, int w, int h) { - int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getCamera()->setCullMask(oldCullMask & (~Mask_GUI)); - osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); rttCamera->attach(osg::Camera::COLOR_BUFFER, image); @@ -473,24 +499,21 @@ namespace MWRender image->setPixelFormat(texture->getInternalFormat()); rttCamera->addChild(mLightRoot); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); mRootNode->addChild(rttCamera); - mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); - // The draw needs to complete before we can copy back our image. osg::ref_ptr callback (new NotifyDrawCompletedCallback); rttCamera->setFinalDrawCallback(callback); - OpenThreads::Mutex m; - m.lock(); - callback->mCondition.wait(&m); - m.unlock(); + + mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); + + callback->waitTillDone(); rttCamera->removeChildren(0, rttCamera->getNumChildren()); rttCamera->setGraphicsContext(NULL); mRootNode->removeChild(rttCamera); - - mViewer->getCamera()->setCullMask(oldCullMask); } osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index a3e96a5b1..a7637f2e1 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -22,6 +22,8 @@ #include "../mwworld/fallback.hpp" +#include "../mwmechanics/actorutil.hpp" + namespace { void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback, osg::Node* node) @@ -177,7 +179,7 @@ void RippleSimulation::removeCell(const MWWorld::CellStore *store) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) { - if (it->mPtr.getCell() == store && it->mPtr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (it->mPtr.getCell() == store && it->mPtr != MWMechanics::getPlayer()) { it = mEmitters.erase(it); } diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 8e591a5db..1717cca57 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -59,7 +59,6 @@ namespace MWRender private: osg::ref_ptr mParent; - Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mParticleSystem; osg::ref_ptr mParticleNode; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 49c00c33d..6f1b26733 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1,14 +1,20 @@ #include "sky.hpp" +#include + #include #include #include #include #include -#include #include #include #include +#include +#include +#include +#include +#include #include #include @@ -36,6 +42,7 @@ #include "../mwworld/fallback.hpp" #include "vismask.hpp" +#include "renderbin.hpp" namespace { @@ -319,11 +326,11 @@ private: int mMeshType; }; +/// A base class for the sun and moons. class CelestialBody { public: - CelestialBody(osg::Group* parentNode, Resource::SceneManager* sceneManager, float scaleFactor = 1.f, int numUvSets=1) - : mSceneManager(sceneManager) + CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets) { mGeode = new osg::Geode; osg::ref_ptr geom = createTexturedQuad(numUvSets); @@ -335,15 +342,9 @@ public: parentNode->addChild(mTransform); } - void setDirection(const osg::Vec3f& direction) - { - osg::Vec3f normalizedDirection = direction / direction.length(); - mTransform->setPosition(normalizedDirection*1000.f); + virtual ~CelestialBody() {} - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0,0,1), normalizedDirection); - mTransform->setAttitude(quat); - } + virtual void adjustTransparency(const float ratio) = 0; void setVisible(bool visible) { @@ -351,24 +352,465 @@ public: } protected: + static const float mDistance; osg::ref_ptr mTransform; osg::ref_ptr mGeode; - Resource::SceneManager* mSceneManager; - }; +const float CelestialBody::mDistance = 1000.0f; + class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::SceneManager* sceneManager) - : CelestialBody(parentNode, sceneManager, 1.f, 1) + Sun(osg::Group* parentNode, Resource::TextureManager& textureManager) + : CelestialBody(parentNode, 1.0f, 1) + , mUpdater(new Updater) { - osg::ref_ptr tex = mSceneManager->getTextureManager()->getTexture2D("textures/tx_sun_05.dds", - osg::Texture::CLAMP, osg::Texture::CLAMP); + mTransform->addUpdateCallback(mUpdater); - mTransform->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); - mTransform->getOrCreateStateSet()->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + osg::ref_ptr sunTex = textureManager.getTexture2D("textures/tx_sun_05.dds", + osg::Texture::CLAMP, + osg::Texture::CLAMP); + + mGeode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + + osg::ref_ptr queryNode (new osg::Group); + // Need to render after the world geometry so we can correctly test for occlusions + queryNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + queryNode->getOrCreateStateSet()->setNestRenderBins(false); + // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun + osg::ref_ptr alphaFunc (new osg::AlphaFunc); + alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); + queryNode->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + queryNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + queryNode->getOrCreateStateSet()->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + + mTransform->addChild(queryNode); + + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + + createSunFlash(textureManager); + createSunGlare(); } + + ~Sun() + { + mTransform->removeUpdateCallback(mUpdater); + destroySunFlash(); + destroySunGlare(); + } + + void setColor(const osg::Vec4f& color) + { + mUpdater->mColor.r() = color.r(); + mUpdater->mColor.g() = color.g(); + mUpdater->mColor.b() = color.b(); + } + + virtual void adjustTransparency(const float ratio) + { + mUpdater->mColor.a() = ratio; + if (mSunGlareCallback) + mSunGlareCallback->setGlareView(ratio); + if (mSunFlashCallback) + mSunFlashCallback->setGlareView(ratio); + } + + void setDirection(const osg::Vec3f& direction) + { + osg::Vec3f normalizedDirection = direction / direction.length(); + mTransform->setPosition(normalizedDirection * mDistance); + + osg::Quat quat; + quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); + mTransform->setAttitude(quat); + } + + void setGlareTimeOfDayFade(float val) + { + if (mSunGlareCallback) + mSunGlareCallback->setTimeOfDayFade(val); + } + +private: + /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) + { + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->setQueriesEnabled(true); + + // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, + // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry + // is only called once. + // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. + oqn->getQueryGeometry()->setDataVariance(osg::Object::STATIC); + + osg::ref_ptr queryGeode = osg::clone(mGeode.get(), osg::CopyOp::DEEP_COPY_ALL); + // Disable writing to the color buffer. We are using this geode for visibility tests only. + osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); + queryGeode->getOrCreateStateSet()->setAttributeAndModes(colormask, osg::StateAttribute::ON); + + oqn->addChild(queryGeode); + + // Remove the default OFF|PROTECTED setting for texturing. We *want* to enable texturing for alpha testing purposes + oqn->getQueryStateSet()->removeTextureMode(0, GL_TEXTURE_2D); + + // Need to add texture coordinates so that texturing works. A bit ugly, relies on the vertex ordering + // used within OcclusionQueryNode. + osg::ref_ptr texCoordArray (new osg::Vec2Array); + for (int i=0; i<8; ++i) + { + texCoordArray->push_back(osg::Vec2(0,0)); + texCoordArray->push_back(osg::Vec2(1,0)); + texCoordArray->push_back(osg::Vec2(0,0)); + texCoordArray->push_back(osg::Vec2(1,0)); + texCoordArray->push_back(osg::Vec2(1,1)); + texCoordArray->push_back(osg::Vec2(0,1)); + texCoordArray->push_back(osg::Vec2(0,1)); + texCoordArray->push_back(osg::Vec2(1,1)); + } + + oqn->getQueryGeometry()->setTexCoordArray(0, texCoordArray, osg::Array::BIND_PER_VERTEX); + + if (queryVisible) + { + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::LESS); + // This is a trick to make fragments written by the query always use the maximum depth value, + // without having to retrieve the current far clipping distance. + // We want the sun glare to be "infinitely" far away. + depth->setZNear(1.0); + depth->setZFar(1.0); + oqn->getQueryStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else + { + oqn->getQueryStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + + parent->addChild(oqn); + + return oqn; + } + + void createSunFlash(Resource::TextureManager& textureManager) + { + osg::ref_ptr tex = textureManager.getTexture2D("textures/tx_sun_flash_grey_05.dds", + osg::Texture::CLAMP, + osg::Texture::CLAMP); + + osg::ref_ptr transform (new osg::PositionAttitudeTransform); + const float scale = 2.6f; + transform->setScale(osg::Vec3f(scale,scale,scale)); + + mTransform->addChild(transform); + + osg::ref_ptr geode (new osg::Geode); + transform->addChild(geode); + + geode->addDrawable(createTexturedQuad()); + + osg::StateSet* stateset = geode->getOrCreateStateSet(); + + stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + + mSunFlashNode = transform; + + mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); + mSunFlashNode->addCullCallback(mSunFlashCallback); + } + void destroySunFlash() + { + if (mSunFlashNode) + { + mSunFlashNode->removeCullCallback(mSunFlashCallback); + mSunFlashCallback = NULL; + } + } + + void createSunGlare() + { + osg::ref_ptr camera (new osg::Camera); + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? + camera->setViewMatrix(osg::Matrix::identity()); + camera->setClearMask(0); + camera->setRenderOrder(osg::Camera::NESTED_RENDER); + camera->setAllowEventFocus(false); + + osg::ref_ptr geode (new osg::Geode); + osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); + geode->addDrawable(geom); + + camera->addChild(geode); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + // set up additive blending + osg::ref_ptr blendFunc (new osg::BlendFunc); + blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); + blendFunc->setDestination(osg::BlendFunc::ONE); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + + mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); + mSunGlareNode = camera; + + mSunGlareNode->addCullCallback(mSunGlareCallback); + + mTransform->addChild(camera); + } + void destroySunGlare() + { + if (mSunGlareNode) + { + mSunGlareNode->removeCullCallback(mSunGlareCallback); + mSunGlareCallback = NULL; + } + } + + class Updater : public SceneUtil::StateSetUpdater + { + public: + osg::Vec4f mColor; + + Updater() + : mColor(1.f, 1.f, 1.f, 1.f) + { + } + + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); + } + }; + + class OcclusionCallback : public osg::NodeCallback + { + public: + OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : mOcclusionQueryVisiblePixels(oqnVisible) + , mOcclusionQueryTotalPixels(oqnTotal) + { + } + + protected: + float getVisibleRatio (osg::Camera* camera) + { + int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); + int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); + + float visibleRatio = 0.f; + if (total > 0) + visibleRatio = static_cast(visible) / static_cast(total); + + float dt = MWBase::Environment::get().getFrameDuration(); + + float lastRatio = mLastRatio[osg::observer_ptr(camera)]; + + float change = dt*10; + + if (visibleRatio > lastRatio) + visibleRatio = std::min(visibleRatio, lastRatio + change); + else + visibleRatio = std::max(visibleRatio, lastRatio - change); + + mLastRatio[osg::observer_ptr(camera)] = visibleRatio; + + return visibleRatio; + } + + private: + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + + std::map, float> mLastRatio; + }; + + /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. + class SunFlashCallback : public OcclusionCallback + { + public: + SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : OcclusionCallback(oqnVisible, oqnTotal) + , mGlareView(1.f) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + osg::ref_ptr stateset; + + if (visibleRatio > 0.f) + { + const float fadeThreshold = 0.1; + if (visibleRatio < fadeThreshold) + { + float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; + osg::ref_ptr mat (createUnlitMaterial()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); + stateset = new osg::StateSet; + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } + + float scale = visibleRatio; + + if (scale == 0.f) + { + // no traverse + return; + } + else + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); + + if (stateset) + cv->pushStateSet(stateset); + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, nv); + + cv->popModelViewMatrix(); + + if (stateset) + cv->popStateSet(); + } + } + + void setGlareView(float value) + { + mGlareView = value; + } + + private: + float mGlareView; + }; + + + /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. + /// Must be attached as a cull callback to the node above the glare node. + class SunGlareCallback : public OcclusionCallback + { + public: + SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, + osg::ref_ptr sunTransform) + : OcclusionCallback(oqnVisible, oqnTotal) + , mSunTransform(sunTransform) + , mTimeOfDayFade(1.f) + , mGlareView(1.f) + { + + } + + virtual void operator ()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + float angleRadians = getAngleToSunInRadians(cv->getCurrentCamera()); + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + const float angleMaxRadians = osg::DegreesToRadians(30.f); // Sun Glare Fader Angle Max + + float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); + + const float sunGlareFaderMax = 0.5f; + float fade = value * sunGlareFaderMax; + + fade *= mTimeOfDayFade * mGlareView * visibleRatio; + + if (fade == 0.f) + { + // no traverse + return; + } + else + { + osg::ref_ptr stateset (new osg::StateSet); + + osg::ref_ptr mat (createUnlitMaterial()); + + osg::Vec4f sunGlareFaderColor (222/255.f, 95/255.f, 39/255.f, 1); + + // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, + // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, + // so the resulting color looks more orange than red. + sunGlareFaderColor *= 2; + for (int i=0; i<3; ++i) + sunGlareFaderColor[i] = std::min(1.f, sunGlareFaderColor[i]); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); + mat->setEmission(osg::Material::FRONT_AND_BACK, sunGlareFaderColor); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + cv->pushStateSet(stateset); + traverse(node, nv); + cv->popStateSet(); + } + } + + void setTimeOfDayFade(float val) + { + mTimeOfDayFade = val; + } + + void setGlareView(float glareView) + { + mGlareView = glareView; + } + + private: + float getAngleToSunInRadians(osg::Camera* camera) const + { + osg::Vec3d eye, center, up; + camera->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d sun = mSunTransform->getPosition(); + + forward.normalize(); + sun.normalize(); + float angleRadians = std::acos(forward * sun); + return angleRadians; + } + + osg::ref_ptr mSunTransform; + float mTimeOfDayFade; + float mGlareView; + }; + + osg::ref_ptr mUpdater; + osg::ref_ptr mSunFlashCallback; + osg::ref_ptr mSunFlashNode; + osg::ref_ptr mSunGlareCallback; + osg::ref_ptr mSunGlareNode; + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; }; class Moon : public CelestialBody @@ -380,91 +822,102 @@ public: Type_Secunda }; - Moon(osg::Group* parentNode, Resource::SceneManager* sceneManager, float scaleFactor, Type type) - : CelestialBody(parentNode, sceneManager, scaleFactor, 2) + Moon(osg::Group* parentNode, Resource::TextureManager& textureManager, float scaleFactor, Type type) + : CelestialBody(parentNode, scaleFactor, 2) , mType(type) - , mPhase(Phase_Unspecified) + , mPhase(MoonState::Phase_Unspecified) + , mUpdater(new Updater(textureManager)) { - mUpdater = new MoonUpdater; + setPhase(MoonState::Phase_Full); + setVisible(true); + mGeode->addUpdateCallback(mUpdater); - - setPhase(Phase_WaxingCrescent); } - enum Phase + ~Moon() { - Phase_New = 0, - Phase_WaxingCrescent, - Phase_WaxingHalf, - Phase_WaxingGibbous, - Phase_Full, - Phase_WaningGibbous, - Phase_WaningHalf, - Phase_WaningCrescent, - Phase_Unspecified - }; - - void setTextures(const std::string& phaseTex, const std::string& circleTex) - { - osg::ref_ptr phaseTexPtr = mSceneManager->getTextureManager()->getTexture2D(phaseTex, - osg::Texture::CLAMP, osg::Texture::CLAMP); - - osg::ref_ptr circleTexPtr = mSceneManager->getTextureManager()->getTexture2D(circleTex, - osg::Texture::CLAMP, osg::Texture::CLAMP); - - mUpdater->setTextures(phaseTexPtr, circleTexPtr); + mGeode->removeUpdateCallback(mUpdater); } - void setPhase(const Phase& phase) + virtual void adjustTransparency(const float ratio) { - if (mPhase == phase) - return; - mPhase = phase; - - std::string textureName = "textures/tx_"; - - if (mType == Moon::Type_Secunda) textureName += "secunda_"; - else textureName += "masser_"; - - if (phase == Moon::Phase_New) textureName += "new"; - else if (phase == Moon::Phase_WaxingCrescent) textureName += "one_wax"; - else if (phase == Moon::Phase_WaxingHalf) textureName += "half_wax"; - else if (phase == Moon::Phase_WaxingGibbous) textureName += "three_wax"; - else if (phase == Moon::Phase_WaningCrescent) textureName += "one_wan"; - else if (phase == Moon::Phase_WaningHalf) textureName += "half_wan"; - else if (phase == Moon::Phase_WaningGibbous) textureName += "three_wan"; - else if (phase == Moon::Phase_Full) textureName += "full"; - - textureName += ".dds"; - - if (mType == Moon::Type_Secunda) - setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); - else - setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); + mUpdater->mTransparency *= ratio; } - void setType(const Type& type) + void setState(const MoonState& state) { - mType = type; + float radsX = ((state.mRotationFromHorizon) * M_PI) / 180.0f; + float radsZ = ((state.mRotationFromNorth) * M_PI) / 180.0f; + + osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); + + osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); + mTransform->setPosition(direction * mDistance); + + // The moon quad is initially oriented facing down, so we need to offset its X-axis + // rotation to rotate it to face the camera when sitting at the horizon. + osg::Quat attX((-M_PI / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + mTransform->setAttitude(attX * rotZ); + + setPhase(state.mPhase); + mUpdater->mTransparency = state.mMoonAlpha; + mUpdater->mShadowBlend = state.mShadowBlend; } - class MoonUpdater : public SceneUtil::StateSetUpdater + void setAtmosphereColor(const osg::Vec4f& color) { - public: - MoonUpdater() - : mFade(0.f) - , mMoonColor(1,1,1,1) + mUpdater->mAtmosphereColor = color; + } + + void setColor(const osg::Vec4f& color) + { + mUpdater->mMoonColor = color; + } + + unsigned int getPhaseInt() const + { + if (mPhase == MoonState::Phase_New) return 0; + else if (mPhase == MoonState::Phase_WaxingCrescent) return 1; + else if (mPhase == MoonState::Phase_WaningCrescent) return 1; + else if (mPhase == MoonState::Phase_FirstQuarter) return 2; + else if (mPhase == MoonState::Phase_ThirdQuarter) return 2; + else if (mPhase == MoonState::Phase_WaxingGibbous) return 3; + else if (mPhase == MoonState::Phase_WaningGibbous) return 3; + else if (mPhase == MoonState::Phase_Full) return 4; + return 0; + } + +private: + struct Updater : public SceneUtil::StateSetUpdater + { + Resource::TextureManager& mTextureManager; + osg::ref_ptr mPhaseTex; + osg::ref_ptr mCircleTex; + float mTransparency; + float mShadowBlend; + osg::Vec4f mAtmosphereColor; + osg::Vec4f mMoonColor; + + Updater(Resource::TextureManager& textureManager) + : mTextureManager(textureManager) + , mPhaseTex() + , mCircleTex() + , mTransparency(1.0f) + , mShadowBlend(1.0f) + , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) + , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) { } - virtual void setDefaults(osg::StateSet *stateset) + virtual void setDefaults(osg::StateSet* stateset) { stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // fade * MoonRedColor + texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); @@ -475,90 +928,64 @@ public: texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); - texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // atmospherecolor + texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } - virtual void apply(osg::StateSet *stateset, osg::NodeVisitor*) + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*) { osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mMoonColor * mFade); + texEnv->setConstantColor(mMoonColor * mShadowBlend); osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - const float backdropFadeThreshold = 0.03; - if (mFade <= backdropFadeThreshold) - { - texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mFade / backdropFadeThreshold)); - } - else - texEnv2->setConstantColor(mAtmosphereColor); + texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); } - void setFade (const float fade) + void setTextures(const std::string& phaseTex, const std::string& circleTex) { - mFade = fade; - } + mPhaseTex = mTextureManager.getTexture2D(phaseTex, osg::Texture::CLAMP, osg::Texture::CLAMP); + mCircleTex = mTextureManager.getTexture2D(circleTex, osg::Texture::CLAMP, osg::Texture::CLAMP); - void setAtmosphereColor(const osg::Vec4f& color) - { - mAtmosphereColor = color; - } - - void setMoonColor(const osg::Vec4f& color) - { - mMoonColor = color; - } - - void setTextures(osg::ref_ptr phaseTex, osg::ref_ptr circleTex) - { - mPhaseTex = phaseTex; - mCircleTex = circleTex; reset(); } - - private: - float mFade; - osg::Vec4f mAtmosphereColor; - osg::Vec4f mMoonColor; - osg::ref_ptr mPhaseTex; - osg::ref_ptr mCircleTex; }; - - void setAtmosphereColor(const osg::Vec4f& color) - { - mUpdater->setAtmosphereColor(color); - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->setMoonColor(color); - } - - void setFade(const float fade) - { - mUpdater->setFade(fade); - } - - unsigned int getPhaseInt() const - { - if (mPhase == Moon::Phase_New) return 0; - else if (mPhase == Moon::Phase_WaxingCrescent) return 1; - else if (mPhase == Moon::Phase_WaningCrescent) return 1; - else if (mPhase == Moon::Phase_WaxingHalf) return 2; - else if (mPhase == Moon::Phase_WaningHalf) return 2; - else if (mPhase == Moon::Phase_WaxingGibbous) return 3; - else if (mPhase == Moon::Phase_WaningGibbous) return 3; - else if (mPhase == Moon::Phase_Full) return 4; - return 0; - } - -private: Type mType; - Phase mPhase; - osg::ref_ptr mUpdater; + MoonState::Phase mPhase; + osg::ref_ptr mUpdater; + + void setPhase(const MoonState::Phase& phase) + { + if(mPhase == phase) + return; + + mPhase = phase; + + std::string textureName = "textures/tx_"; + + if (mType == Moon::Type_Secunda) + textureName += "secunda_"; + else + textureName += "masser_"; + + if (phase == MoonState::Phase_New) textureName += "new"; + else if(phase == MoonState::Phase_WaxingCrescent) textureName += "one_wax"; + else if(phase == MoonState::Phase_FirstQuarter) textureName += "half_wax"; + else if(phase == MoonState::Phase_WaxingGibbous) textureName += "three_wax"; + else if(phase == MoonState::Phase_WaningCrescent) textureName += "one_wan"; + else if(phase == MoonState::Phase_ThirdQuarter) textureName += "half_wan"; + else if(phase == MoonState::Phase_WaningGibbous) textureName += "three_wan"; + else if(phase == MoonState::Phase_Full) textureName += "full"; + + textureName += ".dds"; + + if (mType == Moon::Type_Secunda) + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); + else + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); + } }; SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) @@ -574,12 +1001,9 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mClouds() , mNextClouds() , mCloudBlendFactor(0.0f) - , mCloudOpacity(0.0f) , mCloudSpeed(0.0f) , mStarsOpacity(0.0f) , mRemainingTransitionTime(0.0f) - , mGlare(0.0f) - , mGlareFade(0.0f) , mRainEnabled(false) , mRainSpeed(0) , mRainFrequency(1) @@ -594,7 +1018,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana mRootNode = skyroot; // By default render before the world is rendered - mRootNode->getOrCreateStateSet()->setRenderBinDetails(-1, "RenderBin"); + mRootNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); } void SkyManager::create() @@ -623,11 +1047,11 @@ void SkyManager::create() mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getTextureManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - mSun.reset(new Sun(mRootNode, mSceneManager)); + mSun.reset(new Sun(mRootNode, *mSceneManager->getTextureManager())); const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); - mMasser.reset(new Moon(mRootNode, mSceneManager, fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mRootNode, mSceneManager, fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); + mMasser.reset(new Moon(mRootNode, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mRootNode, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); mCloudNode = new osg::PositionAttitudeTransform; mRootNode->addChild(mCloudNode); @@ -635,12 +1059,15 @@ void SkyManager::create() ModVertexAlphaVisitor modClouds(1); mCloudMesh->accept(modClouds); mCloudUpdater = new CloudUpdater; + mCloudUpdater->setOpacity(1.f); mCloudMesh->addUpdateCallback(mCloudUpdater); mCloudMesh2 = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); mCloudMesh2->accept(modClouds); mCloudUpdater2 = new CloudUpdater; + mCloudUpdater2->setOpacity(0.f); mCloudMesh2->addUpdateCallback(mCloudUpdater2); + mCloudMesh2->setNodeMask(0); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(false); @@ -705,6 +1132,13 @@ public: mAlpha = alpha; } + virtual void setDefaults(osg::StateSet* stateset) + { + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + } + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); @@ -886,36 +1320,6 @@ void SkyManager::update(float duration) mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); - /// \todo improve this - mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); - mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); - - if (mSunEnabled) - { - // take 1/10 sec for fading the glare effect from invisible to full - if (mGlareFade > mGlare) - { - mGlareFade -= duration*10; - if (mGlareFade < mGlare) mGlareFade = mGlare; - } - else if (mGlareFade < mGlare) - { - mGlareFade += duration*10; - if (mGlareFade > mGlare) mGlareFade = mGlare; - } - - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun - /* - Vector3 sun = mSunGlare->getPosition(); - Vector3 cam = mCamera->getRealDirection(); - const Degree angle = sun.angleBetween( cam ); - float val = 1- (angle.valueDegrees() / 180.f); - val = (val*val*val*val)*6; - mSunGlare->setSize(val * mGlareFade); - */ - } - // rotate the stars by 360 degrees every 4 days mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); if (mAtmosphereNightNode->getNodeMask() != 0) @@ -949,7 +1353,7 @@ void SkyManager::updateRainParameters() } } -void SkyManager::setWeather(const MWWorld::WeatherResult& weather) +void SkyManager::setWeather(const WeatherResult& weather) { if (!mCreated) return; @@ -1026,14 +1430,12 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) osg::Texture::REPEAT, osg::Texture::REPEAT)); } - if (mCloudBlendFactor != weather.mCloudBlendFactor - || mCloudOpacity != weather.mCloudOpacity) + if (mCloudBlendFactor != weather.mCloudBlendFactor) { mCloudBlendFactor = weather.mCloudBlendFactor; - mCloudOpacity = weather.mCloudOpacity; - mCloudUpdater->setOpacity(mCloudOpacity * (1.f-mCloudBlendFactor)); - mCloudUpdater2->setOpacity(mCloudOpacity * mCloudBlendFactor); + mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); + mCloudUpdater2->setOpacity(mCloudBlendFactor); mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0 : 0); } @@ -1066,40 +1468,28 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) mCloudSpeed = weather.mCloudSpeed; - if (weather.mNight && mStarsOpacity != weather.mNightFade) + mMasser->adjustTransparency(weather.mGlareView); + mSecunda->adjustTransparency(weather.mGlareView); + + mSun->setColor(weather.mSunDiscColor); + mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); + + float nextStarsOpacity = weather.mNightFade * weather.mGlareView; + if(weather.mNight && mStarsOpacity != nextStarsOpacity) { - mStarsOpacity = weather.mNightFade; + mStarsOpacity = nextStarsOpacity; mAtmosphereNightUpdater->setFade(mStarsOpacity); } mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); - - /* - float strength; - float timeofday_angle = std::abs(mSunGlare->getPosition().z/mSunGlare->getPosition().length()); - if (timeofday_angle <= 0.44) - strength = timeofday_angle/0.44f; - else - strength = 1.f; - - mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); - - mSun->setVisibility(weather.mGlareView * strength); - */ - if (mRainFader) mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? if (mParticleFader) mParticleFader->setAlpha(weather.mEffectFade); } -void SkyManager::setGlare(const float glare) -{ - mGlare = glare; -} - void SkyManager::sunEnable() { if (!mCreated) return; @@ -1124,76 +1514,20 @@ void SkyManager::setSunDirection(const osg::Vec3f& direction) if (!mCreated) return; mSun->setDirection(direction); - - //mSunGlare->setPosition(direction); } -void SkyManager::setMasserDirection(const osg::Vec3f& direction) +void SkyManager::setMasserState(const MoonState& state) { - if (!mCreated) return; + if(!mCreated) return; - mMasser->setDirection(direction); + mMasser->setState(state); } -void SkyManager::setSecundaDirection(const osg::Vec3f& direction) +void SkyManager::setSecundaState(const MoonState& state) { - if (!mCreated) return; + if(!mCreated) return; - mSecunda->setDirection(direction); -} - -void SkyManager::masserEnable() -{ - if (!mCreated) return; - - mMasser->setVisible(true); -} - -void SkyManager::secundaEnable() -{ - if (!mCreated) return; - - mSecunda->setVisible(true); -} - -void SkyManager::masserDisable() -{ - if (!mCreated) return; - - mMasser->setVisible(false); -} - -void SkyManager::secundaDisable() -{ - if (!mCreated) return; - - mSecunda->setVisible(false); -} - -void SkyManager::setLightningStrength(const float factor) -{ - if (!mCreated) return; - /* - if (factor > 0.f) - { - mLightning->setDiffuseColour (ColourValue(2*factor, 2*factor, 2*factor)); - mLightning->setVisible(true); - } - else - mLightning->setVisible(false); - */ -} - -void SkyManager::setMasserFade(const float fade) -{ - if (!mCreated) return; - mMasser->setFade(fade); -} - -void SkyManager::setSecundaFade(const float fade) -{ - if (!mCreated) return; - mSecunda->setFade(fade); + mSecunda->setState(state); } void SkyManager::setDate(int day, int month) @@ -1202,11 +1536,9 @@ void SkyManager::setDate(int day, int month) mMonth = month; } -void SkyManager::setGlareEnabled (bool enabled) +void SkyManager::setGlareTimeOfDayFade(float val) { - if (!mCreated || !mEnabled) - return; - //mSunGlare->setVisible (mSunEnabled && enabled); + mSun->setGlareTimeOfDayFade(val); } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 4d1c73e44..072083d27 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -1,9 +1,12 @@ #ifndef OPENMW_MWRENDER_SKY_H #define OPENMW_MWRENDER_SKY_H -#include +#include +#include -#include "../mwworld/weather.hpp" +#include +#include +#include namespace osg { @@ -33,6 +36,70 @@ namespace MWRender class RainFader; class AlphaFader; + struct WeatherResult + { + std::string mCloudTexture; + std::string mNextCloudTexture; + float mCloudBlendFactor; + + osg::Vec4f mFogColor; + + osg::Vec4f mAmbientColor; + + osg::Vec4f mSkyColor; + + // sun light color + osg::Vec4f mSunColor; + + // alpha is the sun transparency + osg::Vec4f mSunDiscColor; + + float mFogDepth; + + float mWindSpeed; + + float mCloudSpeed; + + float mGlareView; + + bool mNight; // use night skybox + float mNightFade; // fading factor for night skybox + + bool mIsStorm; + + std::string mAmbientLoopSoundID; + float mAmbientSoundVolume; + + std::string mParticleEffect; + std::string mRainEffect; + float mEffectFade; + + float mRainSpeed; + float mRainFrequency; + }; + + struct MoonState + { + enum Phase + { + Phase_Full = 0, + Phase_WaningGibbous, + Phase_ThirdQuarter, + Phase_WaningCrescent, + Phase_New, + Phase_WaxingCrescent, + Phase_FirstQuarter, + Phase_WaxingGibbous, + Phase_Unspecified + }; + + float mRotationFromHorizon; + float mRotationFromNorth; + Phase mPhase; + float mShadowBlend; + float mMoonAlpha; + }; + class SkyManager { public: @@ -60,7 +127,7 @@ namespace MWRender void setMoonColour (bool red); ///< change Secunda colour to red - void setWeather(const MWWorld::WeatherResult& weather); + void setWeather(const WeatherResult& weather); void sunEnable(); @@ -72,24 +139,10 @@ namespace MWRender void setSunDirection(const osg::Vec3f& direction); - void setMasserDirection(const osg::Vec3f& direction); + void setMasserState(const MoonState& state); + void setSecundaState(const MoonState& state); - void setSecundaDirection(const osg::Vec3f& direction); - - void setMasserFade(const float fade); - - void setSecundaFade(const float fade); - - void masserEnable(); - void masserDisable(); - - void secundaEnable(); - void secundaDisable(); - - void setLightningStrength(const float factor); - - void setGlare(const float glare); - void setGlareEnabled(bool enabled); + void setGlareTimeOfDayFade(float val); private: void create(); @@ -148,7 +201,6 @@ namespace MWRender std::string mClouds; std::string mNextClouds; float mCloudBlendFactor; - float mCloudOpacity; float mCloudSpeed; float mStarsOpacity; osg::Vec4f mCloudColour; @@ -159,9 +211,6 @@ namespace MWRender float mRemainingTransitionTime; - float mGlare; // target - float mGlareFade; // actual - bool mRainEnabled; std::string mRainEffect; float mRainSpeed; diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 269e7f99f..f9a9083f0 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -51,7 +51,7 @@ namespace MWRender maxY += 1; } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 93531a552..a12ffd540 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -10,7 +10,7 @@ namespace MWRender class TerrainStorage : public ESMTerrain::Storage { private: - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7cad745dd..03ab58e6b 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -17,8 +17,11 @@ #include #include +#include + #include "vismask.hpp" #include "ripplesimulation.hpp" +#include "renderbin.hpp" namespace { @@ -84,7 +87,7 @@ namespace depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); - stateset->setRenderBinDetails(9, "RenderBin"); + stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); std::vector > textures; for (int i=0; i<32; ++i) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 4a7952c44..78c84141a 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -1,4 +1,3 @@ - #include "aiextensions.hpp" #include diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index c43cdf565..07a8a300a 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -1,4 +1,3 @@ - #include "animationextensions.hpp" #include diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 8af2e5ef3..5dd3cf411 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -16,6 +16,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" namespace MWScript @@ -91,14 +93,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { runtime.push (0); return; } bool interior = - !MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell()->isExterior(); + !MWMechanics::getPlayer().getCell()->getCell()->isExterior(); runtime.push (interior ? 1 : 0); } @@ -113,12 +115,12 @@ namespace MWScript std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { runtime.push(0); return; } - const MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + const MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); Misc::StringUtils::toLower(current); @@ -136,12 +138,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { runtime.push(0.f); return; } - MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->hasWater()) runtime.push (cell->getWaterLevel()); else @@ -157,12 +159,12 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { return; } - MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); @@ -180,12 +182,12 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { return; } - MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 3ff75eeae..083f463bc 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -1,4 +1,3 @@ - #include "compilercontext.hpp" #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp index 30956d429..d2e07d3e1 100644 --- a/apps/openmw/mwscript/consoleextensions.cpp +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -1,4 +1,3 @@ - #include "consoleextensions.hpp" #include diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 70475d8e2..4e3e010f7 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -1,4 +1,3 @@ - #include "containerextensions.hpp" #include @@ -24,9 +23,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -56,6 +56,12 @@ namespace MWScript if (count == 0) return; + if(::Misc::StringUtils::ciEqual(item, "gold_005") + || ::Misc::StringUtils::ciEqual(item, "gold_010") + || ::Misc::StringUtils::ciEqual(item, "gold_025") + || ::Misc::StringUtils::ciEqual(item, "gold_100")) + item = "gold_001"; + 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) @@ -91,6 +97,12 @@ namespace MWScript std::string item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + if(::Misc::StringUtils::ciEqual(item, "gold_005") + || ::Misc::StringUtils::ciEqual(item, "gold_010") + || ::Misc::StringUtils::ciEqual(item, "gold_025") + || ::Misc::StringUtils::ciEqual(item, "gold_100")) + item = "gold_001"; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); runtime.push (store.count(item)); @@ -119,6 +131,12 @@ namespace MWScript if (count == 0) return; + if(::Misc::StringUtils::ciEqual(item, "gold_005") + || ::Misc::StringUtils::ciEqual(item, "gold_010") + || ::Misc::StringUtils::ciEqual(item, "gold_025") + || ::Misc::StringUtils::ciEqual(item, "gold_100")) + item = "gold_001"; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; @@ -130,7 +148,7 @@ namespace MWScript // Spawn a messagebox (only for items removed from player's inventory) if ((numRemoved > 0) - && (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())) + && (ptr == MWMechanics::getPlayer())) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory std::string msgBox; @@ -172,11 +190,13 @@ namespace MWScript if (it == invStore.end()) throw std::runtime_error("Item to equip not found"); - MWWorld::ActionEquip action (*it); - action.execute(ptr); - - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() && !ptr.getClass().getScript(ptr).empty()) - ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->useItem(*it); + else + { + boost::shared_ptr action = it->getClass().use(*it); + action->execute(ptr); + } } }; diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 904e0ee85..626fafb5a 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -1,4 +1,3 @@ - #include "controlextensions.hpp" #include diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 8b6805264..c305fb81f 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -1,4 +1,3 @@ - #include "dialogueextensions.hpp" #include diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index 2170ba4fb..12bf3413a 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 44d96e949..6074a12d0 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,4 +1,3 @@ - #include "globalscripts.hpp" #include @@ -199,13 +198,12 @@ namespace MWScript if (iter==mScripts.end()) { - if (const ESM::Script *script = mStore.get().find (name)) - { - GlobalScriptDesc desc; - desc.mLocals.configure (*script); + const ESM::Script *script = mStore.get().find (name); - iter = mScripts.insert (std::make_pair (name, desc)).first; - } + GlobalScriptDesc desc; + desc.mLocals.configure (*script); + + iter = mScripts.insert (std::make_pair (name2, desc)).first; } return iter->second.mLocals; diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 40c555f50..397e0cac5 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -1,4 +1,3 @@ - #include "guiextensions.hpp" #include @@ -15,6 +14,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -54,7 +55,7 @@ namespace MWScript { MWWorld::Ptr bed = R()(runtime, false); - if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWBase::Environment::get().getWorld()->getPlayerPtr(), + if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWMechanics::getPlayer(), bed)) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_RestBed); } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index df675aebb..b0d4d3f2d 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -1,4 +1,3 @@ - #include "interpretercontext.hpp" #include diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index a18d0d5ae..ee93ea2e7 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -9,13 +9,32 @@ #include "../mwbase/environment.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" #include namespace MWScript { - void Locals::configure (const ESM::Script& script) + void Locals::ensure (const std::string& scriptName) { + if (!mInitialised) + { + const ESM::Script *script = MWBase::Environment::get().getWorld()->getStore(). + get().find (scriptName); + + configure (*script); + } + } + + Locals::Locals() : mInitialised (false) {} + + bool Locals::configure (const ESM::Script& script) + { + if (mInitialised) + return false; + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals (script.mId); @@ -25,6 +44,9 @@ namespace MWScript mLongs.resize (locals.get ('l').size(), 0); mFloats.clear(); mFloats.resize (locals.get ('f').size(), 0); + + mInitialised = true; + return true; } bool Locals::isEmpty() const @@ -36,6 +58,8 @@ namespace MWScript { try { + ensure (script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); @@ -49,6 +73,8 @@ namespace MWScript int Locals::getIntVar(const std::string &script, const std::string &var) { + ensure (script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); @@ -73,6 +99,8 @@ namespace MWScript bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { + ensure (script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); @@ -94,8 +122,11 @@ namespace MWScript return false; } - void Locals::write (ESM::Locals& locals, const std::string& script) const + bool Locals::write (ESM::Locals& locals, const std::string& script) const { + if (!mInitialised) + return false; + try { const Compiler::Locals& declarations = @@ -132,10 +163,14 @@ namespace MWScript catch (const Compiler::SourceException&) { } + + return true; } void Locals::read (const ESM::Locals& locals, const std::string& script) { + ensure (script); + try { const Compiler::Locals& declarations = diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index a9fcf317c..bb4df42bf 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -15,30 +15,50 @@ namespace MWScript { class Locals { + bool mInitialised; + + void ensure (const std::string& scriptName); + public: std::vector mShorts; std::vector mLongs; std::vector mFloats; + Locals(); + /// Are there any locals? + /// + /// \note Will return false, if locals have not been configured yet. bool isEmpty() const; - void configure (const ESM::Script& script); + /// \return Did the state of *this change from uninitialised to initialised? + bool configure (const ESM::Script& script); /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary bool setVarByInt(const std::string& script, const std::string& var, int val); + /// \note Locals will be automatically configured first, if necessary + // + // \note If it can not be determined if the variable exists, the error will be + // ignored and false will be returned. bool hasVar(const std::string& script, const std::string& var); /// if var does not exist, returns 0 /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary int getIntVar (const std::string& script, const std::string& var); - void write (ESM::Locals& locals, const std::string& script) const; + /// \note If locals have not been configured yet, no data is written. + /// + /// \return Locals written? + bool write (ESM::Locals& locals, const std::string& script) const; + /// \note Locals will be automatically configured first, if necessary void read (const ESM::Locals& locals, const std::string& script); }; } #endif - diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 8409351aa..32d86f55a 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1,4 +1,3 @@ - #include "miscextensions.hpp" #include @@ -28,6 +27,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -157,7 +157,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); + context.executeActivation(ptr, MWMechanics::getPlayer()); } }; @@ -567,7 +567,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + runtime.push((ptr.getClass().hasInventoryStore(ptr) || ptr.getClass().isBipedal(ptr)) && + ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); } }; @@ -580,7 +581,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); + runtime.push(ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); } }; @@ -976,7 +977,7 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); @@ -988,7 +989,7 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 5f755e542..b73c9a642 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -1,4 +1,3 @@ - #include "scriptmanagerimp.hpp" #include @@ -171,7 +170,7 @@ namespace MWScript return iter->second; } - if (const ESM::Script *script = mStore.get().find (name2)) + if (const ESM::Script *script = mStore.get().search (name2)) { if (mVerbose) std::cout diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index d28d01b63..e0fccd16d 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -1,4 +1,3 @@ - #include "skyextensions.hpp" #include diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 606de7aa0..a9896d203 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 61a31e115..e4ad71875 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -25,6 +25,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -227,7 +228,7 @@ namespace MWScript // workaround broken endgame scripts that kill dagoth ur if (!R::implicit && - Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "dagoth_ur_1")) + ::Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "dagoth_ur_1")) { runtime.push (peek); @@ -460,7 +461,7 @@ namespace MWScript MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() && + if (ptr == MWMechanics::getPlayer() && id == wm->getSelectedSpell()) { wm->unsetSelectedSpell(); @@ -548,7 +549,7 @@ namespace MWScript if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).joinFaction(factionID); } } @@ -580,7 +581,7 @@ namespace MWScript if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) == player.getClass().getNpcStats(player).getFactionRanks().end()) { player.getClass().getNpcStats(player).joinFaction(factionID); @@ -619,7 +620,7 @@ namespace MWScript if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).lowerRank(factionID); } } @@ -648,7 +649,7 @@ namespace MWScript // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) @@ -757,7 +758,7 @@ namespace MWScript ::Misc::StringUtils::toLower (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); runtime.push ( player.getClass().getNpcStats (player).getFactionReputation (factionId)); } @@ -792,7 +793,7 @@ namespace MWScript ::Misc::StringUtils::toLower (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, value); } }; @@ -826,7 +827,7 @@ namespace MWScript ::Misc::StringUtils::toLower (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, player.getClass().getNpcStats (player).getFactionReputation (factionId)+ value); @@ -911,7 +912,7 @@ namespace MWScript factionID = ptr.getClass().getPrimaryFaction(ptr); } ::Misc::StringUtils::toLower(factionID); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); @@ -942,7 +943,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { player.getClass().getNpcStats(player).expell(factionID); @@ -969,7 +970,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") player.getClass().getNpcStats(player).clearExpelled(factionID); } @@ -988,7 +989,7 @@ namespace MWScript if(factionID.empty()) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) @@ -1011,7 +1012,7 @@ namespace MWScript if(factionID.empty()) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) @@ -1120,7 +1121,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) ptr.getClass().getCreatureStats(ptr).resurrect(); else if (ptr.getClass().getCreatureStats(ptr).isDead()) { @@ -1159,10 +1160,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float currentValue = stats.getMagicEffects().get(mPositiveEffect).getMagnitude(); + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + float currentValue = effects.get(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) - currentValue -= stats.getMagicEffects().get(mNegativeEffect).getMagnitude(); + currentValue -= effects.get(mNegativeEffect).getMagnitude(); int ret = static_cast(currentValue); runtime.push(ret); @@ -1185,14 +1186,14 @@ namespace MWScript virtual void execute(Interpreter::Runtime &runtime) { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float currentValue = stats.getMagicEffects().get(mPositiveEffect).getMagnitude(); + MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + float currentValue = effects.get(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) - currentValue -= stats.getMagicEffects().get(mNegativeEffect).getMagnitude(); + currentValue -= effects.get(mNegativeEffect).getMagnitude(); int arg = runtime[0].mInteger; runtime.pop(); - stats.getMagicEffects().modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); + effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 0f9faa11a..679a8d2de 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -20,6 +20,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -214,7 +216,7 @@ namespace MWScript if (!ptr.isInCell()) return; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -289,7 +291,7 @@ namespace MWScript if (ptr.getContainerStore()) return; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -333,7 +335,7 @@ namespace MWScript // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. - if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); @@ -354,7 +356,7 @@ namespace MWScript if (!ptr.isInCell()) return; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -372,7 +374,7 @@ namespace MWScript // another morrowind oddity: player will be moved to the exterior cell at this location, // non-player actors will move within the cell they are in. - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); @@ -389,7 +391,7 @@ namespace MWScript // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. - if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); ptr.getClass().adjustPosition(ptr, false); @@ -469,7 +471,7 @@ namespace MWScript Interpreter::Type_Float zRot = runtime[0].mFloat; runtime.pop(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::CellStore* store = NULL; if (player.getCell()->isExterior()) { @@ -501,7 +503,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr actor = pc - ? MWBase::Environment::get().getWorld()->getPlayerPtr() + ? MWMechanics::getPlayer() : R()(runtime); std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp index 538133c65..165a93062 100644 --- a/apps/openmw/mwscript/userextensions.cpp +++ b/apps/openmw/mwscript/userextensions.cpp @@ -1,4 +1,3 @@ - #include "userextensions.hpp" #include diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 1f73fc1fc..bc97f1601 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -15,6 +15,8 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "sound_output.hpp" #include "sound_decoder.hpp" #include "sound.hpp" @@ -490,7 +492,7 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(snditer->second.first != MWWorld::Ptr() && - snditer->second.first != MWBase::Environment::get().getWorld()->getPlayerPtr() && + snditer->second.first != MWMechanics::getPlayer() && snditer->second.first.getCell() == cell) { snditer->first->stop(); @@ -735,7 +737,7 @@ namespace MWSound mListenerUp = up; MWWorld::Ptr player = - MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::getPlayer(); const MWWorld::CellStore *cell = player.getCell(); mListenerUnderwater = ((cell->getCell()->mData.mFlags&ESM::Cell::HasWater) && mListenerPos.z() < cell->getWaterLevel()); diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index fcd1ca19e..733ac1171 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -1,4 +1,3 @@ - #include "character.hpp" #include diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 70e9f0925..22192c355 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -1,4 +1,3 @@ - #include "charactermanager.hpp" #include diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index ac8dc863a..6a780f2ca 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "statemanagerimp.hpp" #include @@ -37,6 +36,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwscript/globalscripts.hpp" @@ -473,7 +473,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(); - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr ptr = MWMechanics::getPlayer(); ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); @@ -520,7 +520,7 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); std::string name = player.get()->mBase->mName; return mCharacterManager.getCurrentCharacter (create, name); @@ -607,7 +607,7 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, ostream); if (!result.success()) { - std::cerr << "Unable to write screenshot: " << result.message() << std::endl; + std::cerr << "Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; return; } diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 5e1fb41a6..6361b3404 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -1,4 +1,3 @@ - #include "action.hpp" #include "../mwbase/environment.hpp" @@ -6,6 +5,8 @@ #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + const MWWorld::Ptr& MWWorld::Action::getTarget() const { return mTarget; @@ -20,7 +21,7 @@ void MWWorld::Action::execute (const Ptr& actor) { if (!mSoundId.empty()) { - if (mKeepSound && actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mKeepSound && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Normal,mSoundOffset); else diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index bbba1081c..53ed1ad84 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -5,12 +5,16 @@ #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { void ActionAlchemy::executeImp (const Ptr& actor) { - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + if (actor != MWMechanics::getPlayer()) + return; + + if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}"); return; } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index bfd64c85d..e3699a6ac 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -1,4 +1,3 @@ - #include "actionapply.hpp" #include "class.hpp" @@ -8,6 +7,8 @@ #include "../mwworld/containerstore.hpp" +#include "../mwmechanics/actorutil.hpp" + namespace MWWorld { ActionApply::ActionApply (const Ptr& object, const std::string& id) @@ -33,7 +34,7 @@ namespace MWWorld { MWBase::Environment::get().getWorld()->breakInvisibility(actor); - if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1) + if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1 && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 660915523..82c7fb80e 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -1,8 +1,5 @@ - #include "actioneat.hpp" -#include - #include #include "../mwbase/environment.hpp" @@ -10,6 +7,8 @@ #include "../mwworld/containerstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "class.hpp" namespace MWWorld @@ -22,7 +21,7 @@ namespace MWWorld // apply to actor std::string id = getTarget().getClass().getId (getTarget()); - if (actor.getClass().apply (actor, id, actor)) + if (actor.getClass().apply (actor, id, actor) && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 87a4c6308..147f22963 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -4,6 +4,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include #include "inventorystore.hpp" @@ -24,7 +26,7 @@ namespace MWWorld 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()->getPlayerPtr()) + if (!result.second.empty() && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(result.second); switch(result.first) diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 8578995ae..454cc09f1 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONOPEN_H #define GAME_MWWORLD_ACTIONOPEN_H diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index cd0471a0e..90e9a375b 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -5,6 +5,7 @@ #include "../mwbase/world.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "player.hpp" #include "class.hpp" @@ -18,8 +19,11 @@ namespace MWWorld void ActionRead::executeImp (const MWWorld::Ptr& actor) { + if (actor != MWMechanics::getPlayer()) + return; + //Ensure we're not in combat - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat() + if(MWMechanics::isPlayerInCombat() // Reading in combat is still allowed if the scroll/book is not in the player inventory yet // (since otherwise, there would be no way to pick it up) && getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor) diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index 699440a01..8e19927b8 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -4,6 +4,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { @@ -14,7 +15,10 @@ namespace MWWorld void ActionRepair::executeImp (const Ptr& actor) { - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + if (actor != MWMechanics::getPlayer()) + return; + + if(MWMechanics::isPlayerInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}"); return; } diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index 7237fd334..98fe8ee34 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { @@ -16,7 +17,10 @@ namespace MWWorld void ActionSoulgem::executeImp(const Ptr &actor) { - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + if (actor != MWMechanics::getPlayer()) + return; + + if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage5}"); return; } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 269d941dc..4e6135764 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -1,4 +1,3 @@ - #include "actiontake.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 905497f85..051380ff5 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,4 +1,3 @@ - #include "actiontalk.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index f879343d9..f88bf0958 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -5,13 +5,32 @@ #include #include #include +#include + #include #include "livecellref.hpp" #include "cellreflist.hpp" -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 5ec2d4e16..4c73c603b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -1,4 +1,3 @@ - #include "class.hpp" #include @@ -109,11 +108,6 @@ namespace MWWorld throw std::runtime_error("class cannot be hit"); } - void Class::setActorHealth(const Ptr& ptr, float health, const Ptr& attacker) const - { - throw std::runtime_error("class does not have actor health"); - } - boost::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { return boost::shared_ptr (new NullAction); @@ -149,6 +143,11 @@ namespace MWWorld throw std::runtime_error ("class does not support unlocking"); } + bool Class::canLock(const Ptr &ptr) const + { + return false; + } + void Class::setRemainingUsageTime (const Ptr& ptr, float duration) const { throw std::runtime_error ("class does not support time-based uses"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 7ef173555..1157db670 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -136,12 +136,6 @@ namespace MWWorld ///< 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 - /// correct dialog and actor states are properly handled when being hurt or healed. - /// (default implementation: throw an exception) - virtual boost::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). @@ -167,6 +161,8 @@ namespace MWWorld virtual void unlock (const Ptr& ptr) const; ///< Unlock object (default implementation: throw an exception) + virtual bool canLock (const Ptr& ptr) const; + 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) @@ -284,6 +280,8 @@ namespace MWWorld virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; } + virtual bool isGold(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; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d4aadc6c7..8c6f7d259 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,4 +1,3 @@ - #include "containerstore.hpp" #include @@ -12,6 +11,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/levelledlist.hpp" +#include "../mwmechanics/actorutil.hpp" #include "manualref.hpp" #include "refdata.hpp" @@ -210,7 +210,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string & { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); // a bit pointless to set owner for the player - if (actorPtr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actorPtr != MWMechanics::getPlayer()) return add(ref.getPtr(), count, actorPtr, true); else return add(ref.getPtr(), count, actorPtr, false); @@ -225,7 +225,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr // HACK: Set owner on the original item, then reset it after we have copied it // If we set the owner on the copied item, it would not stack correctly... std::string oldOwner = itemPtr.getCellRef().getOwner(); - if (!setOwner || actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) // No point in setting owner to the player - NPCs will not respect this anyway + if (!setOwner || actorPtr == MWMechanics::getPlayer()) // No point in setting owner to the player - NPCs will not respect this anyway { itemPtr.getCellRef().setOwner(""); } @@ -298,11 +298,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold) - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100")) + if(ptr.getClass().isGold(ptr)) { int realCount = count * ptr.getClass().getValue(ptr); diff --git a/apps/openmw/mwworld/failedaction.cpp b/apps/openmw/mwworld/failedaction.cpp index cf5a2a5c2..49ca9dae0 100644 --- a/apps/openmw/mwworld/failedaction.cpp +++ b/apps/openmw/mwworld/failedaction.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { @@ -13,7 +14,7 @@ namespace MWWorld void FailedAction::executeImp(const Ptr &actor) { - if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && !mMessage.empty()) + if(actor == MWMechanics::getPlayer() && !mMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(mMessage); } } diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index e7cb04590..dcd7924a2 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -1,4 +1,3 @@ - #include "globals.hpp" #include diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 6c283bb3e..0755c1555 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -1,4 +1,3 @@ - #include "inventorystore.hpp" #include @@ -17,6 +16,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "esmstore.hpp" @@ -137,7 +137,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, setOwner); // Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves - if (actorPtr != MWBase::Environment::get().getWorld()->getPlayerPtr() + if (actorPtr != MWMechanics::getPlayer() && !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())) { std::string type = itemPtr.getTypeName(); @@ -490,6 +490,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor { int retCount = ContainerStore::remove(item, count, actor); + bool wasEquipped = false; if (!item.getRefData().getCount()) { for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) @@ -500,6 +501,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (*mSlots[slot] == item) { unequipSlot(slot, actor); + wasEquipped = true; break; } } @@ -507,7 +509,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor // If an armor/clothing item is removed, try to find a replacement, // but not for the player nor werewolves. - if ((actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (wasEquipped && (actor != MWMechanics::getPlayer()) && !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())) { std::string type = item.getTypeName(); @@ -539,7 +541,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c { retval = restack(*it); - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actor == MWMechanics::getPlayer()) { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared const std::string& script = it->getClass().getScript(*it); @@ -594,7 +596,7 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) // if player, update inventory window /* - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actor == MWMechanics::getPlayer()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index a60d1f464..95c16c115 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -87,7 +87,6 @@ namespace MWWorld float mMultiplier; }; - // TODO: store in savegame typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index b926c5799..d6e40ad09 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -1,7 +1,6 @@ #include "manualref.hpp" #include "esmstore.hpp" -#include "cellstore.hpp" namespace { diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index b17b8e1f0..6bfdd2986 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -1,7 +1,7 @@ - #include "player.hpp" #include +#include #include #include @@ -19,11 +19,9 @@ #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/actors.hpp" #include "class.hpp" #include "ptr.hpp" -#include "inventorystore.hpp" #include "cellstore.hpp" namespace MWWorld @@ -327,6 +325,7 @@ namespace MWWorld } catch (...) { + std::cerr << "Player cell '" << player.mCellId.mWorldspace << "' no longer exists" << std::endl; // Cell no longer exists. Place the player in a default cell. ESM::Position pos = mPlayer.mData.getPosition(); MWBase::Environment::get().getWorld()->indexToPosition(0, 0, pos.pos[0], pos.pos[1], true); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6295ed159..3e9f17278 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -20,6 +20,7 @@ #include "../mwmechanics/combat.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwrender/effectmanager.hpp" #include "../mwrender/animation.hpp" @@ -187,7 +188,7 @@ namespace MWWorld } // Explodes when hitting water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), newPos)) + if (MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos)) hit = true; if (hit) diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 1cf22744a..4d74c3c58 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -1,4 +1,3 @@ - #include "ptr.hpp" #include diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index c4f63137a..88a6fa353 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -1,4 +1,3 @@ - #include "refdata.hpp" #include @@ -15,7 +14,6 @@ namespace MWWorld { mBaseNode = refData.mBaseNode; mLocals = refData.mLocals; - mHasLocals = refData.mHasLocals; mEnabled = refData.mEnabled; mCount = refData.mCount; mPosition = refData.mPosition; @@ -35,7 +33,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mDeleted(false), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mDeleted(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) { for (int i=0; i<3; ++i) { @@ -46,7 +44,7 @@ namespace MWWorld } RefData::RefData (const ESM::CellRef& cellRef) - : mBaseNode(0), mDeleted(false), mHasLocals (false), mEnabled (true), + : mBaseNode(0), mDeleted(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), mChanged(false) // Loading from ESM/ESP files -> assume unchanged @@ -57,13 +55,13 @@ namespace MWWorld } RefData::RefData (const ESM::ObjectState& objectState) - : mBaseNode(0), mDeleted(false), mHasLocals (false), + : mBaseNode(0), mDeleted(false), mEnabled (objectState.mEnabled != 0), mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), mChanged(true) // Loading from a savegame -> assume changed - { + { for (int i=0; i<3; ++i) mLocalRotation.rot[i] = objectState.mLocalRotation[i]; } @@ -84,10 +82,7 @@ namespace MWWorld void RefData::write (ESM::ObjectState& objectState, const std::string& scriptId) const { - objectState.mHasLocals = mHasLocals; - - if (mHasLocals) - mLocals.write (objectState.mLocals, scriptId); + objectState.mHasLocals = mLocals.write (objectState.mLocals, scriptId); objectState.mEnabled = mEnabled; objectState.mCount = mCount; @@ -140,13 +135,8 @@ namespace MWWorld void RefData::setLocals (const ESM::Script& script) { - if (!mHasLocals) - { - mLocals.configure (script); - mHasLocals = true; - if (!mLocals.isEmpty()) - mChanged = true; - } + if (mLocals.configure (script) && !mLocals.isEmpty()) + mChanged = true; } void RefData::setCount (int count) diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 61055aa73..4075f62ce 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -31,11 +31,9 @@ namespace MWWorld { osg::PositionAttitudeTransform* mBaseNode; - MWScript::Locals mLocals; // if we find the overhead of heaving a locals - // object in the refdata of refs without a script, - // we can make this a pointer later. + MWScript::Locals mLocals; + bool mDeleted; // separate delete flag used for deletion by a content file - bool mHasLocals; bool mEnabled; int mCount; // 0: deleted diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 8029cb773..459b3b6ca 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1,15 +1,12 @@ #include "scene.hpp" #include +#include -#include #include #include #include #include -#include - -#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -236,7 +233,7 @@ namespace MWWorld if(result.second) { - std::cout << "loading cell " << cell->getCell()->getDescription() << std::endl; + std::cout << "Loading cell " << cell->getCell()->getDescription() << std::endl; float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; @@ -253,9 +250,9 @@ namespace MWWorld // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. // Load everything now to reduce IO overhead. const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(flags)) - land->loadData(flags); - mPhysics->addHeightField (land->mLandData->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), + + const ESM::Land::LandData *data = land->getLandData (flags); + mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts); } } @@ -327,7 +324,7 @@ namespace MWWorld std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); - const int halfGridSize = Settings::Manager::getInt("exterior grid size", "Cells")/2; + const int halfGridSize = Settings::Manager::getInt("exterior cell load distance", "Cells"); CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 96c711896..b647a6c88 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1,5 +1,4 @@ #include "store.hpp" -#include "esmstore.hpp" #include #include @@ -995,7 +994,7 @@ namespace MWWorld template<> - inline void Store::setUp() + void Store::setUp() { // DialInfos marked as deleted are kept during the loading phase, so that the linked list // structure is kept intact for inserting further INFOs. Delete them now that loading is done. @@ -1014,7 +1013,7 @@ namespace MWWorld } template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { + void Store::load(ESM::ESMReader &esm, const std::string &id) { std::string idLower = Misc::StringUtils::lowerCase(id); std::map::iterator it = mStatic.find(idLower); @@ -1030,7 +1029,6 @@ namespace MWWorld // Script //========================================================================= - template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { ESM::Script scpt; scpt.load(esm); @@ -1047,7 +1045,6 @@ namespace MWWorld // StartScript //========================================================================= - template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { ESM::StartScript s; @@ -1064,11 +1061,11 @@ namespace MWWorld template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; @@ -1082,21 +1079,21 @@ template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 02fb983cd..47c87f742 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -170,6 +170,7 @@ namespace MWWorld size_t getSize() const; int getDynamicSize() const; + /// @note The record identifiers are listed in the order that the records were defined by the content files. void listIdentifier(std::vector &list) const; T *insert(const T &item); @@ -319,7 +320,7 @@ namespace MWWorld public: - Store(); + Store(); void setCells(Store& cells); void load(ESM::ESMReader &esm, const std::string &id); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index f51cfd59b..5008d8fbf 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1,17 +1,18 @@ -#define _USE_MATH_DEFINES -#include - #include "weather.hpp" #include +#include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwsound/sound.hpp" #include "../mwrender/renderingmanager.hpp" @@ -22,10 +23,14 @@ #include "fallback.hpp" #include "cellstore.hpp" +#include + using namespace MWWorld; namespace { + static const int invalidWeatherID = -1; + float lerp (float x, float y, float factor) { return x * (1-factor) + y * factor; @@ -37,157 +42,441 @@ namespace } } -void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name) +Weather::Weather(const std::string& name, + const MWWorld::Fallback& fallback, + float stormWindSpeed, + float rainSpeed, + const std::string& ambientLoopSoundID, + const std::string& particleEffect) + : mCloudTexture(fallback.getFallbackString("Weather_" + name + "_Cloud_Texture")) + , mSkySunriseColor(fallback.getFallbackColour("Weather_" + name +"_Sky_Sunrise_Color")) + , mSkyDayColor(fallback.getFallbackColour("Weather_" + name + "_Sky_Day_Color")) + , mSkySunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sky_Sunset_Color")) + , mSkyNightColor(fallback.getFallbackColour("Weather_" + name + "_Sky_Night_Color")) + , mFogSunriseColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Sunrise_Color")) + , mFogDayColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Day_Color")) + , mFogSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Sunset_Color")) + , mFogNightColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Night_Color")) + , mAmbientSunriseColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunrise_Color")) + , mAmbientDayColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Day_Color")) + , mAmbientSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunset_Color")) + , mAmbientNightColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Night_Color")) + , mSunSunriseColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Sunrise_Color")) + , mSunDayColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Day_Color")) + , mSunSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Sunset_Color")) + , mSunNightColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Night_Color")) + , mLandFogDayDepth(fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth")) + , mLandFogNightDepth(fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Night_Depth")) + , mSunDiscSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) + , mWindSpeed(fallback.getFallbackFloat("Weather_" + name + "_Wind_Speed")) + , mCloudSpeed(fallback.getFallbackFloat("Weather_" + name + "_Cloud_Speed")) + , mGlareView(fallback.getFallbackFloat("Weather_" + name + "_Glare_View")) + , mAmbientLoopSoundID(ambientLoopSoundID) + , mIsStorm(mWindSpeed > stormWindSpeed) + , mRainSpeed(rainSpeed) + , mRainFrequency(fallback.getFallbackFloat("Weather_" + name + "_Rain_Entrance_Speed")) + , mParticleEffect(particleEffect) + , mRainEffect(fallback.getFallbackBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") + , mTransitionDelta(fallback.getFallbackFloat("Weather_" + name + "_Transition_Delta")) + , mCloudsMaximumPercent(fallback.getFallbackFloat("Weather_" + name + "_Clouds_Maximum_Percent")) + , mThunderFrequency(fallback.getFallbackFloat("Weather_" + name + "_Thunder_Frequency")) + , mThunderThreshold(fallback.getFallbackFloat("Weather_" + name + "_Thunder_Threshold")) + , mThunderSoundID() + , mFlashDecrement(fallback.getFallbackFloat("Weather_" + name + "_Flash_Decrement")) + , mFlashBrightness(0.0f) { - std::string upper=name; - upper[0]=toupper(name[0]); - weather.mCloudsMaximumPercent = mFallback->getFallbackFloat("Weather_"+upper+"_Clouds_Maximum_Percent"); - weather.mTransitionDelta = mFallback->getFallbackFloat("Weather_"+upper+"_Transition_Delta"); - weather.mSkySunriseColor=mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunrise_Color"); - weather.mSkyDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Day_Color"); - weather.mSkySunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunset_Color"); - weather.mSkyNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Night_Color"); - weather.mFogSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunrise_Color"); - weather.mFogDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Day_Color"); - weather.mFogSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunset_Color"); - weather.mFogNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Night_Color"); - weather.mAmbientSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunrise_Color"); - weather.mAmbientDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Day_Color"); - weather.mAmbientSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunset_Color"); - weather.mAmbientNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Night_Color"); - weather.mSunSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunrise_Color"); - weather.mSunDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Day_Color"); - weather.mSunSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunset_Color"); - weather.mSunNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Night_Color"); - weather.mSunDiscSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Disc_Sunset_Color"); - weather.mLandFogDayDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Day_Depth"); - weather.mLandFogNightDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Night_Depth"); - weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed"); - weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed"); - weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View"); - weather.mCloudTexture = mFallback->getFallbackString("Weather_"+upper+"_Cloud_Texture"); - - bool usesPrecip = mFallback->getFallbackBool("Weather_"+upper+"_Using_Precip"); - if (usesPrecip) - weather.mRainEffect = "meshes\\raindrop.nif"; - weather.mRainSpeed = mRainSpeed; - weather.mRainFrequency = mFallback->getFallbackFloat("Weather_"+upper+"_Rain_Entrance_Speed"); + mThunderSoundID[0] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_0"); + mThunderSoundID[1] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_1"); + mThunderSoundID[2] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_2"); + mThunderSoundID[3] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_3"); /* -Unhandled: -Rain Diameter=600 ? -Rain Height Min=200 ? -Rain Height Max=700 ? -Rain Threshold=0.6 ? -Max Raindrops=650 ? -*/ - weather.mIsStorm = (name == "ashstorm" || name == "blight"); - - mWeatherSettings[name] = weather; + Unhandled: + Rain Diameter=600 ? + Rain Height Min=200 ? + Rain Height Max=700 ? + Rain Threshold=0.6 ? + Max Raindrops=650 ? + */ } - -float WeatherManager::calculateHourFade (const std::string& moonName) const +float Weather::transitionDelta() const { - float fadeOutStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Start"); - float fadeOutFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Finish"); - float fadeInStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Start"); - float fadeInFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Finish"); + // Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the + // measurement is in real time, not in-game time. + return mTransitionDelta; +} - if (mHour >= fadeOutStart && mHour <= fadeOutFinish) - return (1 - ((mHour - fadeOutStart) / (fadeOutFinish - fadeOutStart))); - if (mHour >= fadeInStart && mHour <= fadeInFinish) - return (1 - ((mHour - fadeInStart) / (fadeInFinish - fadeInStart))); +float Weather::cloudBlendFactor(const float transitionRatio) const +{ + // Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next. + return transitionRatio / mCloudsMaximumPercent; +} + +float Weather::calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused) +{ + // When paused, the flash brightness remains the same and no new strikes can occur. + if(!isPaused) + { + // Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold. + if(transitionRatio >= mThunderThreshold && mThunderFrequency > 0.0f) + { + flashDecrement(elapsedSeconds); + + if(Misc::Rng::rollProbability() <= thunderChance(transitionRatio, elapsedSeconds)) + { + lightningAndThunder(); + } + } + else + { + mFlashBrightness = 0.0f; + } + } + + return mFlashBrightness; +} + +inline void Weather::flashDecrement(const float elapsedSeconds) +{ + // The Flash Decrement is measured in whole units per second. This means that if the flash brightness was + // currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum). + float decrement = mFlashDecrement * elapsedSeconds; + mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement; +} + +inline float Weather::thunderChance(const float transitionRatio, const float elapsedSeconds) const +{ + // This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes + // per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of + // Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to + // scaled based on how far past it is past the Thunder Threshold. + float scaleFactor = (transitionRatio - mThunderThreshold) / (1.0f - mThunderThreshold); + return ((mThunderFrequency * 10.0f) / 60.0f) * elapsedSeconds * scaleFactor; +} + +inline void Weather::lightningAndThunder(void) +{ + // Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects. + // They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance + // was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0. + // TODO: Determine the distribution of each distance to see if it's evenly weighted. + unsigned int distance = Misc::Rng::rollDice(4); + // Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0. + mFlashBrightness += 1 - (distance * 0.25f); + MWBase::Environment::get().getSoundManager()->playSound(mThunderSoundID[distance], 1.0, 1.0); +} + +RegionWeather::RegionWeather(const ESM::Region& region) + : mWeather(invalidWeatherID) + , mChances() +{ + mChances.reserve(10); + mChances.push_back(region.mData.mClear); + mChances.push_back(region.mData.mCloudy); + mChances.push_back(region.mData.mFoggy); + mChances.push_back(region.mData.mOvercast); + mChances.push_back(region.mData.mRain); + mChances.push_back(region.mData.mThunder); + mChances.push_back(region.mData.mAsh); + mChances.push_back(region.mData.mBlight); + mChances.push_back(region.mData.mA); + mChances.push_back(region.mData.mB); +} + +RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) + : mWeather(state.mWeather) + , mChances(state.mChances) +{ +} + +RegionWeather::operator ESM::RegionWeatherState() const +{ + ESM::RegionWeatherState state = + { + mWeather, + mChances + }; + + return state; +} + +void RegionWeather::setChances(const std::vector& chances) +{ + if(mChances.size() < chances.size()) + { + mChances.reserve(chances.size()); + } + + std::vector::const_iterator it = chances.begin(); + for(size_t i = 0; it != chances.end(); ++it, ++i) + { + mChances[i] = *it; + } + + // Regional weather no longer supports the current type, select a new weather pattern. + if((static_cast(mWeather) >= mChances.size()) || (mChances[mWeather] == 0)) + { + chooseNewWeather(); + } +} + +void RegionWeather::setWeather(int weatherID) +{ + mWeather = weatherID; +} + +int RegionWeather::getWeather() +{ + // If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value. + // Note that the region weather will be expired periodically when the weather update timer expires. + if(mWeather == invalidWeatherID) + { + chooseNewWeather(); + } + + return mWeather; +} + +void RegionWeather::chooseNewWeather() +{ + // All probabilities must add to 100 (responsibility of the user). + // If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30 + // and 70% will be greater than 30 (in theory). + int chance = Misc::Rng::rollDice(100) + 1; // 1..100 + int sum = 0; + int i = 0; + for(; static_cast(i) < mChances.size(); ++i) + { + sum += mChances[i]; + if(chance <= sum) + break; + } + + mWeather = i; +} + +MoonModel::MoonModel(const std::string& name, const MWWorld::Fallback& fallback) + : mFadeInStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Start")) + , mFadeInFinish(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Finish")) + , mFadeOutStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_Out_Start")) + , mFadeOutFinish(fallback.getFallbackFloat("Moons_" + name + "_Fade_Out_Finish")) + , mAxisOffset(fallback.getFallbackFloat("Moons_" + name + "_Axis_Offset")) + , mSpeed(fallback.getFallbackFloat("Moons_" + name + "_Speed")) + , mDailyIncrement(fallback.getFallbackFloat("Moons_" + name + "_Daily_Increment")) + , mFadeStartAngle(fallback.getFallbackFloat("Moons_" + name + "_Fade_Start_Angle")) + , mFadeEndAngle(fallback.getFallbackFloat("Moons_" + name + "_Fade_End_Angle")) + , mMoonShadowEarlyFadeAngle(fallback.getFallbackFloat("Moons_" + name + "_Moon_Shadow_Early_Fade_Angle")) +{ + // Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably + // complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering. + mSpeed = std::min(mSpeed, 180.0f / 23.0f); +} + +MWRender::MoonState MoonModel::calculateState(const TimeStamp& gameTime) const +{ + float rotationFromHorizon = angle(gameTime); + MWRender::MoonState state = + { + rotationFromHorizon, + mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices. + static_cast(phase(gameTime)), + shadowBlend(rotationFromHorizon), + earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameTime.getHour()) + }; + + return state; +} + +inline float MoonModel::angle(const TimeStamp& gameTime) const +{ + // Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the + // opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise. + + // When calculating the angle of the moon, several cases have to be taken into account: + // 1. Moon rises and then sets in one day. + // 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24). + // 3. Moon sets and then rises in one day. + float moonRiseHourToday = moonRiseHour(gameTime.getDay()); + float moonRiseAngleToday = 0; + + if(gameTime.getHour() < moonRiseHourToday) + { + float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1); + if(moonRiseHourYesterday < 24) + { + float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday); + if(moonRiseAngleYesterday < 180) + { + // The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today. + moonRiseAngleToday = rotation(gameTime.getHour()) + moonRiseAngleYesterday; + } + } + } else - return 1; + { + moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); + } + + if(moonRiseAngleToday >= 180) + { + // The moon set today, reset the angle to the horizon. + moonRiseAngleToday = 0; + } + + return moonRiseAngleToday; } -float WeatherManager::calculateAngleFade (const std::string& moonName, float angle) const +inline float MoonModel::moonRiseHour(unsigned int daysPassed) const { - float endAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_End_Angle"); - float startAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Start_Angle"); - if (angle <= startAngle && angle >= endAngle) - return (1 - ((angle - endAngle)/(startAngle-endAngle))); - else if (angle > startAngle) - return 0.f; + // This arises from the start date of 16 Last Seed, 427 + // TODO: Find an alternate formula that doesn't rely on this day being fixed. + static const unsigned int startDay = 16; + + // This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning + // that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed. + // Note that we don't modulo after adding the latest daily increment because other calculations need to + // know if doing so would cause the moon rise to be postponed until the next day (which happens when + // the moon rise hour is >= 24 in Morrowind). + return mDailyIncrement + std::fmod((daysPassed - 1 + startDay) * mDailyIncrement, 24.0f); +} + +inline float MoonModel::rotation(float hours) const +{ + // 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph. + // Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure + // of whole rotations that could be completed in a day. + return 15.0f * mSpeed * hours; +} + +inline unsigned int MoonModel::phase(const TimeStamp& gameTime) const +{ + // Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle. + // Note: this is an internal helper, and as such we don't want to return MWRender::MoonState::Phase since we can't + // forward declare it (C++11 strongly typed enums solve this). + + // If the moon didn't rise yet today, use yesterday's moon phase. + if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) + return (gameTime.getDay() / 3) % 8; else - return 1.f; + return ((gameTime.getDay() + 1) / 3) % 8; } -WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fallback* fallback) : - mHour(14), mWindSpeed(0.f), mIsStorm(false), mStormDirection(0,1,0), mFallback(fallback), - mRendering(rendering), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true), - mRemainingTransitionTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), - mTimePassed(0), mWeatherUpdateTime(0), mThunderSoundDelay(0) +inline float MoonModel::shadowBlend(float angle) const { - //Globals - mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0"); - mThunderSoundID1 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_1"); - mThunderSoundID2 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_2"); - mThunderSoundID3 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_3"); - mSunriseTime = mFallback->getFallbackFloat("Weather_Sunrise_Time"); - mSunsetTime = mFallback->getFallbackFloat("Weather_Sunset_Time"); - mSunriseDuration = mFallback->getFallbackFloat("Weather_Sunrise_Duration"); - mSunsetDuration = mFallback->getFallbackFloat("Weather_Sunset_Duration"); - mHoursBetweenWeatherChanges = mFallback->getFallbackFloat("Weather_Hours_Between_Weather_Changes"); - mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; - mThunderFrequency = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Frequency"); - mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold"); - mThunderSoundDelay = 0.25; + // The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk + // that is roughly the color of the sky, to a textured surface. + // Depending on the current angle, the following values describe the ratio between the textured moon + // and the solid disk: + // 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1 + // 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured) + // 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0 + // 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk) + float fadeAngle = mFadeStartAngle - mFadeEndAngle; + float fadeEndAngle2 = 180.0f - mFadeEndAngle; + float fadeStartAngle2 = 180.0f - mFadeStartAngle; + if((angle >= mFadeEndAngle) && (angle < mFadeStartAngle)) + return (angle - mFadeEndAngle) / fadeAngle; + else if((angle >= mFadeStartAngle) && (angle < fadeStartAngle2)) + return 1.0f; + else if((angle >= fadeStartAngle2) && (angle < fadeEndAngle2)) + return (fadeEndAngle2 - angle) / fadeAngle; + else + return 0.0f; +} - mRainSpeed = mFallback->getFallbackFloat("Weather_Precip_Gravity"); +inline float MoonModel::hourlyAlpha(float gameHour) const +{ + // The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon + // appears and disappears. + // Depending on the current hour, the following values describe how transparent the moon is. + // 1. From Fade Out Start to Fade Out Finish: 1..0 + // 2. From Fade Out Finish to Fade In Start: 0 (transparent) + // 3. From Fade In Start to Fade In Finish: 0..1 + // 4. From Fade In Finish to Fade Out Start: 1 (solid) + if((gameHour >= mFadeOutStart) && (gameHour < mFadeOutFinish)) + return (mFadeOutFinish - gameHour) / (mFadeOutFinish - mFadeOutStart); + else if((gameHour >= mFadeOutFinish) && (gameHour < mFadeInStart)) + return 0.0f; + else if((gameHour >= mFadeInStart) && (gameHour < mFadeInFinish)) + return (gameHour - mFadeInStart) / (mFadeInFinish - mFadeInStart); + else + return 1.0f; +} - //Some useful values - /* TODO: Use pre-sunrise_time, pre-sunset_time, - * post-sunrise_time, and post-sunset_time to better - * describe sunrise/sunset time. - * These values are fallbacks attached to weather. - */ - mNightStart = mSunsetTime + mSunsetDuration; - mNightEnd = mSunriseTime - 0.5f; - mDayStart = mSunriseTime + mSunriseDuration; - mDayEnd = mSunsetTime; +inline float MoonModel::earlyMoonShadowAlpha(float angle) const +{ + // The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle. + // Depending on the current angle, the following values describe how transparent the moon is. + // 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1 + // 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid) + // 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0 + // 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent) + float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle; + float fadeEndAngle2 = 180.0f - mFadeEndAngle; + float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle; + if((angle >= moonShadowEarlyFadeAngle1) && (angle < mFadeEndAngle)) + return (angle - moonShadowEarlyFadeAngle1) / mMoonShadowEarlyFadeAngle; + else if((angle >= mFadeEndAngle) && (angle < fadeEndAngle2)) + return 1.0f; + else if((angle >= fadeEndAngle2) && (angle < moonShadowEarlyFadeAngle2)) + return (moonShadowEarlyFadeAngle2 - angle) / mMoonShadowEarlyFadeAngle; + else + return 0.0f; +} - //Weather - Weather clear; - setFallbackWeather(clear,"clear"); +WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWorld::Fallback& fallback, MWWorld::ESMStore& store) + : mStore(store) + , mRendering(rendering) + , mSunriseTime(fallback.getFallbackFloat("Weather_Sunrise_Time")) + , mSunsetTime(fallback.getFallbackFloat("Weather_Sunset_Time")) + , mSunriseDuration(fallback.getFallbackFloat("Weather_Sunrise_Duration")) + , mSunsetDuration(fallback.getFallbackFloat("Weather_Sunset_Duration")) + , mSunPreSunsetTime(fallback.getFallbackFloat("Weather_Sun_Pre-Sunset_Time")) + , mNightStart(mSunsetTime + mSunsetDuration) + , mNightEnd(mSunriseTime - 0.5f) + , mDayStart(mSunriseTime + mSunriseDuration) + , mDayEnd(mSunsetTime) + , mHoursBetweenWeatherChanges(fallback.getFallbackFloat("Weather_Hours_Between_Weather_Changes")) + , mRainSpeed(fallback.getFallbackFloat("Weather_Precip_Gravity")) + , mWeatherSettings() + , mMasser("Masser", fallback) + , mSecunda("Secunda", fallback) + , mWindSpeed(0.f) + , mIsStorm(false) + , mStormDirection(0,1,0) + , mCurrentRegion() + , mTimePassed(0) + , mFastForward(false) + , mWeatherUpdateTime(mHoursBetweenWeatherChanges) + , mTransitionFactor(0) + , mCurrentWeather(0) + , mNextWeather(0) + , mQueuedWeather(0) + , mRegions() + , mResult() + , mAmbientSound() + , mPlayingSoundID() +{ + mWeatherSettings.reserve(10); + addWeather("Clear", fallback); // 0 + addWeather("Cloudy", fallback); // 1 + addWeather("Foggy", fallback); // 2 + addWeather("Overcast", fallback); // 3 + addWeather("Rain", fallback, "rain"); // 4 + addWeather("Thunderstorm", fallback, "rain heavy"); // 5 + addWeather("Ashstorm", fallback, "ashstorm", "meshes\\ashcloud.nif"); // 6 + addWeather("Blight", fallback, "blight", "meshes\\blightcloud.nif"); // 7 + addWeather("Snow", fallback, "", "meshes\\snow.nif"); // 8 + addWeather("Blizzard", fallback, "BM Blizzard", "meshes\\blizzard.nif"); // 9 - Weather cloudy; - setFallbackWeather(cloudy,"cloudy"); + Store::iterator it = store.get().begin(); + for(; it != store.get().end(); ++it) + { + std::string regionID = Misc::StringUtils::lowerCase(it->mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + } - Weather foggy; - setFallbackWeather(foggy,"foggy"); - - Weather thunderstorm; - thunderstorm.mAmbientLoopSoundID = "rain heavy"; - thunderstorm.mRainEffect = "meshes\\raindrop.nif"; - setFallbackWeather(thunderstorm,"thunderstorm"); - - Weather rain; - rain.mAmbientLoopSoundID = "rain"; - rain.mRainEffect = "meshes\\raindrop.nif"; - setFallbackWeather(rain,"rain"); - - Weather overcast; - setFallbackWeather(overcast,"overcast"); - - Weather ashstorm; - ashstorm.mAmbientLoopSoundID = "ashstorm"; - ashstorm.mParticleEffect = "meshes\\ashcloud.nif"; - setFallbackWeather(ashstorm,"ashstorm"); - - Weather blight; - blight.mAmbientLoopSoundID = "blight"; - blight.mParticleEffect = "meshes\\blightcloud.nif"; - setFallbackWeather(blight,"blight"); - - Weather snow; - snow.mParticleEffect = "meshes\\snow.nif"; - setFallbackWeather(snow, "snow"); - - Weather blizzard; - blizzard.mAmbientLoopSoundID = "BM Blizzard"; - blizzard.mParticleEffect = "meshes\\blizzard.nif"; - setFallbackWeather(blizzard,"blizzard"); + forceWeather(0); } WeatherManager::~WeatherManager() @@ -195,48 +484,488 @@ WeatherManager::~WeatherManager() stopSounds(); } -void WeatherManager::setWeather(const std::string& weather, bool instant) +void WeatherManager::changeWeather(const std::string& regionID, const unsigned int weatherID) { - if (weather == mCurrentWeather && mNextWeather == "") + // In Morrowind, this seems to have the following behavior, when applied to the current region: + // - When there is no transition in progress, start transitioning to the new weather. + // - If there is a transition in progress, queue up the transition and process it when the current one completes. + // - If there is a transition in progress, and a queued transition, overwrite the queued transition. + // - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used, + // meaning that if there was no transition in progress, only the last ChangeWeather will be processed. + // If the region isn't current, Morrowind will store the new weather for the region in question. + + if(weatherID < mWeatherSettings.size()) { - mFirstUpdate = false; + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); + if(it != mRegions.end()) + { + it->second.setWeather(weatherID); + regionalWeatherChanged(it->first, it->second); + } + } +} + +void WeatherManager::modRegion(const std::string& regionID, const std::vector& chances) +{ + // Sets the region's probability for various weather patterns. Note that this appears to be saved permanently. + // In Morrowind, this seems to have the following behavior when applied to the current region: + // - If the region supports the current weather, no change in current weather occurs. + // - If the region no longer supports the current weather, and there is no transition in progress, begin to + // transition to a new supported weather type. + // - If the region no longer supports the current weather, and there is a transition in progress, queue a + // transition to a new supported weather type. + + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); + if(it != mRegions.end()) + { + it->second.setChances(chances); + regionalWeatherChanged(it->first, it->second); + } +} + +void WeatherManager::playerTeleported() +{ + // If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to + // be changed immediately, and any transitions for the previous region discarded. + MWBase::World* world = MWBase::Environment::get().getWorld(); + if(world->isCellExterior() || world->isCellQuasiExterior()) + { + std::string playerRegion = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->getCell()->mRegion); + std::map::iterator it = mRegions.find(playerRegion); + if(it != mRegions.end() && playerRegion != mCurrentRegion) + { + mCurrentRegion = playerRegion; + forceWeather(it->second.getWeather()); + } + } +} + +void WeatherManager::update(float duration, bool paused) +{ + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::World& world = *MWBase::Environment::get().getWorld(); + TimeStamp time = world.getTimeStamp(); + + if(!paused) + { + // Add new transitions when either the player's current external region changes. + std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); + if(updateWeatherTime() || updateWeatherRegion(playerRegion)) + { + std::map::iterator it = mRegions.find(mCurrentRegion); + if(it != mRegions.end()) + { + addWeatherTransition(it->second.getWeather()); + } + } + + updateWeatherTransitions(duration); + } + + const bool exterior = (world.isCellExterior() || world.isCellQuasiExterior()); + if(!exterior) + { + mRendering.setSkyEnabled(false); + stopSounds(); return; } - if (instant || mFirstUpdate) + calculateWeatherResult(time.getHour(), duration, paused); + + mWindSpeed = mResult.mWindSpeed; + mIsStorm = mResult.mIsStorm; + + if (mIsStorm) { - mNextWeather = ""; - mCurrentWeather = weather; + osg::Vec3f playerPos (player.getRefData().getPosition().asVec3()); + osg::Vec3f redMountainPos (19950, 72032, 27831); + + mStormDirection = (playerPos - redMountainPos); + mStormDirection.z() = 0; + mStormDirection.normalize(); + mRendering.getSkyManager()->setStormDirection(mStormDirection); + } + + // disable sun during night + if (time.getHour() >= mNightStart || time.getHour() <= mSunriseTime) + mRendering.getSkyManager()->sunDisable(); + else + mRendering.getSkyManager()->sunEnable(); + + // Update the sun direction. Run it east to west at a fixed angle from overhead. + // The sun's speed at day and night may differ, since mSunriseTime and mNightStart + // mark when the sun is level with the horizon. + { + // Shift times into a 24-hour window beginning at mSunriseTime... + float adjustedHour = time.getHour(); + float adjustedNightStart = mNightStart; + if ( time.getHour() < mSunriseTime ) + adjustedHour += 24.f; + if ( mNightStart < mSunriseTime ) + adjustedNightStart += 24.f; + + const bool is_night = adjustedHour >= adjustedNightStart; + const float dayDuration = adjustedNightStart - mSunriseTime; + const float nightDuration = 24.f - dayDuration; + + double theta; + if ( !is_night ) { + theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration; + } else { + theta = M_PI * (adjustedHour - adjustedNightStart) / nightDuration; + } + + osg::Vec3f final( + static_cast(cos(theta)), + -0.268f, // approx tan( -15 degrees ) + static_cast(sin(theta))); + mRendering.setSunDirection( final * -1 ); + } + + float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; + if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) + mRendering.getSkyManager()->setGlareTimeOfDayFade(0); + else if (time.getHour() < peakHour) + mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (peakHour - time.getHour()) / (peakHour - mSunriseTime)); + else + mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (time.getHour() - peakHour) / (mSunsetTime - peakHour)); + + mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); + mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); + + mRendering.configureFog(mResult.mFogDepth, mResult.mFogColor); + mRendering.setAmbientColour(mResult.mAmbientColor); + mRendering.setSunColour(mResult.mSunColor); + + mRendering.getSkyManager()->setWeather(mResult); + + // Play sounds + if (mPlayingSoundID != mResult.mAmbientLoopSoundID) + { + stopSounds(); + if (!mResult.mAmbientLoopSoundID.empty()) + mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + + mPlayingSoundID = mResult.mAmbientLoopSoundID; + } + if (mAmbientSound.get()) + mAmbientSound->setVolume(mResult.mAmbientSoundVolume); +} + +void WeatherManager::stopSounds() +{ + if (mAmbientSound.get()) + { + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound.reset(); + mPlayingSoundID.clear(); + } +} + +float WeatherManager::getWindSpeed() const +{ + return mWindSpeed; +} + +bool WeatherManager::isInStorm() const +{ + return mIsStorm; +} + +osg::Vec3f WeatherManager::getStormDirection() const +{ + return mStormDirection; +} + +void WeatherManager::advanceTime(double hours, bool incremental) +{ + // In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are + // immediately applied, regardless of whatever transition time might have been remaining. + mTimePassed += hours; + mFastForward = !incremental ? true : mFastForward; +} + +unsigned int WeatherManager::getWeatherID() const +{ + return mCurrentWeather; +} + +bool WeatherManager::isDark() const +{ + TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); + bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() + || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); + return exterior && (time.getHour() < mSunriseTime || time.getHour() > mNightStart - 1); +} + +void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) +{ + ESM::WeatherState state; + state.mCurrentRegion = mCurrentRegion; + state.mTimePassed = mTimePassed; + state.mFastForward = mFastForward; + state.mWeatherUpdateTime = mWeatherUpdateTime; + state.mTransitionFactor = mTransitionFactor; + state.mCurrentWeather = mCurrentWeather; + state.mNextWeather = mNextWeather; + state.mQueuedWeather = mQueuedWeather; + + std::map::iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + state.mRegions.insert(std::make_pair(it->first, it->second)); + } + + writer.startRecord(ESM::REC_WTHR); + state.save(writer); + writer.endRecord(ESM::REC_WTHR); +} + +bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) +{ + if(ESM::REC_WTHR == type) + { + static const int oldestCompatibleSaveFormat = 2; + if(reader.getFormat() < oldestCompatibleSaveFormat) + { + // Weather state isn't really all that important, so to preserve older save games, we'll just discard the + // older weather records, rather than fail to handle the record. + reader.skipRecord(); + } + else + { + ESM::WeatherState state; + state.load(reader); + + mCurrentRegion.swap(state.mCurrentRegion); + mTimePassed = state.mTimePassed; + mFastForward = state.mFastForward; + mWeatherUpdateTime = state.mWeatherUpdateTime; + mTransitionFactor = state.mTransitionFactor; + mCurrentWeather = state.mCurrentWeather; + mNextWeather = state.mNextWeather; + mQueuedWeather = state.mQueuedWeather; + + mRegions.clear(); + std::map::iterator it = state.mRegions.begin(); + if(it == state.mRegions.end()) + { + // When loading an imported save, the region modifiers aren't currently being set, so just reset them. + importRegions(); + } + else + { + for(; it != state.mRegions.end(); ++it) + { + mRegions.insert(std::make_pair(it->first, RegionWeather(it->second))); + } + } + } + + return true; + } + + return false; +} + +void WeatherManager::clear() +{ + stopSounds(); + + mCurrentRegion = ""; + mTimePassed = 0.0f; + mWeatherUpdateTime = 0.0f; + forceWeather(0); + mRegions.clear(); + importRegions(); +} + +inline void WeatherManager::addWeather(const std::string& name, + const MWWorld::Fallback& fallback, + const std::string& ambientLoopSoundID, + const std::string& particleEffect) +{ + static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); + + Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, ambientLoopSoundID, particleEffect); + + mWeatherSettings.push_back(weather); +} + +inline void WeatherManager::importRegions() +{ + Store::iterator it = mStore.get().begin(); + for(; it != mStore.get().end(); ++it) + { + std::string regionID = Misc::StringUtils::lowerCase(it->mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + } +} + +inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region) +{ + // If the region is current, then add a weather transition for it. + MWWorld::Ptr player = MWMechanics::getPlayer(); + if(player.isInCell()) + { + std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); + if(!playerRegion.empty() && (playerRegion == regionID)) + { + addWeatherTransition(region.getWeather()); + } + } +} + +inline bool WeatherManager::updateWeatherTime() +{ + mWeatherUpdateTime -= mTimePassed; + mTimePassed = 0.0f; + if(mWeatherUpdateTime <= 0.0f) + { + // Expire all regional weather, so that any call to getWeather() will return a new weather ID. + std::map::iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + it->second.setWeather(invalidWeatherID); + } + + mWeatherUpdateTime += mHoursBetweenWeatherChanges; + + return true; + } + + return false; +} + +inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion) +{ + if(!playerRegion.empty() && playerRegion != mCurrentRegion) + { + mCurrentRegion = playerRegion; + + return true; + } + + return false; +} + +inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds) +{ + // When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last + // weather type set, regardless of the remaining transition time. + if(!mFastForward && inTransition()) + { + const float delta = mWeatherSettings[mNextWeather].transitionDelta(); + mTransitionFactor -= elapsedRealSeconds * delta; + if(mTransitionFactor <= 0.0f) + { + mCurrentWeather = mNextWeather; + mNextWeather = mQueuedWeather; + mQueuedWeather = invalidWeatherID; + + // We may have begun processing the queued transition, so we need to apply the remaining time towards it. + if(inTransition()) + { + const float newDelta = mWeatherSettings[mNextWeather].transitionDelta(); + const float remainingSeconds = -(mTransitionFactor / delta); + mTransitionFactor = 1.0f - (remainingSeconds * newDelta); + } + else + { + mTransitionFactor = 0.0f; + } + } } else { - if (mNextWeather != "") + if(mQueuedWeather != invalidWeatherID) { - // transition more than 50% finished? - if (mRemainingTransitionTime/(mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600) <= 0.5) - mCurrentWeather = mNextWeather; + mCurrentWeather = mQueuedWeather; + } + else if(mNextWeather != invalidWeatherID) + { + mCurrentWeather = mNextWeather; } - mNextWeather = weather; - mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f; + mNextWeather = invalidWeatherID; + mQueuedWeather = invalidWeatherID; + mFastForward = false; } - mFirstUpdate = false; } -void WeatherManager::setResult(const std::string& weatherType) +inline void WeatherManager::forceWeather(const int weatherID) { - const Weather& current = mWeatherSettings[weatherType]; + mTransitionFactor = 0.0f; + mCurrentWeather = weatherID; + mNextWeather = invalidWeatherID; + mQueuedWeather = invalidWeatherID; +} + +inline bool WeatherManager::inTransition() +{ + return mNextWeather != invalidWeatherID; +} + +inline void WeatherManager::addWeatherTransition(const int weatherID) +{ + // In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if + // no transition is in progress, otherwise it queues it to be transitioned. + + assert(weatherID >= 0 && static_cast(weatherID) < mWeatherSettings.size()); + + if(!inTransition() && (weatherID != mCurrentWeather)) + { + mNextWeather = weatherID; + mTransitionFactor = 1.0f; + } + else if(inTransition() && (weatherID != mNextWeather)) + { + mQueuedWeather = weatherID; + } +} + +inline void WeatherManager::calculateWeatherResult(const float gameHour, + const float elapsedSeconds, + const bool isPaused) +{ + float flash = 0.0f; + if(!inTransition()) + { + calculateResult(mCurrentWeather, gameHour); + flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, isPaused); + } + else + { + calculateTransitionResult(1 - mTransitionFactor, gameHour); + float currentFlash = mWeatherSettings[mCurrentWeather].calculateThunder(mTransitionFactor, + elapsedSeconds, + isPaused); + float nextFlash = mWeatherSettings[mNextWeather].calculateThunder(1 - mTransitionFactor, + elapsedSeconds, + isPaused); + flash = currentFlash + nextFlash; + } + osg::Vec4f flashColor(flash, flash, flash, 0.0f); + + mResult.mFogColor += flashColor; + mResult.mAmbientColor += flashColor; + mResult.mSunColor += flashColor; +} + +inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) +{ + const Weather& current = mWeatherSettings[weatherID]; mResult.mCloudTexture = current.mCloudTexture; mResult.mCloudBlendFactor = 0; - mResult.mCloudOpacity = current.mCloudsMaximumPercent; mResult.mWindSpeed = current.mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientSoundVolume = 1.f; mResult.mEffectFade = 1.f; - mResult.mSunColor = current.mSunDiscSunsetColor; mResult.mIsStorm = current.mIsStorm; @@ -246,12 +975,13 @@ void WeatherManager::setResult(const std::string& weatherType) mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); + mResult.mNight = (gameHour < mSunriseTime || gameHour > mNightStart - 1); mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; + // TODO: use pre/post sunset/sunrise time values in [Weather] section // night - if (mHour <= mNightEnd || mHour >= mNightStart + 1) + if (gameHour <= mNightEnd || gameHour >= mNightStart + 1) { mResult.mFogColor = current.mFogNightColor; mResult.mAmbientColor = current.mAmbientNightColor; @@ -261,12 +991,12 @@ void WeatherManager::setResult(const std::string& weatherType) } // sunrise - else if (mHour >= mNightEnd && mHour <= mDayStart + 1) + else if (gameHour >= mNightEnd && gameHour <= mDayStart + 1) { - if (mHour <= mSunriseTime) + if (gameHour <= mSunriseTime) { // fade in - float advance = mSunriseTime - mHour; + float advance = mSunriseTime - gameHour; float factor = advance / 0.5f; mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor); @@ -274,10 +1004,10 @@ void WeatherManager::setResult(const std::string& weatherType) mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor); mResult.mNightFade = factor; } - else //if (mHour >= 6) + else //if (gameHour >= 6) { // fade out - float advance = mHour - mSunriseTime; + float advance = gameHour - mSunriseTime; float factor = advance / 3.f; mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor); @@ -287,7 +1017,7 @@ void WeatherManager::setResult(const std::string& weatherType) } // day - else if (mHour >= mDayStart + 1 && mHour <= mDayEnd - 1) + else if (gameHour >= mDayStart + 1 && gameHour <= mDayEnd - 1) { mResult.mFogColor = current.mFogDayColor; mResult.mAmbientColor = current.mAmbientDayColor; @@ -296,22 +1026,22 @@ void WeatherManager::setResult(const std::string& weatherType) } // sunset - else if (mHour >= mDayEnd - 1 && mHour <= mNightStart + 1) + else if (gameHour >= mDayEnd - 1 && gameHour <= mNightStart + 1) { - if (mHour <= mDayEnd + 1) + if (gameHour <= mDayEnd + 1) { // fade in - float advance = (mDayEnd + 1) - mHour; + float advance = (mDayEnd + 1) - gameHour; float factor = (advance / 2); mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor); mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor); mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor); } - else //if (mHour >= 19) + else //if (gameHour >= 19) { // fade out - float advance = mHour - (mDayEnd + 1); + float advance = gameHour - (mDayEnd + 1); float factor = advance / 2.f; mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor); @@ -320,20 +1050,49 @@ void WeatherManager::setResult(const std::string& weatherType) mResult.mNightFade = factor; } } + + if (gameHour >= mSunsetTime - mSunPreSunsetTime) + { + float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; + factor = std::min(1.f, factor); + mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); + // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because + // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline + // would then clamp the total lighting to (1,1,1). A noticable change in color tone can be observed when only one of the color components gets clamped. + // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. + mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); + for (int i=0; i<3; ++i) + mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]); + } + else + mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); + + if (gameHour >= mSunsetTime) + { + float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); + fade = fade*fade; + mResult.mSunDiscColor.a() = 1.f - fade; + } + else if (gameHour >= mSunriseTime && gameHour <= mSunriseTime + 1) + { + mResult.mSunDiscColor.a() = gameHour - mSunriseTime; + } + else + mResult.mSunDiscColor.a() = 1; + } -void WeatherManager::transition(float factor) +inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour) { - setResult(mCurrentWeather); - const WeatherResult current = mResult; - setResult(mNextWeather); - const WeatherResult other = mResult; + calculateResult(mCurrentWeather, gameHour); + const MWRender::WeatherResult current = mResult; + calculateResult(mNextWeather, gameHour); + const MWRender::WeatherResult other = mResult; mResult.mCloudTexture = current.mCloudTexture; mResult.mNextCloudTexture = other.mCloudTexture; - mResult.mCloudBlendFactor = factor; + mResult.mCloudBlendFactor = mWeatherSettings[mNextWeather].cloudBlendFactor(factor); - mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor); mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor); mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor); mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor); @@ -343,13 +1102,12 @@ void WeatherManager::transition(float factor) mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor); mResult.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); - mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor); mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); mResult.mNight = current.mNight; - if (factor < 0.5) + if(factor < 0.5) { mResult.mIsStorm = current.mIsStorm; mResult.mParticleEffect = current.mParticleEffect; @@ -375,494 +1133,3 @@ void WeatherManager::transition(float factor) } } -void WeatherManager::update(float duration, bool paused) -{ - float timePassed = static_cast(mTimePassed); - mTimePassed = 0; - - mWeatherUpdateTime -= timePassed; - - MWBase::World* world = MWBase::Environment::get().getWorld(); - const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior()); - if (!exterior) - { - mRendering->setSkyEnabled(false); - //mRendering->getSkyManager()->setLightningStrength(0.f); - stopSounds(); - return; - } - - switchToNextWeather(false); - - if (mNextWeather != "") - { - mRemainingTransitionTime -= timePassed; - if (mRemainingTransitionTime < 0) - { - mCurrentWeather = mNextWeather; - mNextWeather = ""; - } - } - - if (mNextWeather != "") - transition(1 - (mRemainingTransitionTime / (mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600))); - else - setResult(mCurrentWeather); - - mWindSpeed = mResult.mWindSpeed; - mIsStorm = mResult.mIsStorm; - - if (mIsStorm) - { - MWWorld::Ptr player = world->getPlayerPtr(); - osg::Vec3f playerPos (player.getRefData().getPosition().asVec3()); - osg::Vec3f redMountainPos (19950, 72032, 27831); - - mStormDirection = (playerPos - redMountainPos); - mStormDirection.z() = 0; - mStormDirection.normalize(); - mRendering->getSkyManager()->setStormDirection(mStormDirection); - } - - mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor); - - // disable sun during night - if (mHour >= mNightStart || mHour <= mSunriseTime) - mRendering->getSkyManager()->sunDisable(); - else - mRendering->getSkyManager()->sunEnable(); - - // Update the sun direction. Run it east to west at a fixed angle from overhead. - // The sun's speed at day and night may differ, since mSunriseTime and mNightStart - // mark when the sun is level with the horizon. - { - // Shift times into a 24-hour window beginning at mSunriseTime... - float adjustedHour = mHour; - float adjustedNightStart = mNightStart; - if ( mHour < mSunriseTime ) - adjustedHour += 24.f; - if ( mNightStart < mSunriseTime ) - adjustedNightStart += 24.f; - - const bool is_night = adjustedHour >= adjustedNightStart; - const float dayDuration = adjustedNightStart - mSunriseTime; - const float nightDuration = 24.f - dayDuration; - - double theta; - if ( !is_night ) { - theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration; - } else { - theta = M_PI * (adjustedHour - adjustedNightStart) / nightDuration; - } - - osg::Vec3f final( - static_cast(cos(theta)), - -0.268f, // approx tan( -15 degrees ) - static_cast(sin(theta))); - mRendering->setSunDirection( final * -1 ); - } - - /* - * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish - * for masser and secunda - */ - - float fadeOutFinish=mFallback->getFallbackFloat("Moons_Masser_Fade_Out_Finish"); - float fadeInStart=mFallback->getFallbackFloat("Moons_Masser_Fade_In_Start"); - - //moon calculations - float moonHeight; - if (mHour >= fadeInStart) - moonHeight = mHour - fadeInStart; - else if (mHour <= fadeOutFinish) - moonHeight = mHour + fadeOutFinish; - else - moonHeight = 0; - - moonHeight /= (24.f - (fadeInStart - fadeOutFinish)); - - if (moonHeight != 0) - { - int facing = (moonHeight <= 1) ? 1 : -1; - osg::Vec3f masser( - (moonHeight - 1) * facing, - (1 - moonHeight) * facing, - moonHeight); - - osg::Vec3f secunda( - (moonHeight - 1) * facing * 1.25f, - (1 - moonHeight) * facing * 0.8f, - moonHeight); - - mRendering->getSkyManager()->setMasserDirection(masser); - mRendering->getSkyManager()->setSecundaDirection(secunda); - - float angle = (1-moonHeight) * 90.f * facing; - float masserHourFade = calculateHourFade("Masser"); - float secundaHourFade = calculateHourFade("Secunda"); - float masserAngleFade = calculateAngleFade("Masser", angle); - float secundaAngleFade = calculateAngleFade("Secunda", angle); - - masserAngleFade *= masserHourFade; - secundaAngleFade *= secundaHourFade; - - if (masserAngleFade > 0) - { - mRendering->getSkyManager()->setMasserFade(masserAngleFade); - mRendering->getSkyManager()->masserEnable(); - } - else - mRendering->getSkyManager()->masserDisable(); - - if (secundaAngleFade > 0) - { - mRendering->getSkyManager()->setSecundaFade(secundaAngleFade); - mRendering->getSkyManager()->secundaEnable(); - } - else - mRendering->getSkyManager()->secundaDisable(); - - } - else - { - mRendering->getSkyManager()->masserDisable(); - mRendering->getSkyManager()->secundaDisable(); - } - - if (!paused) - { - if (mCurrentWeather == "thunderstorm" && mNextWeather == "") - { - if (mThunderFlash > 0) - { - // play the sound after a delay - mThunderSoundDelay -= duration; - if (mThunderSoundDelay <= 0) - { - // pick a random sound - int sound = Misc::Rng::rollDice(4); - std::string* soundName = NULL; - if (sound == 0) soundName = &mThunderSoundID0; - else if (sound == 1) soundName = &mThunderSoundID1; - else if (sound == 2) soundName = &mThunderSoundID2; - else if (sound == 3) soundName = &mThunderSoundID3; - if (soundName) - MWBase::Environment::get().getSoundManager()->playSound(*soundName, 1.0, 1.0); - mThunderSoundDelay = 1000; - } - - mThunderFlash -= duration; - //if (mThunderFlash > 0) - //mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); - //else - { - mThunderChanceNeeded = static_cast(Misc::Rng::rollDice(100)); - mThunderChance = 0; - //mRendering->getSkyManager()->setLightningStrength( 0.f ); - } - } - else - { - // no thunder active - mThunderChance += duration*4; // chance increases by 4 percent every second - if (mThunderChance >= mThunderChanceNeeded) - { - mThunderFlash = mThunderThreshold; - - //mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); - - mThunderSoundDelay = 0.25; - } - } - } - //else - //mRendering->getSkyManager()->setLightningStrength(0.f); - } - - - mRendering->setAmbientColour(mResult.mAmbientColor); - mRendering->setSunColour(mResult.mSunColor); - - mRendering->getSkyManager()->setWeather(mResult); - - // Play sounds - if (mPlayingSoundID != mResult.mAmbientLoopSoundID) - { - stopSounds(); - if (!mResult.mAmbientLoopSoundID.empty()) - mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - - mPlayingSoundID = mResult.mAmbientLoopSoundID; - } - if (mAmbientSound.get()) - mAmbientSound->setVolume(mResult.mAmbientSoundVolume); -} - -void WeatherManager::stopSounds() -{ - if (mAmbientSound.get()) - { - MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound.reset(); - mPlayingSoundID.clear(); - } -} - -std::string WeatherManager::nextWeather(const ESM::Region* region) const -{ - std::vector probability; - - RegionModMap::const_iterator iter = mRegionMods.find(Misc::StringUtils::lowerCase(region->mId)); - if(iter != mRegionMods.end()) - probability = iter->second; - else - { - probability.reserve(10); - probability.push_back(region->mData.mClear); - probability.push_back(region->mData.mCloudy); - probability.push_back(region->mData.mFoggy); - probability.push_back(region->mData.mOvercast); - probability.push_back(region->mData.mRain); - probability.push_back(region->mData.mThunder); - probability.push_back(region->mData.mAsh); - probability.push_back(region->mData.mBlight); - probability.push_back(region->mData.mA); - probability.push_back(region->mData.mB); - } - - /* - * All probabilities must add to 100 (responsibility of the user). - * If chances A and B has values 30 and 70 then by generating - * 100 numbers 1..100, 30% will be lesser or equal 30 and - * 70% will be greater than 30 (in theory). - */ - - int chance = Misc::Rng::rollDice(100) + 1; // 1..100 - int sum = 0; - unsigned int i = 0; - for (; i < probability.size(); ++i) - { - sum += probability[i]; - if (chance < sum) - break; - } - - switch (i) - { - case 1: - return "cloudy"; - case 2: - return "foggy"; - case 3: - return "overcast"; - case 4: - return "rain"; - case 5: - return "thunderstorm"; - case 6: - return "ashstorm"; - case 7: - return "blight"; - case 8: - return "snow"; - case 9: - return "blizzard"; - default: // case 0 - return "clear"; - } -} - -void WeatherManager::setHour(const float hour) -{ - mHour = hour; -} - -unsigned int WeatherManager::getWeatherID() const -{ - // Source: http://www.uesp.net/wiki/Tes3Mod:GetCurrentWeather - - if (mCurrentWeather == "clear") - return 0; - else if (mCurrentWeather == "cloudy") - return 1; - else if (mCurrentWeather == "foggy") - return 2; - else if (mCurrentWeather == "overcast") - return 3; - else if (mCurrentWeather == "rain") - return 4; - else if (mCurrentWeather == "thunderstorm") - return 5; - else if (mCurrentWeather == "ashstorm") - return 6; - else if (mCurrentWeather == "blight") - return 7; - else if (mCurrentWeather == "snow") - return 8; - else if (mCurrentWeather == "blizzard") - return 9; - - else - return 0; -} - -void WeatherManager::changeWeather(const std::string& region, const unsigned int id) -{ - // make sure this region exists - MWBase::Environment::get().getWorld()->getStore().get().find(region); - - std::string weather; - if (id==0) - weather = "clear"; - else if (id==1) - weather = "cloudy"; - else if (id==2) - weather = "foggy"; - else if (id==3) - weather = "overcast"; - else if (id==4) - weather = "rain"; - else if (id==5) - weather = "thunderstorm"; - else if (id==6) - weather = "ashstorm"; - else if (id==7) - weather = "blight"; - else if (id==8) - weather = "snow"; - else if (id==9) - weather = "blizzard"; - else - weather = "clear"; - - mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.isInCell()) - { - std::string playerRegion = player.getCell()->getCell()->mRegion; - if (Misc::StringUtils::ciEqual(region, playerRegion)) - setWeather(weather); - } -} - -void WeatherManager::modRegion(const std::string ®ionid, const std::vector &chances) -{ - mRegionMods[Misc::StringUtils::lowerCase(regionid)] = chances; - // Start transitioning right away if the region no longer supports the current weather type - unsigned int current = getWeatherID(); - if(current >= chances.size() || chances[current] == 0) - mWeatherUpdateTime = 0.0f; -} - -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::write(ESM::ESMWriter& writer, Loading::Listener& progress) -{ - ESM::WeatherState state; - state.mHour = mHour; - state.mWindSpeed = mWindSpeed; - state.mCurrentWeather = mCurrentWeather; - state.mNextWeather = mNextWeather; - state.mCurrentRegion = mCurrentRegion; - state.mFirstUpdate = mFirstUpdate; - state.mRemainingTransitionTime = mRemainingTransitionTime; - state.mTimePassed = mTimePassed; - - writer.startRecord(ESM::REC_WTHR); - state.save(writer); - writer.endRecord(ESM::REC_WTHR); -} - -bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) -{ - if(ESM::REC_WTHR == type) - { - // load first so that if it fails, we haven't accidentally reset the state below - ESM::WeatherState state; - state.load(reader); - - // swap in the loaded values now that we can't fail - mHour = state.mHour; - mWindSpeed = state.mWindSpeed; - mCurrentWeather.swap(state.mCurrentWeather); - mNextWeather.swap(state.mNextWeather); - mCurrentRegion.swap(state.mCurrentRegion); - mFirstUpdate = state.mFirstUpdate; - mRemainingTransitionTime = state.mRemainingTransitionTime; - mTimePassed = state.mTimePassed; - - return true; - } - - return false; -} - -void WeatherManager::clear() -{ - stopSounds(); - mRegionOverrides.clear(); - mRegionMods.clear(); - mThunderFlash = 0.0; - mThunderChance = 0.0; - mThunderChanceNeeded = 50.0; -} - -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()->getCell()->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); - } - } -} - -bool WeatherManager::isInStorm() const -{ - return mIsStorm; -} - -osg::Vec3f WeatherManager::getStormDirection() const -{ - return mStormDirection; -} - -void WeatherManager::advanceTime(double hours) -{ - mTimePassed += hours*3600; -} diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index d7dfee99b..7ce7c1bf8 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -9,9 +9,12 @@ #include "../mwbase/soundmanager.hpp" +#include "../mwrender/sky.hpp" + namespace ESM { struct Region; + struct RegionWeatherState; class ESMWriter; class ESMReader; } @@ -29,100 +32,60 @@ namespace Loading namespace MWWorld { class Fallback; - - /// Defines the actual weather that results from weather setting (see below), time of day and weather transition - struct WeatherResult - { - std::string mCloudTexture; - std::string mNextCloudTexture; - float mCloudBlendFactor; - - osg::Vec4f mFogColor; - - osg::Vec4f mAmbientColor; - - osg::Vec4f mSkyColor; - - osg::Vec4f mSunColor; - - osg::Vec4f mSunDiscColor; - - float mFogDepth; - - float mWindSpeed; - - float mCloudSpeed; - - float mCloudOpacity; - - float mGlareView; - - bool mNight; // use night skybox - float mNightFade; // fading factor for night skybox - - bool mIsStorm; - - std::string mAmbientLoopSoundID; - float mAmbientSoundVolume; - - std::string mParticleEffect; - std::string mRainEffect; - float mEffectFade; - - float mRainSpeed; - float mRainFrequency; - }; - + class TimeStamp; /// Defines a single weather setting (according to INI) - struct Weather + class Weather { + public: + Weather(const std::string& name, + const MWWorld::Fallback& fallback, + float stormWindSpeed, + float rainSpeed, + const std::string& ambientLoopSoundID, + const std::string& particleEffect); + std::string mCloudTexture; // Sky (atmosphere) colors - osg::Vec4f mSkySunriseColor, - mSkyDayColor, - mSkySunsetColor, - mSkyNightColor; + osg::Vec4f mSkySunriseColor; + osg::Vec4f mSkyDayColor; + osg::Vec4f mSkySunsetColor; + osg::Vec4f mSkyNightColor; // Fog colors - osg::Vec4f mFogSunriseColor, - mFogDayColor, - mFogSunsetColor, - mFogNightColor; + osg::Vec4f mFogSunriseColor; + osg::Vec4f mFogDayColor; + osg::Vec4f mFogSunsetColor; + osg::Vec4f mFogNightColor; // Ambient lighting colors - osg::Vec4f mAmbientSunriseColor, - mAmbientDayColor, - mAmbientSunsetColor, - mAmbientNightColor; + osg::Vec4f mAmbientSunriseColor; + osg::Vec4f mAmbientDayColor; + osg::Vec4f mAmbientSunsetColor; + osg::Vec4f mAmbientNightColor; // Sun (directional) lighting colors - osg::Vec4f mSunSunriseColor, - mSunDayColor, - mSunSunsetColor, - mSunNightColor; + osg::Vec4f mSunSunriseColor; + osg::Vec4f mSunDayColor; + osg::Vec4f mSunSunsetColor; + osg::Vec4f mSunNightColor; // Fog depth/density - float mLandFogDayDepth, - mLandFogNightDepth; + float mLandFogDayDepth; + float mLandFogNightDepth; - // Color modulation for the sun itself during sunset (not completely sure) + // Color modulation for the sun itself during sunset osg::Vec4f mSunDiscSunsetColor; - // Duration of weather transition (in days) - float mTransitionDelta; - // Used by scripts to animate signs, etc based on the wind (GetWindSpeed) float mWindSpeed; // Cloud animation speed multiplier float mCloudSpeed; - // Multiplier for clouds transparency - float mCloudsMaximumPercent; - - // Value between 0 and 1, defines the strength of the sun glare effect + // Value between 0 and 1, defines the strength of the sun glare effect. + // Also appears to modify how visible the sun, moons, and stars are for various weather effects. float mGlareView; // Sound effect @@ -148,15 +111,90 @@ namespace MWWorld // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // is broken in the vanilla game and was disabled. + + float transitionDelta() const; + float cloudBlendFactor(const float transitionRatio) const; + + float calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused); + + private: + float mTransitionDelta; + float mCloudsMaximumPercent; + + // Note: In MW, only thunderstorms support these attributes, but in the interest of making weather more + // flexible, these settings are imported for all weather types. Only thunderstorms will normally have any + // non-zero values. + float mThunderFrequency; + float mThunderThreshold; + std::string mThunderSoundID[4]; + float mFlashDecrement; + + float mFlashBrightness; + + void flashDecrement(const float elapsedSeconds); + float thunderChance(const float transitionRatio, const float elapsedSeconds) const; + void lightningAndThunder(void); + }; + + /// A class for storing a region's weather. + class RegionWeather + { + public: + explicit RegionWeather(const ESM::Region& region); + explicit RegionWeather(const ESM::RegionWeatherState& state); + + operator ESM::RegionWeatherState() const; + + void setChances(const std::vector& chances); + + void setWeather(int weatherID); + + int getWeather(); + + private: + int mWeather; + std::vector mChances; + + void chooseNewWeather(); + }; + + /// A class that acts as a model for the moons. + class MoonModel + { + public: + MoonModel(const std::string& name, const MWWorld::Fallback& fallback); + + MWRender::MoonState calculateState(const TimeStamp& gameTime) const; + + private: + float mFadeInStart; + float mFadeInFinish; + float mFadeOutStart; + float mFadeOutFinish; + float mAxisOffset; + float mSpeed; + float mDailyIncrement; + float mFadeStartAngle; + float mFadeEndAngle; + float mMoonShadowEarlyFadeAngle; + + float angle(const TimeStamp& gameTime) const; + float moonRiseHour(unsigned int daysPassed) const; + float rotation(float hours) const; + unsigned int phase(const TimeStamp& gameTime) const; + float shadowBlend(float angle) const; + float hourlyAlpha(float gameHour) const; + float earlyMoonShadowAlpha(float angle) const; }; - /// /// Interface for weather settings - /// class WeatherManager { public: - WeatherManager(MWRender::RenderingManager*,MWWorld::Fallback* fallback); + // Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time + WeatherManager(MWRender::RenderingManager& rendering, + const MWWorld::Fallback& fallback, + MWWorld::ESMStore& store); ~WeatherManager(); /** @@ -164,8 +202,9 @@ namespace MWWorld * @param region that should be changed * @param ID of the weather setting to shift to */ - void changeWeather(const std::string& region, const unsigned int id); - void switchToNextWeather(bool instantly = true); + void changeWeather(const std::string& regionID, const unsigned int weatherID); + void modRegion(const std::string& regionID, const std::vector& chances); + void playerTeleported(); /** * Per-frame update @@ -176,8 +215,6 @@ namespace MWWorld void stopSounds(); - void setHour(const float hour); - float getWindSpeed() const; /// Are we in an ash or blight storm? @@ -185,12 +222,10 @@ namespace MWWorld osg::Vec3f getStormDirection() const; - void advanceTime(double hours); + void advanceTime(double hours, bool incremental); unsigned int getWeatherID() const; - void modRegion(const std::string ®ionid, const std::vector &chances); - /// @see World::isDark bool isDark() const; @@ -201,68 +236,60 @@ namespace MWWorld void clear(); private: - float mHour; - float mWindSpeed; - bool mIsStorm; - osg::Vec3f mStormDirection; - - MWBase::SoundPtr mAmbientSound; - std::string mPlayingSoundID; - - MWWorld::Fallback* mFallback; - void setFallbackWeather(Weather& weather,const std::string& name); - MWRender::RenderingManager* mRendering; - - std::map mWeatherSettings; - - std::map mRegionOverrides; - - std::string mCurrentWeather; - std::string mNextWeather; - - std::string mCurrentRegion; - - bool mFirstUpdate; - - float mRemainingTransitionTime; - - float mThunderFlash; - float mThunderChance; - float mThunderChanceNeeded; - - double mTimePassed; // time passed since last update - - void transition(const float factor); - void setResult(const std::string& weatherType); - - float calculateHourFade (const std::string& moonName) const; - float calculateAngleFade (const std::string& moonName, float angle) const; - - void setWeather(const std::string& weatherType, bool instant=false); - std::string nextWeather(const ESM::Region* region) const; - WeatherResult mResult; - - typedef std::map > RegionModMap; - RegionModMap mRegionMods; - - float mRainSpeed; + MWWorld::ESMStore& mStore; + MWRender::RenderingManager& mRendering; float mSunriseTime; float mSunsetTime; float mSunriseDuration; float mSunsetDuration; - float mWeatherUpdateTime; - float mHoursBetweenWeatherChanges; - float mThunderFrequency; - float mThunderThreshold; - float mThunderSoundDelay; + float mSunPreSunsetTime; float mNightStart; float mNightEnd; float mDayStart; float mDayEnd; - std::string mThunderSoundID0; - std::string mThunderSoundID1; - std::string mThunderSoundID2; - std::string mThunderSoundID3; + float mHoursBetweenWeatherChanges; + float mRainSpeed; + std::vector mWeatherSettings; + MoonModel mMasser; + MoonModel mSecunda; + + float mWindSpeed; + bool mIsStorm; + osg::Vec3f mStormDirection; + + std::string mCurrentRegion; + float mTimePassed; + bool mFastForward; + float mWeatherUpdateTime; + float mTransitionFactor; + int mCurrentWeather; + int mNextWeather; + int mQueuedWeather; + std::map mRegions; + MWRender::WeatherResult mResult; + + MWBase::SoundPtr mAmbientSound; + std::string mPlayingSoundID; + + void addWeather(const std::string& name, + const MWWorld::Fallback& fallback, + const std::string& ambientLoopSoundID = "", + const std::string& particleEffect = ""); + + void importRegions(); + + void regionalWeatherChanged(const std::string& regionID, RegionWeather& region); + bool updateWeatherTime(); + bool updateWeatherRegion(const std::string& playerRegion); + void updateWeatherTransitions(const float elapsedRealSeconds); + void forceWeather(const int weatherID); + + bool inTransition(); + void addWeatherTransition(const int weatherID); + + void calculateWeatherResult(const float gameHour, const float elapsedSeconds, const bool isPaused); + void calculateResult(const int weatherID, const float gameHour); + void calculateTransitionResult(const float factor, const float gameHour); }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3b57e22c5..3ff7a750a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -14,13 +14,11 @@ #include #include +#include #include #include -#include -#include -#include #include #include @@ -167,8 +165,6 @@ namespace MWWorld mProjectileManager.reset(new ProjectileManager(rootNode, resourceSystem, mPhysics)); mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem, &mFallback); - mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -197,6 +193,8 @@ namespace MWWorld mGlobalVariables.fill (mStore); + mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); + mWorldScene = new Scene(*mRendering, mPhysics); } @@ -214,11 +212,15 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); + // we don't want old weather to persist on a new game + // Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost. + delete mWeatherManager; + mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); + if (!bypass) { // set new game mark mGlobalVariables["chargenstate"].setInteger (1); - mGlobalVariables["pcrace"].setInteger (3); } else mGlobalVariables["chargenstate"].setInteger (-1); @@ -230,6 +232,7 @@ namespace MWWorld if (findExteriorPosition (mStartCell, pos)) { changeToExteriorCell (pos); + fixPosition(getPlayerPtr()); } else { @@ -266,11 +269,6 @@ namespace MWWorld if (!mPhysics->toggleCollisionMode()) mPhysics->toggleCollisionMode(); - // we don't want old weather to persist on a new game - delete mWeatherManager; - mWeatherManager = 0; - mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - if (!mStartupScript.empty()) MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); } @@ -289,7 +287,7 @@ namespace MWWorld if (mPlayer) { - mPlayer->clear(); + mPlayer->clear(); mPlayer->setCell(0); mPlayer->getPlayer().getRefData() = RefData(); mPlayer->set(mStore.get().find ("player")); @@ -788,11 +786,11 @@ namespace MWWorld } } - void World::advanceTime (double hours) + void World::advanceTime (double hours, bool incremental) { MWBase::Environment::get().getMechanicsManager()->advanceTime(static_cast(hours * 3600)); - mWeatherManager->advanceTime (hours); + mWeatherManager->advanceTime (hours, incremental); hours += mGlobalVariables["gamehour"].getFloat(); @@ -816,8 +814,6 @@ namespace MWWorld mGlobalVariables["gamehour"].setFloat(static_cast(hour)); - mWeatherManager->setHour(static_cast(hour)); - if (days>0) setDay (days + mGlobalVariables["day"].getInteger()); } @@ -1022,14 +1018,11 @@ namespace MWWorld return facedObject; } - std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) + osg::Vec3f getActorHeadPosition(const MWWorld::Ptr& actor, MWRender::RenderingManager* rendering) { - const ESM::Position &posdata = ptr.getRefData().getPosition(); + osg::Vec3f origin(actor.getRefData().getPosition().asVec3()); - osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f pos (posdata.asVec3()); - - MWRender::Animation* anim = mRendering->getAnimation(ptr); + MWRender::Animation* anim = rendering->getAnimation(actor); if (anim != NULL) { const osg::Node* node = anim->getNode("Head"); @@ -1039,9 +1032,18 @@ namespace MWWorld { osg::MatrixList mats = node->getWorldMatrices(); if (mats.size()) - pos = mats[0].getTrans(); + origin = mats[0].getTrans(); } } + return origin; + } + + std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) + { + const ESM::Position &posdata = ptr.getRefData().getPosition(); + + osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); + osg::Vec3f pos = getActorHeadPosition(ptr, mRendering); std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance); if(result.first.isEmpty()) @@ -1052,7 +1054,7 @@ namespace MWWorld void World::deleteObject (const Ptr& ptr) { - if (!ptr.getRefData().isDeleted()) + if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == NULL) { ptr.getRefData().setCount(0); @@ -1214,7 +1216,6 @@ namespace MWWorld void World::rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust) { const float pi = static_cast(osg::PI); - const float two_pi = pi*2.f; ESM::Position pos = ptr.getRefData().getPosition(); float *objRot = pos.rot; @@ -1244,15 +1245,11 @@ namespace MWWorld } else { - while(objRot[0] < -pi) objRot[0] += two_pi; - while(objRot[0] > pi) objRot[0] -= two_pi; + wrap(objRot[0]); } - while(objRot[1] < -pi) objRot[1] += two_pi; - while(objRot[1] > pi) objRot[1] -= two_pi; - - while(objRot[2] < -pi) objRot[2] += two_pi; - while(objRot[2] > pi) objRot[2] -= two_pi; + wrap(objRot[1]); + wrap(objRot[2]); ptr.getRefData().setPosition(pos); @@ -1423,7 +1420,7 @@ namespace MWWorld if (ptr.getClass().isActor()) { // Collided with actor, ask actor to try to avoid door - if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr() ) { + if(ptr != getPlayerPtr() ) { MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr); @@ -1499,17 +1496,6 @@ namespace MWWorld if (Misc::StringUtils::ciEqual(record.mId, "player")) { - std::vector ids; - getStore().get().listIdentifier(ids); - - unsigned int i=0; - - for (; igetPlayer().get()->mBase; @@ -1740,7 +1726,7 @@ namespace MWWorld else { cellid.mPaged = true; - MWBase::Environment::get().getWorld()->positionToIndex( + positionToIndex( ref.mRef.getDoorDest().pos[0], ref.mRef.getDoorDest().pos[1], cellid.mIndex.mX, @@ -1928,16 +1914,15 @@ namespace MWWorld bool World::isFlying(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0); if(!ptr.getClass().isActor()) return false; - if (ptr.getClass().getCreatureStats(ptr).isDead()) + if (stats.isDead()) return false; if (ptr.getClass().canFly(ptr)) - return !isParalyzed; + return !stats.isParalyzed(); if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && isLevitationEnabled()) @@ -2518,7 +2503,7 @@ namespace MWWorld if (reported) { npcStats.setBounty(npcStats.getBounty()+ - MWBase::Environment::get().getWorld()->getStore().get().find("iWereWolfBounty")->getInt()); + mStore.get().find("iWereWolfBounty")->getInt()); windowManager->messageBox("#{sCrimeMessage}"); } } @@ -2625,27 +2610,11 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // Get the target to use for "on touch" effects + // Get the target to use for "on touch" effects, using the facing direction from Head node MWWorld::Ptr target; float distance = 192.f; // ?? osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - - // For NPCs use facing direction from Head node - osg::Vec3f origin(actor.getRefData().getPosition().asVec3()); - - MWRender::Animation* anim = mRendering->getAnimation(actor); - if (anim != NULL) - { - const osg::Node* node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if (node != NULL) - { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.size()) - origin = mats[0].getTrans(); - } - } + osg::Vec3f origin = getActorHeadPosition(actor, mRendering); osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); @@ -2653,13 +2622,33 @@ namespace MWWorld osg::Vec3f direction = orient * osg::Vec3f(0,1,0); osg::Vec3f dest = origin + direction * distance; - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(origin, dest, actor); - target = result.mHitObject; - hitPosition = result.mHitPos; + // For actor targets, we want to use bounding boxes (physics raycast). + // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. - // don't allow casting on non-activatable objects - if (!target.isEmpty() && !target.getClass().isActor() && target.getClass().getName(target).empty()) - target = MWWorld::Ptr(); + MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, MWPhysics::CollisionType_Actor); + + MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + + float dist1 = std::numeric_limits::max(); + float dist2 = std::numeric_limits::max(); + + if (result1.mHit) + dist1 = (origin - result1.mHitPos).length(); + if (result2.mHit) + dist2 = (origin - result2.mHitPointWorld).length(); + + if (dist1 <= dist2 && result1.mHit) + { + target = result1.mHitObject; + hitPosition = result1.mHitPos; + } + else if (result2.mHit) + { + target = result2.mHitObject; + hitPosition = result2.mHitPointWorld; + } std::string selectedSpell = stats.getSpells().getSelectedSpell(); @@ -2836,7 +2825,7 @@ namespace MWWorld MWWorld::Ptr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) { MWWorld::Ptr closestMarker; - float closestDistance = FLT_MAX; + float closestDistance = std::numeric_limits::max(); std::vector markers; mCells.getExteriorPtrs(id, markers); @@ -2875,15 +2864,15 @@ namespace MWWorld MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false); action.execute(ptr); } - + void World::updateWeather(float duration, bool paused) { if (mPlayer->wasTeleported()) { mPlayer->setTeleported(false); - mWeatherManager->switchToNextWeather(true); + mWeatherManager->playerTeleported(); } - + mWeatherManager->update(duration, paused); } @@ -3159,7 +3148,7 @@ namespace MWWorld // Spawn the explosion orb effect const ESM::Static* areaStatic; - if (!effect->mCasting.empty()) + if (!effect->mArea.empty()) areaStatic = getStore().get().find (effect->mArea); else areaStatic = getStore().get().find ("VFX_DefaultArea"); @@ -3265,4 +3254,12 @@ namespace MWWorld return true; return false; } + + osg::Vec3f World::aimToTarget(const Ptr &actor, const MWWorld::Ptr& target) + { + osg::Vec3f weaponPos = getActorHeadPosition(actor, mRendering); + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + targetPos.z() += mPhysics->getHalfExtents(target).z(); + return (targetPos - weaponPos); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 5251427c5..26153086a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -274,7 +274,7 @@ namespace MWWorld virtual void disable (const Ptr& ptr); - virtual void advanceTime (double hours); + virtual void advanceTime (double hours, bool incremental = false); ///< Advance in-game time. virtual void setHour (double hour); @@ -336,7 +336,9 @@ namespace MWWorld /// use the "Head" node as a basis. virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance); + /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const Ptr& ptr); + virtual void undeleteObject (const Ptr& ptr); virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); @@ -628,6 +630,10 @@ namespace MWWorld virtual void resetActors(); virtual bool isWalkingOnWater (const MWWorld::Ptr& actor); + + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; } diff --git a/cmake/BundleUtilitiesWithRPath.cmake b/cmake/BundleUtilitiesWithRPath.cmake new file mode 100644 index 000000000..5254e1a52 --- /dev/null +++ b/cmake/BundleUtilitiesWithRPath.cmake @@ -0,0 +1,961 @@ +#.rst: +# BundleUtilities +# --------------- +# +# Functions to help assemble a standalone bundle application. +# +# A collection of CMake utility functions useful for dealing with .app +# bundles on the Mac and bundle-like directories on any OS. +# +# The following functions are provided by this module: +# +# :: +# +# fixup_bundle +# copy_and_fixup_bundle +# verify_app +# get_bundle_main_executable +# get_dotapp_dir +# get_bundle_and_executable +# get_bundle_all_executables +# get_item_key +# clear_bundle_keys +# set_bundle_key_values +# get_bundle_keys +# copy_resolved_item_into_bundle +# copy_resolved_framework_into_bundle +# fixup_bundle_item +# verify_bundle_prerequisites +# verify_bundle_symlinks +# +# Requires CMake 2.6 or greater because it uses function, break and +# PARENT_SCOPE. Also depends on GetPrerequisites.cmake. +# +# :: +# +# FIXUP_BUNDLE( ) +# +# Fix up a bundle in-place and make it standalone, such that it can be +# drag-n-drop copied to another machine and run on that machine as long +# as all of the system libraries are compatible. +# +# If you pass plugins to fixup_bundle as the libs parameter, you should +# install them or copy them into the bundle before calling fixup_bundle. +# The "libs" parameter is a list of libraries that must be fixed up, but +# that cannot be determined by otool output analysis. (i.e., plugins) +# +# Gather all the keys for all the executables and libraries in a bundle, +# and then, for each key, copy each prerequisite into the bundle. Then +# fix each one up according to its own list of prerequisites. +# +# Then clear all the keys and call verify_app on the final bundle to +# ensure that it is truly standalone. +# +# :: +# +# COPY_AND_FIXUP_BUNDLE( ) +# +# Makes a copy of the bundle at location and then fixes up +# the new copied bundle in-place at ... +# +# :: +# +# VERIFY_APP() +# +# Verifies that an application appears valid based on running +# analysis tools on it. Calls "message(FATAL_ERROR" if the application +# is not verified. +# +# :: +# +# GET_BUNDLE_MAIN_EXECUTABLE( ) +# +# The result will be the full path name of the bundle's main executable +# file or an "error:" prefixed string if it could not be determined. +# +# :: +# +# GET_DOTAPP_DIR( ) +# +# Returns the nearest parent dir whose name ends with ".app" given the +# full path to an executable. If there is no such parent dir, then +# simply return the dir containing the executable. +# +# The returned directory may or may not exist. +# +# :: +# +# GET_BUNDLE_AND_EXECUTABLE( ) +# +# Takes either a ".app" directory name or the name of an executable +# nested inside a ".app" directory and returns the path to the ".app" +# directory in and the path to its main executable in +# +# +# :: +# +# GET_BUNDLE_ALL_EXECUTABLES( ) +# +# Scans the given bundle recursively for all executable files and +# accumulates them into a variable. +# +# :: +# +# GET_ITEM_KEY( ) +# +# Given a file (item) name, generate a key that should be unique +# considering the set of libraries that need copying or fixing up to +# make a bundle standalone. This is essentially the file name including +# extension with "." replaced by "_" +# +# This key is used as a prefix for CMake variables so that we can +# associate a set of variables with a given item based on its key. +# +# :: +# +# CLEAR_BUNDLE_KEYS() +# +# Loop over the list of keys, clearing all the variables associated with +# each key. After the loop, clear the list of keys itself. +# +# Caller of get_bundle_keys should call clear_bundle_keys when done with +# list of keys. +# +# :: +# +# SET_BUNDLE_KEY_VALUES( +# ) +# +# Add a key to the list (if necessary) for the given item. If added, +# also set all the variables associated with that key. +# +# :: +# +# GET_BUNDLE_KEYS( ) +# +# Loop over all the executable and library files within the bundle (and +# given as extra ) and accumulate a list of keys representing +# them. Set values associated with each key such that we can loop over +# all of them and copy prerequisite libs into the bundle and then do +# appropriate install_name_tool fixups. +# +# :: +# +# COPY_RESOLVED_ITEM_INTO_BUNDLE( ) +# +# Copy a resolved item into the bundle if necessary. Copy is not +# necessary if the resolved_item is "the same as" the +# resolved_embedded_item. +# +# :: +# +# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE( ) +# +# Copy a resolved framework into the bundle if necessary. Copy is not +# necessary if the resolved_item is "the same as" the +# resolved_embedded_item. +# +# By default, BU_COPY_FULL_FRAMEWORK_CONTENTS is not set. If you want +# full frameworks embedded in your bundles, set +# BU_COPY_FULL_FRAMEWORK_CONTENTS to ON before calling fixup_bundle. By +# default, COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE copies the framework +# dylib itself plus the framework Resources directory. +# +# :: +# +# IS_RESOLVED_ITEM_EMBEDDED( ) +# +# Set variable to True if the resolved item is +# embedded into the bundle. The function does NOT check for the existence of the +# item, instead if checks if the provided path would correspond to an embeddable +# item. If is True, extra information will be displayed in case the item +# is not embedded. +# +# :: +# +# FIXUP_BUNDLE_ITEM( ) +# +# Get the direct/non-system prerequisites of the resolved embedded item. +# For each prerequisite, change the way it is referenced to the value of +# the _EMBEDDED_ITEM keyed variable for that prerequisite. (Most likely +# changing to an "@executable_path" style reference.) +# +# This function requires that the resolved_embedded_item be "inside" the +# bundle already. In other words, if you pass plugins to fixup_bundle +# as the libs parameter, you should install them or copy them into the +# bundle before calling fixup_bundle. The "libs" parameter is a list of +# libraries that must be fixed up, but that cannot be determined by +# otool output analysis. (i.e., plugins) +# +# Also, change the id of the item being fixed up to its own +# _EMBEDDED_ITEM value. +# +# Accumulate changes in a local variable and make *one* call to +# install_name_tool at the end of the function with all the changes at +# once. +# +# If the BU_CHMOD_BUNDLE_ITEMS variable is set then bundle items will be +# marked writable before install_name_tool tries to change them. +# +# :: +# +# VERIFY_BUNDLE_PREREQUISITES( ) +# +# Verifies that the sum of all prerequisites of all files inside the +# bundle are contained within the bundle or are "system" libraries, +# presumed to exist everywhere. +# +# :: +# +# VERIFY_BUNDLE_SYMLINKS( ) +# +# Verifies that any symlinks found in the bundle point to other files +# that are already also in the bundle... Anything that points to an +# external file causes this function to fail the verification. + +#============================================================================= +# Copyright 2008-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +# Copyright (c) 2015 BWH and 3D Slicer contributors, http://slicer.org +# +# Redistribution AND use is allowed according to the terms of the +# BSD-style license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# The functions defined in this file depend on the get_prerequisites function +# (and possibly others) found in: +# +get_filename_component(BundleUtilities_cmake_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) +include("${BundleUtilities_cmake_dir}/GetPrerequisitesWithRPath.cmake") + + +function(get_bundle_main_executable bundle result_var) + set(result "error: '${bundle}/Contents/Info.plist' file does not exist") + + if(EXISTS "${bundle}/Contents/Info.plist") + set(result "error: no CFBundleExecutable in '${bundle}/Contents/Info.plist' file") + set(line_is_main_executable 0) + set(bundle_executable "") + + # Read Info.plist as a list of lines: + # + set(eol_char "E") + file(READ "${bundle}/Contents/Info.plist" info_plist) + string(REPLACE ";" "\\;" info_plist "${info_plist}") + string(REPLACE "\n" "${eol_char};" info_plist "${info_plist}") + string(REPLACE "\r" "${eol_char};" info_plist "${info_plist}") + + # Scan the lines for "CFBundleExecutable" - the line after that + # is the name of the main executable. + # + foreach(line ${info_plist}) + if(line_is_main_executable) + string(REGEX REPLACE "^.*(.*).*$" "\\1" bundle_executable "${line}") + break() + endif() + + if(line MATCHES "CFBundleExecutable") + set(line_is_main_executable 1) + endif() + endforeach() + + if(NOT "${bundle_executable}" STREQUAL "") + if(EXISTS "${bundle}/Contents/MacOS/${bundle_executable}") + set(result "${bundle}/Contents/MacOS/${bundle_executable}") + else() + + # Ultimate goal: + # If not in "Contents/MacOS" then scan the bundle for matching files. If + # there is only one executable file that matches, then use it, otherwise + # it's an error... + # + #file(GLOB_RECURSE file_list "${bundle}/${bundle_executable}") + + # But for now, pragmatically, it's an error. Expect the main executable + # for the bundle to be in Contents/MacOS, it's an error if it's not: + # + set(result "error: '${bundle}/Contents/MacOS/${bundle_executable}' does not exist") + endif() + endif() + else() + # + # More inclusive technique... (This one would work on Windows and Linux + # too, if a developer followed the typical Mac bundle naming convention...) + # + # If there is no Info.plist file, try to find an executable with the same + # base name as the .app directory: + # + endif() + + set(${result_var} "${result}" PARENT_SCOPE) +endfunction() + + +function(get_dotapp_dir exe dotapp_dir_var) + set(s "${exe}") + + if(s MATCHES "/.*\\.app/") + # If there is a ".app" parent directory, + # ascend until we hit it: + # (typical of a Mac bundle executable) + # + set(done 0) + while(NOT ${done}) + get_filename_component(snamewe "${s}" NAME_WE) + get_filename_component(sname "${s}" NAME) + get_filename_component(sdir "${s}" PATH) + set(s "${sdir}") + if(sname MATCHES "\\.app$") + set(done 1) + set(dotapp_dir "${sdir}/${sname}") + endif() + endwhile() + else() + # Otherwise use a directory containing the exe + # (typical of a non-bundle executable on Mac, Windows or Linux) + # + is_file_executable("${s}" is_executable) + if(is_executable) + get_filename_component(sdir "${s}" PATH) + set(dotapp_dir "${sdir}") + else() + set(dotapp_dir "${s}") + endif() + endif() + + + set(${dotapp_dir_var} "${dotapp_dir}" PARENT_SCOPE) +endfunction() + + +function(get_bundle_and_executable app bundle_var executable_var valid_var) + set(valid 0) + + if(EXISTS "${app}") + # Is it a directory ending in .app? + if(IS_DIRECTORY "${app}") + if(app MATCHES "\\.app$") + get_bundle_main_executable("${app}" executable) + if(EXISTS "${app}" AND EXISTS "${executable}") + set(${bundle_var} "${app}" PARENT_SCOPE) + set(${executable_var} "${executable}" PARENT_SCOPE) + set(valid 1) + #message(STATUS "info: handled .app directory case...") + else() + message(STATUS "warning: *NOT* handled - .app directory case...") + endif() + else() + message(STATUS "warning: *NOT* handled - directory but not .app case...") + endif() + else() + # Is it an executable file? + is_file_executable("${app}" is_executable) + if(is_executable) + get_dotapp_dir("${app}" dotapp_dir) + if(EXISTS "${dotapp_dir}") + set(${bundle_var} "${dotapp_dir}" PARENT_SCOPE) + set(${executable_var} "${app}" PARENT_SCOPE) + set(valid 1) + #message(STATUS "info: handled executable file in .app dir case...") + else() + get_filename_component(app_dir "${app}" PATH) + set(${bundle_var} "${app_dir}" PARENT_SCOPE) + set(${executable_var} "${app}" PARENT_SCOPE) + set(valid 1) + #message(STATUS "info: handled executable file in any dir case...") + endif() + else() + message(STATUS "warning: *NOT* handled - not .app dir, not executable file...") + endif() + endif() + else() + message(STATUS "warning: *NOT* handled - directory/file does not exist...") + endif() + + if(NOT valid) + set(${bundle_var} "error: not a bundle" PARENT_SCOPE) + set(${executable_var} "error: not a bundle" PARENT_SCOPE) + endif() + + set(${valid_var} ${valid} PARENT_SCOPE) +endfunction() + + +function(get_bundle_all_executables bundle exes_var) + set(exes "") + + file(GLOB_RECURSE file_list "${bundle}/*") + if(UNIX) + find_program(find_cmd "find") + mark_as_advanced(find_cmd) + endif() + + # find command is much quicker than checking every file one by one on Unix + # which can take long time for large bundles, and since anyway we expect + # executable to have execute flag set we can narrow the list much quicker. + if(find_cmd) + execute_process(COMMAND "${find_cmd}" "${bundle}" + -type f \( -perm -0100 -o -perm -0010 -o -perm -0001 \) + OUTPUT_VARIABLE file_list + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + string(REPLACE "\n" ";" file_list "${file_list}") + else() + file(GLOB_RECURSE file_list "${bundle}/*") + endif() + + foreach(f ${file_list}) + is_file_executable("${f}" is_executable) + if(is_executable) + set(exes ${exes} "${f}") + endif() + endforeach() + + set(${exes_var} "${exes}" PARENT_SCOPE) +endfunction() + + +function(get_item_key item key_var) + get_filename_component(item_name "${item}" NAME) + if(WIN32) + string(TOLOWER "${item_name}" item_name) + endif() + string(REPLACE "." "_" ${key_var} "${item_name}") + set(${key_var} ${${key_var}} PARENT_SCOPE) +endfunction() + + +function(clear_bundle_keys keys_var) + foreach(key ${${keys_var}}) + set(${key}_ITEM PARENT_SCOPE) + set(${key}_RESOLVED_ITEM PARENT_SCOPE) + set(${key}_DEFAULT_EMBEDDED_PATH PARENT_SCOPE) + set(${key}_EMBEDDED_ITEM PARENT_SCOPE) + set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE) + set(${key}_COPYFLAG PARENT_SCOPE) + endforeach() + set(${keys_var} PARENT_SCOPE) +endfunction() + + +function(set_bundle_key_values keys_var context item exepath dirs copyflag) + get_filename_component(item_name "${item}" NAME) + + get_item_key("${item}" key) + + list(LENGTH ${keys_var} length_before) + gp_append_unique(${keys_var} "${key}") + list(LENGTH ${keys_var} length_after) + + if(NOT length_before EQUAL length_after) + gp_resolve_item("${context}" "${item}" "${exepath}" "${dirs}" resolved_item) + + gp_item_default_embedded_path("${item}" default_embedded_path) + + if(item MATCHES "[^/]+\\.framework/") + # For frameworks, construct the name under the embedded path from the + # opening "${item_name}.framework/" to the closing "/${item_name}": + # + string(REGEX REPLACE "^.*(${item_name}.framework/.*/?${item_name}).*$" "${default_embedded_path}/\\1" embedded_item "${item}") + else() + # For other items, just use the same name as the original, but in the + # embedded path: + # + set(embedded_item "${default_embedded_path}/${item_name}") + + if(APPLE) + # For executables inside the bundle, extract the expected path. + # This remove the hack introduced in commit 6f8bdd27 consisting in + # reseting the value of 'resolved_embedded_item' with 'resolved_item'. + get_dotapp_dir("${exepath}" exe_dotapp_dir) + if(NOT DEFINED gp_bundle_executables) + get_bundle_all_executables("${exe_dotapp_dir}" gp_bundle_executables) + endif() + foreach(exe ${gp_bundle_executables}) + get_item_key("${exe}" exe_key) + list(APPEND exe_keys ${exe_key}) + endforeach() + list(FIND exe_keys ${key} is_executable) + if(NOT is_executable EQUAL "-1") + get_filename_component(resolved_item_path ${resolved_item} PATH) + file(RELATIVE_PATH exe_relative_path_from_dir ${exe_dotapp_dir} ${resolved_item_path}) + # For example, if input variables are: + # resolved_item: /path/to/MyApp.app/Contents/bin/myapp + # exe_dotapp_dir: /path/to/MyApp.app + # Computed variables will be: + # resolved_item_path: /path/to/MyApp.app/Contents/bin + # exe_relative_path_from_dir: Contents/bin + set(embedded_item "@executable_path/../../${exe_relative_path_from_dir}/${item_name}") + set(show_status 0) + if(show_status) + message(STATUS "resolved_item='${resolved_item}'") + message(STATUS "exe_dotapp_dir='${exe_dotapp_dir}'") + message(STATUS "exe_relative_path_from_dir='${exe_relative_path_from_dir}'") + message(STATUS "item_name='${item_name}'") + message(STATUS "embedded_item='${embedded_item}'") + message(STATUS "") + endif() + endif() + endif() + endif() + + gp_resolve_embedded_item("${context}" "${embedded_item}" "${exepath}" resolved_embedded_item) + get_filename_component(resolved_embedded_item "${resolved_embedded_item}" ABSOLUTE) + + # Do not copy already embedded item + set(verbose 0) + is_resolved_item_embedded("${resolved_embedded_item}" "${exepath}" "${verbose}" is_embedded) + if(EXISTS "${resolved_embedded_item}" AND is_embedded) + set(copyflag 0) + set(resolved_item "${resolved_embedded_item}") + endif() + + set(${keys_var} ${${keys_var}} PARENT_SCOPE) + set(${key}_ITEM "${item}" PARENT_SCOPE) + set(${key}_RESOLVED_ITEM "${resolved_item}" PARENT_SCOPE) + set(${key}_DEFAULT_EMBEDDED_PATH "${default_embedded_path}" PARENT_SCOPE) + set(${key}_EMBEDDED_ITEM "${embedded_item}" PARENT_SCOPE) + set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE) + set(${key}_COPYFLAG "${copyflag}" PARENT_SCOPE) + else() + #message("warning: item key '${key}' already in the list, subsequent references assumed identical to first") + endif() +endfunction() + + +function(get_bundle_keys app libs dirs keys_var) + set(${keys_var} PARENT_SCOPE) + + get_bundle_and_executable("${app}" bundle executable valid) + if(valid) + # Always use the exepath of the main bundle executable for @executable_path + # replacements: + # + get_filename_component(exepath "${executable}" PATH) + + # But do fixups on all executables in the bundle: + # + get_bundle_all_executables("${bundle}" gp_bundle_executables) + + # For each extra lib, accumulate a key as well and then also accumulate + # any of its prerequisites. (Extra libs are typically dynamically loaded + # plugins: libraries that are prerequisites for full runtime functionality + # but that do not show up in otool -L output...) + # + foreach(lib ${libs}) + set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${exepath}" "${dirs}" 0) + + set(prereqs "") + get_prerequisites("${lib}" prereqs 1 1 "${exepath}" "${dirs}") + foreach(pr ${prereqs}) + set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${exepath}" "${dirs}" 1) + endforeach() + endforeach() + + # For each executable found in the bundle, accumulate keys as we go. + # The list of keys should be complete when all prerequisites of all + # binaries in the bundle have been analyzed. + # + foreach(exe ${gp_bundle_executables}) + # Add the exe itself to the keys: + # + set_bundle_key_values(${keys_var} "${exe}" "${exe}" "${exepath}" "${dirs}" 0) + + # Add each prerequisite to the keys: + # + set(prereqs "") + get_prerequisites("${exe}" prereqs 1 1 "${exepath}" "${dirs}") + foreach(pr ${prereqs}) + set_bundle_key_values(${keys_var} "${exe}" "${pr}" "${exepath}" "${dirs}" 1) + endforeach() + endforeach() + + # Propagate values to caller's scope: + # + set(${keys_var} ${${keys_var}} PARENT_SCOPE) + foreach(key ${${keys_var}}) + set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE) + set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE) + set(${key}_DEFAULT_EMBEDDED_PATH "${${key}_DEFAULT_EMBEDDED_PATH}" PARENT_SCOPE) + set(${key}_EMBEDDED_ITEM "${${key}_EMBEDDED_ITEM}" PARENT_SCOPE) + set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE) + set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE) + endforeach() + endif() +endfunction() + + +function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) + if(WIN32) + # ignore case on Windows + string(TOLOWER "${resolved_item}" resolved_item_compare) + string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) + else() + set(resolved_item_compare "${resolved_item}") + set(resolved_embedded_item_compare "${resolved_embedded_item}") + endif() + + if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") + message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...") + else() + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") + if(UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") + endif() + endif() + +endfunction() + + +function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_item) + if(WIN32) + # ignore case on Windows + string(TOLOWER "${resolved_item}" resolved_item_compare) + string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) + else() + set(resolved_item_compare "${resolved_item}") + set(resolved_embedded_item_compare "${resolved_embedded_item}") + endif() + + if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") + message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...") + else() + if(BU_COPY_FULL_FRAMEWORK_CONTENTS) + # Full Framework (everything): + get_filename_component(resolved_dir "${resolved_item}" PATH) + get_filename_component(resolved_dir "${resolved_dir}/../.." ABSOLUTE) + get_filename_component(resolved_embedded_dir "${resolved_embedded_item}" PATH) + get_filename_component(resolved_embedded_dir "${resolved_embedded_dir}/../.." ABSOLUTE) + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_dir}' '${resolved_embedded_dir}'") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_dir}" "${resolved_embedded_dir}") + else() + # Framework lib itself: + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") + + # Plus Resources, if they exist: + string(REGEX REPLACE "^(.*)/[^/]+$" "\\1/Resources" resolved_resources "${resolved_item}") + string(REGEX REPLACE "^(.*)/[^/]+$" "\\1/Resources" resolved_embedded_resources "${resolved_embedded_item}") + if(EXISTS "${resolved_resources}") + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_resources}' '${resolved_embedded_resources}'") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_resources}" "${resolved_embedded_resources}") + endif() + + # Some frameworks e.g. Qt put Info.plist in wrong place, so when it is + # missing in resources, copy it from other well known incorrect locations: + if(NOT EXISTS "${resolved_resources}/Info.plist") + # Check for Contents/Info.plist in framework root (older Qt SDK): + string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Contents/Info.plist" resolved_info_plist "${resolved_item}") + string(REGEX REPLACE "^(.*)/[^/]+$" "\\1/Resources/Info.plist" resolved_embedded_info_plist "${resolved_embedded_item}") + if(EXISTS "${resolved_info_plist}") + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_info_plist}' '${resolved_embedded_info_plist}'") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_info_plist}" "${resolved_embedded_info_plist}") + endif() + endif() + + # Check if framework is versioned and fix it layout + string(REGEX REPLACE "^.*/([^/]+)/[^/]+$" "\\1" resolved_embedded_version "${resolved_embedded_item}") + string(REGEX REPLACE "^(.*)/[^/]+/[^/]+$" "\\1" resolved_embedded_versions "${resolved_embedded_item}") + string(REGEX REPLACE "^.*/([^/]+)/[^/]+/[^/]+$" "\\1" resolved_embedded_versions_basename "${resolved_embedded_item}") + if(resolved_embedded_versions_basename STREQUAL "Versions") + # Ensure Current symlink points to the framework version + if(NOT EXISTS "${resolved_embedded_versions}/Current") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "${resolved_embedded_version}" "${resolved_embedded_versions}/Current") + endif() + # Restore symlinks in framework root pointing to current framework + # binary and resources: + string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1" resolved_embedded_root "${resolved_embedded_item}") + string(REGEX REPLACE "^.*/([^/]+)$" "\\1" resolved_embedded_item_basename "${resolved_embedded_item}") + if(NOT EXISTS "${resolved_embedded_root}/${resolved_embedded_item_basename}") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "Versions/Current/${resolved_embedded_item_basename}" "${resolved_embedded_root}/${resolved_embedded_item_basename}") + endif() + if(NOT EXISTS "${resolved_embedded_root}/Resources") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "Versions/Current/Resources" "${resolved_embedded_root}/Resources") + endif() + endif() + endif() + if(UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") + endif() + endif() + +endfunction() + +function(is_resolved_item_embedded resolved_item exepath verbose is_embedded_var) + get_dotapp_dir("${exepath}" exe_dotapp_dir) + string(LENGTH "${exe_dotapp_dir}/" exe_dotapp_dir_length) + string(LENGTH "${resolved_item}" resolved_item_length) + set(path_too_short 0) + set(is_embedded 0) + if(${resolved_item_length} LESS ${exe_dotapp_dir_length}) + set(path_too_short 1) + endif() + if(NOT path_too_short) + string(SUBSTRING "${resolved_item}" 0 ${exe_dotapp_dir_length} item_substring) + if("${exe_dotapp_dir}/" STREQUAL "${item_substring}") + set(is_embedded 1) + endif() + endif() + if(verbose AND NOT is_embedded) + message(" exe_dotapp_dir/='${exe_dotapp_dir}/'") + message(" item_substring='${item_substring}'") + message(" resolved_item='${resolved_item}'") + message("") + endif() + set(${is_embedded_var} ${is_embedded} PARENT_SCOPE) +endfunction() + +function(fixup_bundle_item resolved_embedded_item exepath dirs) + # This item's key is "ikey": + # + get_item_key("${resolved_embedded_item}" ikey) + + # Ensure the item is "inside the .app bundle" -- it should not be fixed up if + # it is not in the .app bundle... Otherwise, we'll modify files in the build + # tree, or in other varied locations around the file system, with our call to + # install_name_tool. Make sure that doesn't happen here: + # + set(verbose 1) + is_resolved_item_embedded("${resolved_embedded_item}" "${exepath}" "${verbose}" is_embedded) + if(NOT is_embedded) + message("Install or copy the item into the bundle before calling fixup_bundle.") + message("Or maybe there's a typo or incorrect path in one of the args to fixup_bundle?") + message("") + message(FATAL_ERROR "cannot fixup an item that is not in the bundle...") + endif() + + set(prereqs "") + get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${exepath}" "${dirs}") + + set(changes "") + + foreach(pr ${prereqs}) + # Each referenced item's key is "rkey" in the loop: + # + get_item_key("${pr}" rkey) + + if(NOT "${${rkey}_EMBEDDED_ITEM}" STREQUAL "") + set(changes ${changes} "-change" "${pr}" "${${rkey}_EMBEDDED_ITEM}") + else() + message("warning: unexpected reference to '${pr}'") + endif() + endforeach() + + if(BU_CHMOD_BUNDLE_ITEMS) + execute_process(COMMAND chmod u+w "${resolved_embedded_item}") + endif() + + # Change this item's id and all of its references in one call + # to install_name_tool: + # + execute_process(COMMAND install_name_tool + ${changes} -id "${${ikey}_EMBEDDED_ITEM}" "${resolved_embedded_item}" + ) +endfunction() + + +function(fixup_bundle app libs dirs) + message(STATUS "fixup_bundle") + message(STATUS " app='${app}'") + message(STATUS " libs='${libs}'") + message(STATUS " dirs='${dirs}'") + + get_bundle_and_executable("${app}" bundle executable valid) + message(STATUS " bundle='${bundle}'") + message(STATUS " executable='${executable}'") + if(valid) + get_filename_component(exepath "${executable}" PATH) + + # TODO: Extract list of rpath dirs automatically. On MacOSX, the following could be + # done: otool -l path/to/executable | grep -A 3 LC_RPATH | grep path + # See http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html#comment-87ea054b4839586412727dcfc94c79d2 + set(GP_RPATH_DIR ${bundle}/Contents) + message(STATUS " GP_RPATH_DIR='${GP_RPATH_DIR}'") + + message(STATUS "fixup_bundle: preparing...") + get_bundle_keys("${app}" "${libs}" "${dirs}" keys) + + message(STATUS "fixup_bundle: copying...") + list(LENGTH keys n) + math(EXPR n ${n}*2) + + set(i 0) + foreach(key ${keys}) + math(EXPR i ${i}+1) + if(${${key}_COPYFLAG}) + message(STATUS "${i}/${n}: copying '${${key}_RESOLVED_ITEM}'") + else() + message(STATUS "${i}/${n}: *NOT* copying '${${key}_RESOLVED_ITEM}'") + endif() + + set(show_status 0) + if(show_status) + message(STATUS "key='${key}'") + message(STATUS "item='${${key}_ITEM}'") + message(STATUS "resolved_item='${${key}_RESOLVED_ITEM}'") + message(STATUS "default_embedded_path='${${key}_DEFAULT_EMBEDDED_PATH}'") + message(STATUS "embedded_item='${${key}_EMBEDDED_ITEM}'") + message(STATUS "resolved_embedded_item='${${key}_RESOLVED_EMBEDDED_ITEM}'") + message(STATUS "copyflag='${${key}_COPYFLAG}'") + message(STATUS "") + endif() + + if(${${key}_COPYFLAG}) + set(item "${${key}_ITEM}") + if(item MATCHES "[^/]+\\.framework/") + copy_resolved_framework_into_bundle("${${key}_RESOLVED_ITEM}" + "${${key}_RESOLVED_EMBEDDED_ITEM}") + else() + copy_resolved_item_into_bundle("${${key}_RESOLVED_ITEM}" + "${${key}_RESOLVED_EMBEDDED_ITEM}") + endif() + endif() + endforeach() + + message(STATUS "fixup_bundle: fixing...") + foreach(key ${keys}) + math(EXPR i ${i}+1) + if(APPLE) + message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'") + fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}") + else() + message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'") + endif() + endforeach() + + message(STATUS "fixup_bundle: cleaning up...") + clear_bundle_keys(keys) + + message(STATUS "fixup_bundle: verifying...") + verify_app("${app}") + else() + message(SEND_ERROR "error: fixup_bundle: not a valid bundle") + endif() + + message(STATUS "fixup_bundle: done") +endfunction() + + +function(copy_and_fixup_bundle src dst libs dirs) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${src}" "${dst}") + fixup_bundle("${dst}" "${libs}" "${dirs}") +endfunction() + + +function(verify_bundle_prerequisites bundle result_var info_var) + set(result 1) + set(info "") + set(count 0) + + get_bundle_main_executable("${bundle}" main_bundle_exe) + + file(GLOB_RECURSE file_list "${bundle}/*") + foreach(f ${file_list}) + is_file_executable("${f}" is_executable) + if(is_executable) + get_filename_component(exepath "${f}" PATH) + math(EXPR count "${count} + 1") + + message(STATUS "executable file ${count}: ${f}") + + set(prereqs "") + get_prerequisites("${f}" prereqs 1 1 "${exepath}" "") + + # On the Mac, + # "embedded" and "system" prerequisites are fine... anything else means + # the bundle's prerequisites are not verified (i.e., the bundle is not + # really "standalone") + # + # On Windows (and others? Linux/Unix/...?) + # "local" and "system" prereqs are fine... + # + set(external_prereqs "") + + foreach(p ${prereqs}) + set(p_type "") + gp_file_type("${f}" "${p}" p_type) + + if(APPLE) + if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system") + set(external_prereqs ${external_prereqs} "${p}") + endif() + else() + if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system") + set(external_prereqs ${external_prereqs} "${p}") + endif() + endif() + endforeach() + + if(external_prereqs) + # Found non-system/somehow-unacceptable prerequisites: + set(result 0) + set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n") + endif() + endif() + endforeach() + + if(result) + set(info "Verified ${count} executable files in '${bundle}'") + endif() + + set(${result_var} "${result}" PARENT_SCOPE) + set(${info_var} "${info}" PARENT_SCOPE) +endfunction() + + +function(verify_bundle_symlinks bundle result_var info_var) + set(result 1) + set(info "") + set(count 0) + + # TODO: implement this function for real... + # Right now, it is just a stub that verifies unconditionally... + + set(${result_var} "${result}" PARENT_SCOPE) + set(${info_var} "${info}" PARENT_SCOPE) +endfunction() + + +function(verify_app app) + set(verified 0) + set(info "") + + get_bundle_and_executable("${app}" bundle executable valid) + + message(STATUS "===========================================================================") + message(STATUS "Analyzing app='${app}'") + message(STATUS "bundle='${bundle}'") + message(STATUS "executable='${executable}'") + message(STATUS "valid='${valid}'") + + # Verify that the bundle does not have any "external" prerequisites: + # + verify_bundle_prerequisites("${bundle}" verified info) + message(STATUS "verified='${verified}'") + message(STATUS "info='${info}'") + message(STATUS "") + + if(verified) + # Verify that the bundle does not have any symlinks to external files: + # + verify_bundle_symlinks("${bundle}" verified info) + message(STATUS "verified='${verified}'") + message(STATUS "info='${info}'") + message(STATUS "") + endif() + + if(NOT verified) + message(FATAL_ERROR "error: verify_app failed") + endif() +endfunction() diff --git a/cmake/COPYING-CMAKE-SCRIPTS b/cmake/COPYING-CMAKE-SCRIPTS index d33c6f33b..21f1dbf7d 100644 --- a/cmake/COPYING-CMAKE-SCRIPTS +++ b/cmake/COPYING-CMAKE-SCRIPTS @@ -25,3 +25,269 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The following files are derived from the Slicer project +(https://github.com/Slicer/Slicer), which in turn derived from CMake project (http://cmake.org) and are covered under the licenses below. + +BundleUtilitiesWithRPath.cmake, GetPrerequisitesWithRPath.cmake + +# Slicer + +For more information, please see: + + http://www.slicer.org + +The 3D Slicer license below is a BSD style license, with extensions +to cover contributions and other issues specific to 3D Slicer. + + +3D Slicer Contribution and Software License Agreement ("Agreement") +Version 1.0 (December 20, 2005) + +This Agreement covers contributions to and downloads from the 3D +Slicer project ("Slicer") maintained by The Brigham and Women's +Hospital, Inc. ("Brigham"). Part A of this Agreement applies to +contributions of software and/or data to Slicer (including making +revisions of or additions to code and/or data already in Slicer). Part +B of this Agreement applies to downloads of software and/or data from +Slicer. Part C of this Agreement applies to all transactions with +Slicer. If you distribute Software (as defined below) downloaded from +Slicer, all of the paragraphs of Part B of this Agreement must be +included with and apply to such Software. + +Your contribution of software and/or data to Slicer (including prior +to the date of the first publication of this Agreement, each a +"Contribution") and/or downloading, copying, modifying, displaying, +distributing or use of any software and/or data from Slicer +(collectively, the "Software") constitutes acceptance of all of the +terms and conditions of this Agreement. If you do not agree to such +terms and conditions, you have no right to contribute your +Contribution, or to download, copy, modify, display, distribute or use +the Software. + +PART A. CONTRIBUTION AGREEMENT - License to Brigham with Right to +Sublicense ("Contribution Agreement"). + +1. As used in this Contribution Agreement, "you" means the individual + contributing the Contribution to Slicer and the institution or + entity which employs or is otherwise affiliated with such + individual in connection with such Contribution. + +2. This Contribution Agreement applies to all Contributions made to + Slicer, including without limitation Contributions made prior to + the date of first publication of this Agreement. If at any time you + make a Contribution to Slicer, you represent that (i) you are + legally authorized and entitled to make such Contribution and to + grant all licenses granted in this Contribution Agreement with + respect to such Contribution; (ii) if your Contribution includes + any patient data, all such data is de-identified in accordance with + U.S. confidentiality and security laws and requirements, including + but not limited to the Health Insurance Portability and + Accountability Act (HIPAA) and its regulations, and your disclosure + of such data for the purposes contemplated by this Agreement is + properly authorized and in compliance with all applicable laws and + regulations; and (iii) you have preserved in the Contribution all + applicable attributions, copyright notices and licenses for any + third party software or data included in the Contribution. + +3. Except for the licenses granted in this Agreement, you reserve all + right, title and interest in your Contribution. + +4. You hereby grant to Brigham, with the right to sublicense, a + perpetual, worldwide, non-exclusive, no charge, royalty-free, + irrevocable license to use, reproduce, make derivative works of, + display and distribute the Contribution. If your Contribution is + protected by patent, you hereby grant to Brigham, with the right to + sublicense, a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable license under your interest in patent + rights covering the Contribution, to make, have made, use, sell and + otherwise transfer your Contribution, alone or in combination with + any other code. + +5. You acknowledge and agree that Brigham may incorporate your + Contribution into Slicer and may make Slicer available to members + of the public on an open source basis under terms substantially in + accordance with the Software License set forth in Part B of this + Agreement. You further acknowledge and agree that Brigham shall + have no liability arising in connection with claims resulting from + your breach of any of the terms of this Agreement. + +6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION + DOES NOT CONTAIN ANY CODE THAT REQURES OR PRESCRIBES AN "OPEN + SOURCE LICENSE" FOR DERIVATIVE WORKS (by way of non-limiting + example, the GNU General Public License or other so-called + "reciprocal" license that requires any derived work to be licensed + under the GNU General Public License or other "open source + license"). + +PART B. DOWNLOADING AGREEMENT - License from Brigham with Right to +Sublicense ("Software License"). + +1. As used in this Software License, "you" means the individual + downloading and/or using, reproducing, modifying, displaying and/or + distributing the Software and the institution or entity which + employs or is otherwise affiliated with such individual in + connection therewith. The Brigham and Women?s Hospital, + Inc. ("Brigham") hereby grants you, with right to sublicense, with + respect to Brigham's rights in the software, and data, if any, + which is the subject of this Software License (collectively, the + "Software"), a royalty-free, non-exclusive license to use, + reproduce, make derivative works of, display and distribute the + Software, provided that: + +(a) you accept and adhere to all of the terms and conditions of this +Software License; + +(b) in connection with any copy of or sublicense of all or any portion +of the Software, all of the terms and conditions in this Software +License shall appear in and shall apply to such copy and such +sublicense, including without limitation all source and executable +forms and on any user documentation, prefaced with the following +words: "All or portions of this licensed product (such portions are +the "Software") have been obtained under license from The Brigham and +Women's Hospital, Inc. and are subject to the following terms and +conditions:" + +(c) you preserve and maintain all applicable attributions, copyright +notices and licenses included in or applicable to the Software; + +(d) modified versions of the Software must be clearly identified and +marked as such, and must not be misrepresented as being the original +Software; and + +(e) you consider making, but are under no obligation to make, the +source code of any of your modifications to the Software freely +available to others on an open source basis. + +2. The license granted in this Software License includes without + limitation the right to (i) incorporate the Software into + proprietary programs (subject to any restrictions applicable to + such programs), (ii) add your own copyright statement to your + modifications of the Software, and (iii) provide additional or + different license terms and conditions in your sublicenses of + modifications of the Software; provided that in each case your use, + reproduction or distribution of such modifications otherwise + complies with the conditions stated in this Software License. + +3. This Software License does not grant any rights with respect to + third party software, except those rights that Brigham has been + authorized by a third party to grant to you, and accordingly you + are solely responsible for (i) obtaining any permissions from third + parties that you need to use, reproduce, make derivative works of, + display and distribute the Software, and (ii) informing your + sublicensees, including without limitation your end-users, of their + obligations to secure any such required permissions. + +4. The Software has been designed for research purposes only and has + not been reviewed or approved by the Food and Drug Administration + or by any other agency. YOU ACKNOWLEDGE AND AGREE THAT CLINICAL + APPLICATIONS ARE NEITHER RECOMMENDED NOR ADVISED. Any + commercialization of the Software is at the sole risk of the party + or parties engaged in such commercialization. You further agree to + use, reproduce, make derivative works of, display and distribute + the Software in compliance with all applicable governmental laws, + regulations and orders, including without limitation those relating + to export and import control. + +5. The Software is provided "AS IS" and neither Brigham nor any + contributor to the software (each a "Contributor") shall have any + obligation to provide maintenance, support, updates, enhancements + or modifications thereto. BRIGHAM AND ALL CONTRIBUTORS SPECIFICALLY + DISCLAIM ALL EXPRESS AND IMPLIED WARRANTIES OF ANY KIND INCLUDING, + BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR + A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + BRIGHAM OR ANY CONTRIBUTOR BE LIABLE TO ANY PARTY FOR DIRECT, + INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ARISING IN ANY WAY + RELATED TO THE SOFTWARE, EVEN IF BRIGHAM OR ANY CONTRIBUTOR HAS + BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM + EXTENT NOT PROHIBITED BY LAW OR REGULATION, YOU FURTHER ASSUME ALL + LIABILITY FOR YOUR USE, REPRODUCTION, MAKING OF DERIVATIVE WORKS, + DISPLAY, LICENSE OR DISTRIBUTION OF THE SOFTWARE AND AGREE TO + INDEMNIFY AND HOLD HARMLESS BRIGHAM AND ALL CONTRIBUTORS FROM AND + AGAINST ANY AND ALL CLAIMS, SUITS, ACTIONS, DEMANDS AND JUDGMENTS + ARISING THEREFROM. + +6. None of the names, logos or trademarks of Brigham or any of + Brigham's affiliates or any of the Contributors, or any funding + agency, may be used to endorse or promote products produced in + whole or in part by operation of the Software or derived from or + based on the Software without specific prior written permission + from the applicable party. + +7. Any use, reproduction or distribution of the Software which is not + in accordance with this Software License shall automatically revoke + all rights granted to you under this Software License and render + Paragraphs 1 and 2 of this Software License null and void. + +8. This Software License does not grant any rights in or to any + intellectual property owned by Brigham or any Contributor except + those rights expressly granted hereunder. + +PART C. MISCELLANEOUS + +This Agreement shall be governed by and construed in accordance with +the laws of The Commonwealth of Massachusetts without regard to +principles of conflicts of law. This Agreement shall supercede and +replace any license terms that you may have agreed to previously with +respect to Slicer. + +# CMake + +CMake - Cross Platform Makefile Generator +Copyright 2000-2015 Kitware, Inc. +Copyright 2000-2011 Insight Software Consortium +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the names of Kitware, Inc., the Insight Software Consortium, + nor the names of their contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +The above copyright and license notice applies to distributions of +CMake in source and binary form. Some source files contain additional +notices of original copyright by their contributors; see each source +for details. Third-party software packages supplied with CMake under +compatible licenses provide their own copyright notices documented in +corresponding subdirectories. + +------------------------------------------------------------------------------ + +CMake was initially developed by Kitware with the following sponsorship: + + * National Library of Medicine at the National Institutes of Health + as part of the Insight Segmentation and Registration Toolkit (ITK). + + * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel + Visualization Initiative. + + * National Alliance for Medical Image Computing (NAMIC) is funded by the + National Institutes of Health through the NIH Roadmap for Medical Research, + Grant U54 EB005149. + + * Kitware, Inc. diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 320765b53..6e93a92ce 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -54,6 +54,7 @@ IF (WIN32) #Windows if ( MYGUI_STATIC ) set(LIB_SUFFIX "Static") + find_package(freetype) endif ( MYGUI_STATIC ) find_library ( MYGUI_LIBRARIES_REL NAMES MyGUIEngine${LIB_SUFFIX}.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) @@ -145,11 +146,12 @@ IF (MYGUI_FOUND) IF (NOT MYGUI_FIND_QUIETLY) MESSAGE(STATUS "MyGUI version: ${MYGUI_VERSION}") ENDIF (NOT MYGUI_FIND_QUIETLY) - -ELSE (MYGUI_FOUND) - IF (MYGUI_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find MYGUI") - ENDIF (MYGUI_FIND_REQUIRED) ENDIF (MYGUI_FOUND) +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MyGUI DEFAULT_MSG + MYGUI_INCLUDE_DIRS + FREETYPE_LIBRARIES + MYGUI_LIBRARIES) + CMAKE_POLICY(POP) diff --git a/cmake/GetPrerequisitesWithRPath.cmake b/cmake/GetPrerequisitesWithRPath.cmake new file mode 100644 index 000000000..5b5751ead --- /dev/null +++ b/cmake/GetPrerequisitesWithRPath.cmake @@ -0,0 +1,990 @@ +# - Functions to analyze and list executable file prerequisites. +# This module provides functions to list the .dll, .dylib or .so +# files that an executable or shared library file depends on. (Its +# prerequisites.) +# +# It uses various tools to obtain the list of required shared library files: +# dumpbin (Windows) +# objdump (MinGW on Windows) +# ldd (Linux/Unix) +# otool (Mac OSX) +# The following functions are provided by this module: +# get_prerequisites +# list_prerequisites +# list_prerequisites_by_glob +# gp_append_unique +# is_file_executable +# gp_item_default_embedded_path +# (projects can override with gp_item_default_embedded_path_override) +# gp_resolve_item +# (projects can override with gp_resolve_item_override) +# gp_resolve_embedded_item +# (projects can override with gp_resolve_embedded_item_override) +# gp_resolved_file_type +# (projects can override with gp_resolved_file_type_override) +# gp_file_type +# Requires CMake 2.6 or greater because it uses function, break, return and +# PARENT_SCOPE. +# +# GET_PREREQUISITES( +# ) +# Get the list of shared library files required by . The list in +# the variable named should be empty on first entry to +# this function. On exit, will contain the list of +# required shared library files. +# +# is the full path to an executable file. is the +# name of a CMake variable to contain the results. must be 0 +# or 1 indicating whether to include or exclude "system" prerequisites. If +# is set to 1 all prerequisites will be found recursively, if set to +# 0 only direct prerequisites are listed. is the path to the top +# level executable used for @executable_path replacment on the Mac. is +# a list of paths where libraries might be found: these paths are searched +# first when a target without any path info is given. Then standard system +# locations are also searched: PATH, Framework locations, /usr/lib... +# +# LIST_PREREQUISITES( [ [ []]]) +# Print a message listing the prerequisites of . +# +# is the name of a shared library or executable target or the full +# path to a shared library or executable file. If is set to 1 all +# prerequisites will be found recursively, if set to 0 only direct +# prerequisites are listed. must be 0 or 1 indicating whether +# to include or exclude "system" prerequisites. With set to 0 only +# the full path names of the prerequisites are printed, set to 1 extra +# informatin will be displayed. +# +# LIST_PREREQUISITES_BY_GLOB( ) +# Print the prerequisites of shared library and executable files matching a +# globbing pattern. is GLOB or GLOB_RECURSE and is a +# globbing expression used with "file(GLOB" or "file(GLOB_RECURSE" to retrieve +# a list of matching files. If a matching file is executable, its prerequisites +# are listed. +# +# Any additional (optional) arguments provided are passed along as the +# optional arguments to the list_prerequisites calls. +# +# GP_APPEND_UNIQUE( ) +# Append to the list variable only if the value is not +# already in the list. +# +# IS_FILE_EXECUTABLE( ) +# Return 1 in if is a binary executable, 0 otherwise. +# +# GP_IS_FILE_EXECUTABLE_EXCLUDE_REGEX can be set to a regular expression used +# to give a hint to identify more quickly if a given file is an executable or not. +# This is particularly useful on unix platform where it can avoid a lot of +# time-consuming call to "file" external process. For packages bundling hundreds +# of libraries, executables, resources and data, it largely speeds up the function +# "get_bundle_all_executables". +# On unix, a convenient command line allowing to collect recursively all file extensions +# useful to generate a regular expression like "\\.(dylib|py|pyc|so)$" is: +# find . -type f -name '*.*' | sed 's@.*/.*\.@@' | sort | uniq | tr "\\n" "|" +# +# GP_ITEM_DEFAULT_EMBEDDED_PATH( ) +# Return the path that others should refer to the item by when the item +# is embedded inside a bundle. +# +# Override on a per-project basis by providing a project-specific +# gp_item_default_embedded_path_override function. +# +# GP_RESOLVE_ITEM( ) +# Resolve an item into an existing full path file. +# +# Override on a per-project basis by providing a project-specific +# gp_resolve_item_override function. +# +# GP_RESOLVE_EMBEDDED_ITEM( ) +# Resolve an embedded item into the full path within the full path. Since the item can be +# copied later, it doesn't have to exist when calling this function. +# +# Override on a per-project basis by providing a project-specific +# gp_resolve_embedded_item_override function. +# +# If GP_RPATH_DIR variable is set then item matching '@rpath' are +# resolved using the provided directory. Currently setting this variable +# has an effect only on MacOSX when fixing up application bundle. The directory +# are also assumed to be located within the application bundle. It is +# usually the directory passed to the 'rpath' linker option. +# +# GP_RESOLVED_FILE_TYPE( ) +# Return the type of with respect to . String +# describing type of prerequisite is returned in variable named . +# +# Use and if necessary to resolve non-absolute +# values -- but only for non-embedded items. +# +# Possible types are: +# system +# local +# embedded +# other +# Override on a per-project basis by providing a project-specific +# gp_resolved_file_type_override function. +# +# GP_FILE_TYPE( ) +# Return the type of with respect to . String +# describing type of prerequisite is returned in variable named . +# +# Possible types are: +# system +# local +# embedded +# other + +#============================================================================= +# Copyright 2008-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +# Copyright (c) 2015 BWH and 3D Slicer contributors, http://slicer.org +# +# Redistribution AND use is allowed according to the terms of the +# BSD-style license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +function(gp_append_unique list_var value) + set(contains 0) + + foreach(item ${${list_var}}) + if("${item}" STREQUAL "${value}") + set(contains 1) + break() + endif() + endforeach() + + if(NOT contains) + set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE) + endif() +endfunction() + + +function(is_file_executable file result_var) + # + # A file is not executable until proven otherwise: + # + set(${result_var} 0 PARENT_SCOPE) + + get_filename_component(file_full "${file}" ABSOLUTE) + string(TOLOWER "${file_full}" file_full_lower) + + # If file name ends in .exe on Windows, *assume* executable: + # + if(WIN32 AND NOT UNIX) + if("${file_full_lower}" MATCHES "\\.exe$") + set(${result_var} 1 PARENT_SCOPE) + return() + endif() + + # A clause could be added here that uses output or return value of dumpbin + # to determine ${result_var}. In 99%+? practical cases, the exe name + # match will be sufficient... + # + endif() + + # Use the information returned from the Unix shell command "file" to + # determine if ${file_full} should be considered an executable file... + # + # If the file command's output contains "executable" and does *not* contain + # "text" then it is likely an executable suitable for prerequisite analysis + # via the get_prerequisites macro. + # + if(UNIX) + + if(NOT "${GP_IS_FILE_EXECUTABLE_EXCLUDE_REGEX}" STREQUAL "") + if(${file_full} MATCHES "${GP_IS_FILE_EXECUTABLE_EXCLUDE_REGEX}") + set(${result_var} 0 PARENT_SCOPE) + return() + endif() + endif() + + if(NOT file_cmd) + find_program(file_cmd "file") + mark_as_advanced(file_cmd) + endif() + + if(file_cmd) + execute_process(COMMAND "${file_cmd}" "${file_full}" + OUTPUT_VARIABLE file_ov + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Replace the name of the file in the output with a placeholder token + # (the string " _file_full_ ") so that just in case the path name of + # the file contains the word "text" or "executable" we are not fooled + # into thinking "the wrong thing" because the file name matches the + # other 'file' command output we are looking for... + # + string(REPLACE "${file_full}" " _file_full_ " file_ov "${file_ov}") + string(TOLOWER "${file_ov}" file_ov) + + #message(STATUS "file_ov='${file_ov}'") + if("${file_ov}" MATCHES "executable") + #message(STATUS "executable!") + if("${file_ov}" MATCHES "text") + #message(STATUS "but text, so *not* a binary executable!") + else() + set(${result_var} 1 PARENT_SCOPE) + return() + endif() + endif() + + # Also detect position independent executables on Linux, + # where "file" gives "shared object ... (uses shared libraries)" + if("${file_ov}" MATCHES "shared object.*\(uses shared libs\)") + set(${result_var} 1 PARENT_SCOPE) + return() + endif() + + else() + message(STATUS "warning: No 'file' command, skipping execute_process...") + endif() + endif() +endfunction() + + +function(gp_item_default_embedded_path item default_embedded_path_var) + + # On Windows and Linux, "embed" prerequisites in the same directory + # as the executable by default: + # + set(path "@executable_path") + set(overridden 0) + + # On the Mac, relative to the executable depending on the type + # of the thing we are embedding: + # + if(APPLE) + # + # The assumption here is that all executables in the bundle will be + # in same-level-directories inside the bundle. The parent directory + # of an executable inside the bundle should be MacOS or a sibling of + # MacOS and all embedded paths returned from here will begin with + # "@executable_path/../" and will work from all executables in all + # such same-level-directories inside the bundle. + # + + # By default, embed things right next to the main bundle executable: + # + set(path "@executable_path/../../Contents/MacOS") + + # Embed .dylibs right next to the main bundle executable: + # + if(item MATCHES "\\.dylib$") + set(path "@executable_path/../MacOS") + set(overridden 1) + endif() + + # Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS): + # + if(NOT overridden) + if(item MATCHES "[^/]+\\.framework/") + set(path "@executable_path/../Frameworks") + set(overridden 1) + endif() + endif() + endif() + + # Provide a hook so that projects can override the default embedded location + # of any given library by whatever logic they choose: + # + if(COMMAND gp_item_default_embedded_path_override) + gp_item_default_embedded_path_override("${item}" path) + endif() + + set(${default_embedded_path_var} "${path}" PARENT_SCOPE) +endfunction() + + +function(gp_resolve_item context item exepath dirs resolved_item_var) + set(resolved 0) + set(resolved_item "${item}") + + # Is it already resolved? + # + if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}") + set(resolved 1) + endif() + + if(NOT resolved) + if(item MATCHES "@executable_path") + # + # @executable_path references are assumed relative to exepath + # + string(REPLACE "@executable_path" "${exepath}" ri "${item}") + get_filename_component(ri "${ri}" ABSOLUTE) + + if(EXISTS "${ri}") + #message(STATUS "info: embedded item exists (${ri})") + set(resolved 1) + set(resolved_item "${ri}") + else() + message(STATUS "warning: embedded item does not exist '${ri}'") + endif() + endif() + endif() + + if(NOT resolved) + if(item MATCHES "@loader_path") + # + # @loader_path references are assumed relative to the + # PATH of the given "context" (presumably another library) + # + get_filename_component(contextpath "${context}" PATH) + string(REPLACE "@loader_path" "${contextpath}" ri "${item}") + get_filename_component(ri "${ri}" ABSOLUTE) + + if(EXISTS "${ri}") + #message(STATUS "info: embedded item exists (${ri})") + set(resolved 1) + set(resolved_item "${ri}") + else() + message(STATUS "warning: embedded item does not exist '${ri}'") + endif() + endif() + endif() + + if(NOT resolved) + if(item MATCHES "@rpath") + # + # @rpath references are relative to the paths built into the binaries with -rpath + # We handle this case like we do for other Unixes. + # + # Two cases of item resolution are considered: + # + # (1) item has been copied into the bundle + # + # (2) item has NOT been copied into the bundle: Since the item can exist in a build or + # install tree outside of the bundle, the item is resolved using its name and the + # passed list of directories. + # + string(REPLACE "@rpath/" "" norpath_item "${item}") + + set(ri "ri-NOTFOUND") + if(EXISTS ${GP_RPATH_DIR}/${norpath_item}) + set(ri ${GP_RPATH_DIR}/${norpath_item}) + set(_msg "'find_file' in GP_RPATH_DIR (${ri})") + else() + get_filename_component(norpath_item_name ${norpath_item} NAME) + find_file(ri "${norpath_item_name}" ${exepath} ${dirs} NO_DEFAULT_PATH) + set(_msg "'find_file' in exepath/dirs (${ri})") + endif() + if(ri) + #message(STATUS "info: ${_msg}") + set(resolved 1) + set(resolved_item "${ri}") + set(ri "ri-NOTFOUND") + endif() + + endif() + endif() + + if(NOT resolved) + set(ri "ri-NOTFOUND") + find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH) + find_file(ri "${item}" ${exepath} ${dirs} /usr/lib) + if(ri) + #message(STATUS "info: 'find_file' in exepath/dirs (${ri})") + set(resolved 1) + set(resolved_item "${ri}") + set(ri "ri-NOTFOUND") + endif() + endif() + + if(NOT resolved) + if(item MATCHES "[^/]+\\.framework/") + set(fw "fw-NOTFOUND") + find_file(fw "${item}" + "~/Library/Frameworks" + "/Library/Frameworks" + "/System/Library/Frameworks" + ) + if(fw) + #message(STATUS "info: 'find_file' found framework (${fw})") + set(resolved 1) + set(resolved_item "${fw}") + set(fw "fw-NOTFOUND") + endif() + endif() + endif() + + # Using find_program on Windows will find dll files that are in the PATH. + # (Converting simple file names into full path names if found.) + # + if(WIN32 AND NOT UNIX) + if(NOT resolved) + set(ri "ri-NOTFOUND") + find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH) + find_program(ri "${item}" PATHS "${exepath};${dirs}") + if(ri) + #message(STATUS "info: 'find_program' in exepath/dirs (${ri})") + set(resolved 1) + set(resolved_item "${ri}") + set(ri "ri-NOTFOUND") + endif() + endif() + endif() + + # Provide a hook so that projects can override item resolution + # by whatever logic they choose: + # + if(COMMAND gp_resolve_item_override) + gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved) + endif() + + if(NOT resolved) + message(STATUS " +warning: cannot resolve item '${item}' + + possible problems: + need more directories? + need to use InstallRequiredSystemLibraries? + run in install tree instead of build tree? +") +# message(STATUS " +#****************************************************************************** +#warning: cannot resolve item '${item}' +# +# possible problems: +# need more directories? +# need to use InstallRequiredSystemLibraries? +# run in install tree instead of build tree? +# +# context='${context}' +# item='${item}' +# exepath='${exepath}' +# dirs='${dirs}' +# resolved_item_var='${resolved_item_var}' +#****************************************************************************** +#") + endif() + + set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE) +endfunction() + +function(gp_resolve_embedded_item context embedded_item exepath resolved_embedded_item_var) + #message(STATUS "**") + set(resolved 0) + set(resolved_embedded_item "${embedded_item}") + + if(embedded_item MATCHES "@executable_path") + string(REPLACE "@executable_path" "${exepath}" resolved_embedded_item "${embedded_item}") + set(resolved 1) + endif() + if(EXISTS "${GP_RPATH_DIR}" AND embedded_item MATCHES "@rpath") + string(REPLACE "@rpath" "${GP_RPATH_DIR}" resolved_embedded_item "${embedded_item}") + set(resolved 1) + endif() + + # Provide a hook so that projects can override embedded item resolution + # by whatever logic they choose: + # + if(COMMAND gp_resolve_embedded_item_override) + gp_resolve_embedded_item_override( + "${context}" "${embedded_item}" "${exepath}" resolved_embedded_item resolved) + endif() + + if(NOT resolved) + message(STATUS " +warning: cannot resolve embedded item '${embedded_item}' + possible problems: + need more directories? + need to use InstallRequiredSystemLibraries? + run in install tree instead of build tree? + + context='${context}' + embedded_item='${embedded_item}' + GP_RPATH_DIR='${GP_RPATH_DIR}' + exepath='${exepath}' + resolved_embedded_item_var='${resolved_embedded_item_var}' +") + endif() + + set(${resolved_embedded_item_var} "${resolved_embedded_item}" PARENT_SCOPE) +endfunction() + +function(gp_resolved_file_type original_file file exepath dirs type_var) + #message(STATUS "**") + + if(NOT IS_ABSOLUTE "${original_file}") + message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file") + endif() + + set(is_embedded 0) + set(is_local 0) + set(is_system 0) + + set(resolved_file "${file}") + + if("${file}" MATCHES "^@(executable_|loader_|r)path") + set(is_embedded 1) + endif() + + if(NOT is_embedded) + if(NOT IS_ABSOLUTE "${file}") + gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file) + endif() + + string(TOLOWER "${original_file}" original_lower) + string(TOLOWER "${resolved_file}" lower) + + if(UNIX) + if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11/|/usr/X11R6/|/usr/bin/)") + set(is_system 1) + endif() + endif() + + if(APPLE) + if(resolved_file MATCHES "^(/System/Library/|/usr/lib/|/opt/X11/)") + set(is_system 1) + endif() + endif() + + if(WIN32) + string(TOLOWER "$ENV{SystemRoot}" sysroot) + string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}") + + string(TOLOWER "$ENV{windir}" windir) + string(REGEX REPLACE "\\\\" "/" windir "${windir}") + + if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)") + set(is_system 1) + endif() + + if(UNIX) + # if cygwin, we can get the properly formed windows paths from cygpath + find_program(CYGPATH_EXECUTABLE cygpath) + + if(CYGPATH_EXECUTABLE) + execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W + OUTPUT_VARIABLE env_windir + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S + OUTPUT_VARIABLE env_sysdir + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(TOLOWER "${env_windir}" windir) + string(TOLOWER "${env_sysdir}" sysroot) + + if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)") + set(is_system 1) + endif() + endif() + endif() + endif() + + if(NOT is_system) + get_filename_component(original_path "${original_lower}" PATH) + get_filename_component(path "${lower}" PATH) + if("${original_path}" STREQUAL "${path}") + set(is_local 1) + else() + string(LENGTH "${original_path}/" original_length) + string(LENGTH "${lower}" path_length) + if(${path_length} GREATER ${original_length}) + string(SUBSTRING "${lower}" 0 ${original_length} path) + if("${original_path}/" STREQUAL "${path}") + set(is_embedded 1) + endif() + endif() + endif() + endif() + endif() + + # Return type string based on computed booleans: + # + set(type "other") + + if(is_system) + set(type "system") + elseif(is_embedded) + set(type "embedded") + elseif(is_local) + set(type "local") + endif() + + #message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'") + #message(STATUS " type: '${type}'") + + if(NOT is_embedded) + if(NOT IS_ABSOLUTE "${resolved_file}") + if(lower MATCHES "^msvc[^/]+dll" AND is_system) + message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'") + else() + message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect") + endif() + endif() + endif() + + # Provide a hook so that projects can override the decision on whether a + # library belongs to the system or not by whatever logic they choose: + # + if(COMMAND gp_resolved_file_type_override) + gp_resolved_file_type_override("${resolved_file}" type) + endif() + + set(${type_var} "${type}" PARENT_SCOPE) + + #message(STATUS "**") +endfunction() + + +function(gp_file_type original_file file type_var) + if(NOT IS_ABSOLUTE "${original_file}") + message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file") + endif() + + get_filename_component(exepath "${original_file}" PATH) + + set(type "") + gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type) + + set(${type_var} "${type}" PARENT_SCOPE) +endfunction() + + +function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs) + set(verbose 0) + set(eol_char "E") + + if(NOT IS_ABSOLUTE "${target}") + message("warning: target '${target}' is not absolute...") + endif() + + if(NOT EXISTS "${target}") + message("warning: target '${target}' does not exist...") + endif() + + set(gp_cmd_paths ${gp_cmd_paths} + "C:/Program Files/Microsoft Visual Studio 9.0/VC/bin" + "C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin" + "C:/Program Files/Microsoft Visual Studio 8/VC/BIN" + "C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN" + "C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN" + "C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN" + "/usr/local/bin" + "/usr/bin" + ) + + # + # + # Try to choose the right tool by default. Caller can set gp_tool prior to + # calling this function to force using a different tool. + # + if("${gp_tool}" STREQUAL "") + set(gp_tool "ldd") + + if(APPLE) + set(gp_tool "otool") + endif() + + if(WIN32 AND NOT UNIX) # This is how to check for cygwin, har! + find_program(gp_dumpbin "dumpbin" PATHS ${gp_cmd_paths}) + if(gp_dumpbin) + set(gp_tool "dumpbin") + else() # Try harder. Maybe we're on MinGW + set(gp_tool "objdump") + endif() + endif() + endif() + + find_program(gp_cmd ${gp_tool} PATHS ${gp_cmd_paths}) + + if(NOT gp_cmd) + message(STATUS "warning: could not find '${gp_tool}' - cannot analyze prerequisites...") + return() + endif() + + set(gp_tool_known 0) + + if("${gp_tool}" STREQUAL "ldd") + set(gp_cmd_args "") + set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$") + set(gp_regex_error "not found${eol_char}$") + set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$") + set(gp_regex_cmp_count 1) + set(gp_tool_known 1) + endif() + + if("${gp_tool}" STREQUAL "otool") + set(gp_cmd_args "-L") + set(gp_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$") + set(gp_regex_error "") + set(gp_regex_fallback "") + set(gp_regex_cmp_count 3) + set(gp_tool_known 1) + endif() + + if("${gp_tool}" STREQUAL "dumpbin") + set(gp_cmd_args "/dependents") + set(gp_regex "^ ([^ ].*[Dd][Ll][Ll])${eol_char}$") + set(gp_regex_error "") + set(gp_regex_fallback "") + set(gp_regex_cmp_count 1) + set(gp_tool_known 1) + set(ENV{VS_UNICODE_OUTPUT} "") # Block extra output from inside VS IDE. + endif() + + if("${gp_tool}" STREQUAL "objdump") + set(gp_cmd_args "-p") + set(gp_regex "^\t*DLL Name: (.*\\.[Dd][Ll][Ll])${eol_char}$") + set(gp_regex_error "") + set(gp_regex_fallback "") + set(gp_regex_cmp_count 1) + set(gp_tool_known 1) + endif() + + if(NOT gp_tool_known) + message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...") + message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'") + message(STATUS "Valid gp_tool values are dumpbin, ldd, objdump and otool.") + return() + endif() + + + if("${gp_tool}" STREQUAL "dumpbin") + # When running dumpbin, it also needs the "Common7/IDE" directory in the + # PATH. It will already be in the PATH if being run from a Visual Studio + # command prompt. Add it to the PATH here in case we are running from a + # different command prompt. + # + get_filename_component(gp_cmd_dir "${gp_cmd}" PATH) + get_filename_component(gp_cmd_dlls_dir "${gp_cmd_dir}/../../Common7/IDE" ABSOLUTE) + # Use cmake paths as a user may have a PATH element ending with a backslash. + # This will escape the list delimiter and create havoc! + if(EXISTS "${gp_cmd_dlls_dir}") + # only add to the path if it is not already in the path + set(gp_found_cmd_dlls_dir 0) + file(TO_CMAKE_PATH "$ENV{PATH}" env_path) + foreach(gp_env_path_element ${env_path}) + if("${gp_env_path_element}" STREQUAL "${gp_cmd_dlls_dir}") + set(gp_found_cmd_dlls_dir 1) + endif() + endforeach() + + if(NOT gp_found_cmd_dlls_dir) + file(TO_NATIVE_PATH "${gp_cmd_dlls_dir}" gp_cmd_dlls_dir) + set(ENV{PATH} "$ENV{PATH};${gp_cmd_dlls_dir}") + endif() + endif() + endif() + # + # + + if("${gp_tool}" STREQUAL "ldd") + set(old_ld_env "$ENV{LD_LIBRARY_PATH}") + foreach(dir ${exepath} ${dirs}) + set(ENV{LD_LIBRARY_PATH} "${dir}:$ENV{LD_LIBRARY_PATH}") + endforeach() + endif() + + + # Track new prerequisites at each new level of recursion. Start with an + # empty list at each level: + # + set(unseen_prereqs) + + # Run gp_cmd on the target: + # + execute_process( + COMMAND ${gp_cmd} ${gp_cmd_args} ${target} + OUTPUT_VARIABLE gp_cmd_ov + ) + + if("${gp_tool}" STREQUAL "ldd") + set(ENV{LD_LIBRARY_PATH} "${old_ld_env}") + endif() + + if(verbose) + message(STATUS "") + message(STATUS "gp_cmd_ov='${gp_cmd_ov}'") + message(STATUS "") + endif() + + get_filename_component(target_dir "${target}" PATH) + + # Convert to a list of lines: + # + string(REGEX REPLACE ";" "\\\\;" candidates "${gp_cmd_ov}") + string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}") + + # check for install id and remove it from list, since otool -L can include a + # reference to itself + set(gp_install_id) + if("${gp_tool}" STREQUAL "otool") + execute_process( + COMMAND otool -D ${target} + OUTPUT_VARIABLE gp_install_id_ov + ) + # second line is install name + string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}") + if(gp_install_id) + # trim + string(REGEX MATCH "[^\n ].*[^\n ]" gp_install_id "${gp_install_id}") + #message("INSTALL ID is \"${gp_install_id}\"") + endif() + endif() + + # Analyze each line for file names that match the regular expression: + # + foreach(candidate ${candidates}) + if("${candidate}" MATCHES "${gp_regex}") + + # Extract information from each candidate: + if(gp_regex_error AND "${candidate}" MATCHES "${gp_regex_error}") + string(REGEX REPLACE "${gp_regex_fallback}" "\\1" raw_item "${candidate}") + else() + string(REGEX REPLACE "${gp_regex}" "\\1" raw_item "${candidate}") + endif() + + if(gp_regex_cmp_count GREATER 1) + string(REGEX REPLACE "${gp_regex}" "\\2" raw_compat_version "${candidate}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}") + endif() + + if(gp_regex_cmp_count GREATER 2) + string(REGEX REPLACE "${gp_regex}" "\\3" raw_current_version "${candidate}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}") + endif() + + # Use the raw_item as the list entries returned by this function. Use the + # gp_resolve_item function to resolve it to an actual full path file if + # necessary. + # + set(item "${raw_item}") + + # Add each item unless it is excluded: + # + set(add_item 1) + + if("${item}" STREQUAL "${gp_install_id}") + set(add_item 0) + endif() + + if(add_item AND ${exclude_system}) + set(type "") + gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type) + + if("${type}" STREQUAL "system") + set(add_item 0) + endif() + endif() + + if(add_item) + list(LENGTH ${prerequisites_var} list_length_before_append) + gp_append_unique(${prerequisites_var} "${item}") + list(LENGTH ${prerequisites_var} list_length_after_append) + + if(${recurse}) + # If item was really added, this is the first time we have seen it. + # Add it to unseen_prereqs so that we can recursively add *its* + # prerequisites... + # + # But first: resolve its name to an absolute full path name such + # that the analysis tools can simply accept it as input. + # + if(NOT list_length_before_append EQUAL list_length_after_append) + gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item) + set(unseen_prereqs ${unseen_prereqs} "${resolved_item}") + endif() + endif() + endif() + else() + if(verbose) + message(STATUS "ignoring non-matching line: '${candidate}'") + endif() + endif() + endforeach() + + list(LENGTH ${prerequisites_var} prerequisites_var_length) + if(prerequisites_var_length GREATER 0) + list(SORT ${prerequisites_var}) + endif() + if(${recurse}) + set(more_inputs ${unseen_prereqs}) + foreach(input ${more_inputs}) + get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}") + endforeach() + endif() + + set(${prerequisites_var} ${${prerequisites_var}} PARENT_SCOPE) +endfunction() + + +function(list_prerequisites target) + if("${ARGV1}" STREQUAL "") + set(all 1) + else() + set(all "${ARGV1}") + endif() + + if("${ARGV2}" STREQUAL "") + set(exclude_system 0) + else() + set(exclude_system "${ARGV2}") + endif() + + if("${ARGV3}" STREQUAL "") + set(verbose 0) + else() + set(verbose "${ARGV3}") + endif() + + set(count 0) + set(count_str "") + set(print_count "${verbose}") + set(print_prerequisite_type "${verbose}") + set(print_target "${verbose}") + set(type_str "") + + get_filename_component(exepath "${target}" PATH) + + set(prereqs "") + get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "") + + if(print_target) + message(STATUS "File '${target}' depends on:") + endif() + + foreach(d ${prereqs}) + math(EXPR count "${count} + 1") + + if(print_count) + set(count_str "${count}. ") + endif() + + if(print_prerequisite_type) + gp_file_type("${target}" "${d}" type) + set(type_str " (${type})") + endif() + + message(STATUS "${count_str}${d}${type_str}") + endforeach() +endfunction() + + +function(list_prerequisites_by_glob glob_arg glob_exp) + message(STATUS "=============================================================================") + message(STATUS "List prerequisites of executables matching ${glob_arg} '${glob_exp}'") + message(STATUS "") + file(${glob_arg} file_list ${glob_exp}) + foreach(f ${file_list}) + is_file_executable("${f}" is_f_executable) + if(is_f_executable) + message(STATUS "=============================================================================") + list_prerequisites("${f}" ${ARGN}) + message(STATUS "") + endif() + endforeach() +endfunction() diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index 0087461a1..0679e406c 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -21,4 +21,4 @@ else (SUCCESS) message(WARNING "Failed to get valid version information from Git") endif (SUCCESS) -configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) +configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3224d0989..00eac6ca5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,23 +1,23 @@ project (Components) # Version file -set (VERSION_HPP_IN ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake) -set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) +set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in") +set (VERSION_FILE "${OpenMW_BINARY_DIR}/resources/version") if (GIT_CHECKOUT) - add_custom_target (git-version - COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + add_custom_target (git-version + COMMAND ${CMAKE_COMMAND} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} + -DVERSION_IN_FILE=${VERSION_IN_FILE} + -DVERSION_FILE=${VERSION_FILE} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + VERBATIM) else (GIT_CHECKOUT) - configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) + configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) endif (GIT_CHECKOUT) find_package(OpenGL REQUIRED) @@ -41,11 +41,13 @@ add_component_dir (resource ) add_component_dir (sceneutil - clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller workqueue + clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller + # not used yet + #workqueue ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property nifkey data node base nifstream + controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream ) add_component_dir (nifosg @@ -180,6 +182,7 @@ target_link_libraries(components ${SDL2_LIBRARY} # For MyGUI platform ${OPENGL_gl_LIBRARY} + ${MYGUI_LIBRARIES} ) if (WIN32) @@ -200,7 +203,10 @@ if (GIT_CHECKOUT) endif (GIT_CHECKOUT) if (WIN32) -target_link_libraries(components shlwapi) + target_link_libraries(components shlwapi) + if(MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOGDI") + endif(MINGW) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index aefe6d16d..b202467db 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -1,4 +1,3 @@ - #include "controlparser.hpp" #include diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index 7961b8f41..ffac252d5 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -1,4 +1,3 @@ - #include "declarationparser.hpp" #include diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index 6028968bb..da114fb3d 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -1,4 +1,3 @@ - #include "discardparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index bcd30ef2d..a987a86da 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -1,4 +1,3 @@ - #include "errorhandler.hpp" namespace Compiler diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp index c92e7bb8d..ea904e385 100644 --- a/components/compiler/errorhandler.hpp +++ b/components/compiler/errorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_ERRORHANDLER_H_INCLUDED #define COMPILER_ERRORHANDLER_H_INCLUDED diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index dc36b58d8..b588b6196 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -1,4 +1,3 @@ - #include "exprparser.hpp" #include @@ -353,7 +352,10 @@ namespace Compiler if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { // pretend this is not a keyword - return parseName (loc.mLiteral, loc, scanner); + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); } } diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp index c2b11c615..dbb953e20 100644 --- a/components/compiler/extensions.cpp +++ b/components/compiler/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/components/compiler/extensions0.hpp b/components/compiler/extensions0.hpp index 83f3a44fa..e9ce6a6d6 100644 --- a/components/compiler/extensions0.hpp +++ b/components/compiler/extensions0.hpp @@ -44,7 +44,7 @@ namespace Compiler namespace Gui { - void registerExtensions (Extensions& extensions); + void registerExtensions (Extensions& extensions); } namespace Misc diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 423841ac3..8711d92fe 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -1,7 +1,5 @@ #include "fileparser.hpp" -#include - #include "tokenloc.hpp" #include "scanner.hpp" @@ -65,7 +63,6 @@ namespace Compiler if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; - scanner.allowNameStartingwithDigit(); return true; } @@ -112,7 +109,6 @@ namespace Compiler scanner.scan (mScriptParser); mState = EndNameState; - scanner.allowNameStartingwithDigit(); return true; } diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index ead0c7290..7e6437e20 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -1,4 +1,3 @@ - #include "generator.hpp" #include diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp index cfa94044e..7608e9bab 100644 --- a/components/compiler/junkparser.cpp +++ b/components/compiler/junkparser.cpp @@ -1,4 +1,3 @@ - #include "junkparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index a71672916..032af7d65 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -1,4 +1,3 @@ - #include "lineparser.hpp" #include @@ -222,6 +221,23 @@ namespace Compiler bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { + if (mState==MessageState || mState==MessageCommaState) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + } + if (mState==SetMemberVarState) { mMemberName = loc.mLiteral; diff --git a/components/compiler/literals.cpp b/components/compiler/literals.cpp index 626b03afb..ee2c4d345 100644 --- a/components/compiler/literals.cpp +++ b/components/compiler/literals.cpp @@ -1,4 +1,3 @@ - #include "literals.hpp" #include diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 60a5704bf..768fc077c 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include diff --git a/components/compiler/nullerrorhandler.cpp b/components/compiler/nullerrorhandler.cpp index ee2884705..a0db53a00 100644 --- a/components/compiler/nullerrorhandler.cpp +++ b/components/compiler/nullerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "nullerrorhandler.hpp" void Compiler::NullErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) {} diff --git a/components/compiler/nullerrorhandler.hpp b/components/compiler/nullerrorhandler.hpp index bb4db99a2..3dcff9250 100644 --- a/components/compiler/nullerrorhandler.hpp +++ b/components/compiler/nullerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_NULLERRORHANDLER_H_INCLUDED #define COMPILER_NULLERRORHANDLER_H_INCLUDED diff --git a/components/compiler/output.cpp b/components/compiler/output.cpp index 46e04b8dc..785b2ce84 100644 --- a/components/compiler/output.cpp +++ b/components/compiler/output.cpp @@ -1,4 +1,3 @@ - #include "output.hpp" #include diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 0f442c350..fe019718a 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/components/compiler/quickfileparser.cpp b/components/compiler/quickfileparser.cpp index 4e9f76e13..53aaf96e5 100644 --- a/components/compiler/quickfileparser.cpp +++ b/components/compiler/quickfileparser.cpp @@ -1,4 +1,3 @@ - #include "quickfileparser.hpp" #include "skipparser.hpp" diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 83d435962..f25e69e39 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -1,4 +1,3 @@ - #include "scanner.hpp" #include @@ -48,9 +47,6 @@ namespace Compiler bool Scanner::scanToken (Parser& parser) { - bool allowDigit = mNameStartingWithDigit; - mNameStartingWithDigit = false; - switch (mPutback) { case Putback_Special: @@ -115,7 +111,6 @@ namespace Compiler else if (isWhitespace (c)) { mLoc.mLiteral.clear(); - mNameStartingWithDigit = allowDigit; return true; } else if (c==':') @@ -124,7 +119,7 @@ namespace Compiler mLoc.mLiteral.clear(); return true; } - else if (std::isalpha (c) || c=='_' || c=='"' || (allowDigit && std::isdigit (c))) + else if (std::isalpha (c) || c=='_' || c=='"') { bool cont = false; @@ -180,10 +175,18 @@ namespace Compiler { value += c; } - else if (std::isalpha (c) || c=='_') - error = true; - else if (c=='.' && !error) + else if (isStringCharacter (c)) { + error = true; + value += c; + } + else if (c=='.') + { + if (error) + { + putback (c); + break; + } return scanFloat (value, parser, cont); } else @@ -194,7 +197,15 @@ namespace Compiler } if (error) - return false; + { + /// workaround that allows names to begin with digits + /// \todo disable + TokenLoc loc (mLoc); + mLoc.mLiteral.clear(); + cont = parser.parseName (value, loc, *this); + return true; +// return false; + } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); @@ -281,8 +292,10 @@ namespace Compiler if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') { name = name.substr (1, name.size()-2); - cont = parser.parseName (name, loc, *this); - return true; +// allow keywords enclosed in "" +/// \todo optionally disable +// cont = parser.parseName (name, loc, *this); +// return true; } int i = 0; @@ -554,8 +567,7 @@ namespace Compiler Scanner::Scanner (ErrorHandler& errorHandler, std::istream& inputStream, const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), - mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mNameStartingWithDigit (false) + mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0) { } @@ -607,9 +619,4 @@ namespace Compiler if (mExtensions) mExtensions->listKeywords (keywords); } - - void Scanner::allowNameStartingwithDigit() - { - mNameStartingWithDigit = true; - } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index ed01bbe44..847895978 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -124,9 +124,6 @@ namespace Compiler void listKeywords (std::vector& keywords); ///< Append all known keywords to \a kaywords. - - /// For the next token allow names to start with a digit. - void allowNameStartingwithDigit(); }; } diff --git a/components/compiler/scriptparser.cpp b/components/compiler/scriptparser.cpp index ea11be5f0..a3bf23288 100644 --- a/components/compiler/scriptparser.cpp +++ b/components/compiler/scriptparser.cpp @@ -1,4 +1,3 @@ - #include "scriptparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp index c7cb31f58..3e704253d 100644 --- a/components/compiler/skipparser.cpp +++ b/components/compiler/skipparser.cpp @@ -1,4 +1,3 @@ - #include "skipparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp index fc1a05943..9ca8aa74b 100644 --- a/components/compiler/streamerrorhandler.cpp +++ b/components/compiler/streamerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "streamerrorhandler.hpp" #include "tokenloc.hpp" diff --git a/components/compiler/streamerrorhandler.hpp b/components/compiler/streamerrorhandler.hpp index 96e02b588..85de1833a 100644 --- a/components/compiler/streamerrorhandler.hpp +++ b/components/compiler/streamerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED #define COMPILER_STREAMERRORHANDLER_H_INCLUDED diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index a86c15794..f8798eccd 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -1,12 +1,14 @@ - #include "stringparser.hpp" #include #include +#include + #include "scanner.hpp" #include "generator.hpp" -#include +#include "context.hpp" +#include "extensions.hpp" namespace Compiler { @@ -33,6 +35,25 @@ namespace Compiler return Parser::parseName (name, loc, scanner); } + bool StringParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + + return Parser::parseKeyword (keyword, loc, scanner); + } + bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_comma && mState==StartState) diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 3859a2496..52469128f 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -32,6 +32,10 @@ namespace Compiler ///< Handle a name token. /// \return fetch another token? + virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); + ///< Handle a keyword token. + /// \return fetch another token? + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); ///< Handle a special character token. /// \return fetch another token? diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp index 3c6e23ffd..f77e2eb55 100644 --- a/components/esm/cellid.cpp +++ b/components/esm/cellid.cpp @@ -1,4 +1,3 @@ - #include "cellid.hpp" #include "esmreader.hpp" @@ -35,3 +34,26 @@ bool ESM::operator!= (const CellId& left, const CellId& right) { return !(left==right); } + +bool ESM::operator < (const CellId& left, const CellId& right) +{ + if (left.mPaged < right.mPaged) + return true; + if (left.mPaged > right.mPaged) + return false; + + if (left.mPaged) + { + if (left.mIndex.mX < right.mIndex.mX) + return true; + if (left.mIndex.mX > right.mIndex.mX) + return false; + + if (left.mIndex.mY < right.mIndex.mY) + return true; + if (left.mIndex.mY > right.mIndex.mY) + return false; + } + + return left.mWorldspace < right.mWorldspace; +} diff --git a/components/esm/cellid.hpp b/components/esm/cellid.hpp index 44a1b387a..56d5a674e 100644 --- a/components/esm/cellid.hpp +++ b/components/esm/cellid.hpp @@ -26,6 +26,7 @@ namespace ESM bool operator== (const CellId& left, const CellId& right); bool operator!= (const CellId& left, const CellId& right); + bool operator< (const CellId& left, const CellId& right); } #endif diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index c3b889df5..33ac4a91e 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,4 +1,3 @@ - #include "cellref.hpp" #include "esmreader.hpp" diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp index 4df04d0e5..83b130dcd 100644 --- a/components/esm/cellstate.cpp +++ b/components/esm/cellstate.cpp @@ -1,4 +1,3 @@ - #include "cellstate.hpp" #include "esmreader.hpp" diff --git a/components/esm/containerstate.cpp b/components/esm/containerstate.cpp index 80ad5cbdc..301549d59 100644 --- a/components/esm/containerstate.cpp +++ b/components/esm/containerstate.cpp @@ -1,4 +1,3 @@ - #include "containerstate.hpp" void ESM::ContainerState::load (ESMReader &esm) diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp index c15becd98..bffa4e5e4 100644 --- a/components/esm/creaturestate.cpp +++ b/components/esm/creaturestate.cpp @@ -1,4 +1,3 @@ - #include "creaturestate.hpp" void ESM::CreatureState::load (ESMReader &esm) diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 6c05fac2a..9d605a6af 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -1,4 +1,3 @@ - #include "debugprofile.hpp" #include "esmreader.hpp" diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index f302e36dc..2b1887e4e 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -1,4 +1,3 @@ - #include "dialoguestate.hpp" #include "esmreader.hpp" diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index a80427bbe..5bc768f72 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include "esmreader.hpp" diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index 0129f8eb7..a42cdc230 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -1,4 +1,3 @@ - #include "globalscript.hpp" #include "esmreader.hpp" diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 4eaaa9f9f..e7257ae53 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -1,4 +1,3 @@ - #include "inventorystate.hpp" #include "esmreader.hpp" diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp index 445213de4..93011e581 100644 --- a/components/esm/journalentry.cpp +++ b/components/esm/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include "esmreader.hpp" diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 50c47349c..fb235e6b3 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -96,7 +96,12 @@ namespace ESM { mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } mTransport.save(esm); @@ -115,7 +120,7 @@ namespace ESM { for (int i=0; i<6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; mFlags = 0; - mScale = 0; + mScale = 1.f; mModel.clear(); mName.clear(); mScript.clear(); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index b0897ec67..784cfd407 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -1,5 +1,7 @@ #include "loadland.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -8,7 +10,7 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) +void Land::LandData::save(ESMWriter &esm) const { if (mDataTypes & Land::DATA_VNML) { esm.writeHNT("VNML", mNormals, sizeof(mNormals)); @@ -53,7 +55,7 @@ void Land::LandData::save(ESMWriter &esm) } } -void Land::LandData::transposeTextureData(uint16_t *in, uint16_t *out) +void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) { int readPos = 0; //bit ugly, but it works for ( int y1 = 0; y1 < 4; y1++ ) @@ -137,7 +139,7 @@ void Land::save(ESMWriter &esm) const esm.writeHNT("DATA", mFlags); } -void Land::loadData(int flags) +void Land::loadData(int flags) const { // Try to load only available data flags = flags & mDataTypes; @@ -199,7 +201,7 @@ void Land::unloadData() } } -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) +bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const { if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { mEsm->getHExact(ptr, size); @@ -215,4 +217,69 @@ bool Land::isDataLoaded(int flags) const return (mDataLoaded & flags) == (flags & mDataTypes); } + Land::Land (const Land& land) + : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), + mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mDataLoaded (land.mDataLoaded), + mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) + {} + + Land& Land::operator= (Land land) + { + swap (land); + return *this; + } + + void Land::swap (Land& land) + { + std::swap (mFlags, land.mFlags); + std::swap (mX, land.mX); + std::swap (mY, land.mY); + std::swap (mPlugin, land.mPlugin); + std::swap (mEsm, land.mEsm); + std::swap (mContext, land.mContext); + std::swap (mDataTypes, land.mDataTypes); + std::swap (mDataLoaded, land.mDataLoaded); + std::swap (mLandData, land.mLandData); + } + + const Land::LandData *Land::getLandData (int flags) const + { + if (!(flags & mDataTypes)) + return 0; + + loadData (flags); + return mLandData; + } + + const Land::LandData *Land::getLandData() const + { + return mLandData; + } + + Land::LandData *Land::getLandData() + { + return mLandData; + } + + void Land::add (int flags) + { + if (!mLandData) + mLandData = new LandData; + + mDataTypes |= flags; + mDataLoaded |= flags; + } + + void Land::remove (int flags) + { + mDataTypes &= ~flags; + mDataLoaded &= ~flags; + + if (!mDataLoaded) + { + delete mLandData; + mLandData = 0; + } + } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 61ce4855e..8ec4f74ea 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -35,7 +35,6 @@ struct Land ESM_Context mContext; int mDataTypes; - int mDataLoaded; enum { @@ -77,26 +76,36 @@ struct Land struct LandData { + // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset; + // Height in world space for each vertex float mHeights[LAND_NUM_VERTS]; + + // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; + + // 2D array of texture indices. An index can be used to look up an ESM::LandTexture, + // but to do so you must subtract 1 from the index first! + // An index of 0 indicates the default texture. uint16_t mTextures[LAND_NUM_TEXTURES]; + // 24-bit RGB color for each vertex unsigned char mColours[3 * LAND_NUM_VERTS]; + + // DataTypes available in this LandData, accessing data that is not available is an undefined operation int mDataTypes; // low-LOD heightmap (used for rendering the global map) signed char mWnam[81]; + // ??? short mUnk1; uint8_t mUnk2; - void save(ESMWriter &esm); - static void transposeTextureData(uint16_t *in, uint16_t *out); + void save(ESMWriter &esm) const; + static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - LandData *mLandData; - void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -105,7 +114,7 @@ struct Land /** * Actually loads data */ - void loadData(int flags); + void loadData(int flags) const; /** * Frees memory allocated for land data @@ -116,14 +125,41 @@ struct Land /// @note We only check data types that *can* be loaded (present in mDataTypes) bool isDataLoaded(int flags) const; + Land (const Land& land); + + Land& operator= (Land land); + + void swap (Land& land); + + /// Return land data with at least the data types specified in \a flags loaded (if they + /// are available). Will return a 0-pointer if there is no data for any of the + /// specified types. + const LandData *getLandData (int flags) const; + + /// Return land data without loading first anything. Can return a 0-pointer. + const LandData *getLandData() const; + + /// Return land data without loading first anything. Can return a 0-pointer. + LandData *getLandData(); + + /// \attention Must not be called on objects that aren't fully loaded. + /// + /// \note Added data fields will be uninitialised + void add (int flags); + + /// \attention Must not be called on objects that aren't fully loaded. + void remove (int flags); + private: - Land(const Land& land); - Land& operator=(const Land& land); /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size); + bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const; + + mutable int mDataLoaded; + + mutable LandData *mLandData; }; } diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 32b8a85a6..eeb4268c2 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -31,9 +31,9 @@ struct MagicEffect CastTouch = 0x80, // Allows range - cast on touch. CastTarget = 0x100, // Allows range - cast on target. 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. + 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. + CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. // Originally modifiable flags AllowSpellmaking = 0x200, // Can be used for spellmaking diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 44d298785..67a437176 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -121,7 +121,12 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 7d749c4d9..df35a2579 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -1,4 +1,3 @@ - #include "loadtes3.hpp" #include "esmcommon.hpp" diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp index f0cfd49f0..bd51be08f 100644 --- a/components/esm/locals.cpp +++ b/components/esm/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include "esmreader.hpp" diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp index 724d67326..6c9988d50 100644 --- a/components/esm/npcstate.cpp +++ b/components/esm/npcstate.cpp @@ -1,4 +1,3 @@ - #include "npcstate.hpp" void ESM::NpcState::load (ESMReader &esm) diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 0ae690ee8..62aa0452a 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -1,4 +1,3 @@ - #include "objectstate.hpp" #include "esmreader.hpp" diff --git a/components/esm/player.cpp b/components/esm/player.cpp index e64cefc30..9ec53240a 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -1,4 +1,3 @@ - #include "player.hpp" #include "esmreader.hpp" diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp index c8cff7adc..5408cd2ff 100644 --- a/components/esm/queststate.cpp +++ b/components/esm/queststate.cpp @@ -1,4 +1,3 @@ - #include "queststate.hpp" #include "esmreader.hpp" diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 9cdb28766..cf9f68c9a 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -1,4 +1,3 @@ - #include "savedgame.hpp" #include "esmreader.hpp" @@ -6,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 1; +int ESM::SavedGame::sCurrentFormat = 2; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index eeb0bf04f..aeea5017e 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -1,4 +1,3 @@ - #include "variantimp.hpp" #include diff --git a/components/esm/weatherstate.cpp b/components/esm/weatherstate.cpp index 48cf55a60..ff2528e58 100644 --- a/components/esm/weatherstate.cpp +++ b/components/esm/weatherstate.cpp @@ -1,59 +1,72 @@ -#include "weatherstate.hpp" - -#include "esmreader.hpp" -#include "esmwriter.hpp" - -namespace -{ - const char* hourRecord = "HOUR"; - const char* windSpeedRecord = "WNSP"; - const char* currentWeatherRecord = "CWTH"; - const char* nextWeatherRecord = "NWTH"; - const char* currentRegionRecord = "CREG"; - const char* firstUpdateRecord = "FUPD"; - const char* remainingTransitionTimeRecord = "RTTM"; - const char* timePassedRecord = "TMPS"; -} - -namespace ESM -{ - void WeatherState::load(ESMReader& esm) - { - // store values locally so that a failed load can't leave the state half set - float newHour = 0.0; - esm.getHNT(newHour, hourRecord); - float newWindSpeed = 0.0; - esm.getHNT(newWindSpeed, windSpeedRecord); - std::string newCurrentWeather = esm.getHNString(currentWeatherRecord); - std::string newNextWeather = esm.getHNString(nextWeatherRecord); - std::string newCurrentRegion = esm.getHNString(currentRegionRecord); - bool newFirstUpdate = false; - esm.getHNT(newFirstUpdate, firstUpdateRecord); - float newRemainingTransitionTime = 0.0; - esm.getHNT(newRemainingTransitionTime, remainingTransitionTimeRecord); - double newTimePassed = 0.0; - esm.getHNT(newTimePassed, timePassedRecord); - - // swap values now that it is safe to do so - mHour = newHour; - mWindSpeed = newWindSpeed; - mCurrentWeather.swap(newCurrentWeather); - mNextWeather.swap(newNextWeather); - mCurrentRegion.swap(newCurrentRegion); - mFirstUpdate = newFirstUpdate; - mRemainingTransitionTime = newRemainingTransitionTime; - mTimePassed = newTimePassed; - } - - void WeatherState::save(ESMWriter& esm) const - { - esm.writeHNT(hourRecord, mHour); - esm.writeHNT(windSpeedRecord, mWindSpeed); - esm.writeHNCString(currentWeatherRecord, mCurrentWeather.c_str()); - esm.writeHNCString(nextWeatherRecord, mNextWeather.c_str()); - esm.writeHNCString(currentRegionRecord, mCurrentRegion.c_str()); - esm.writeHNT(firstUpdateRecord, mFirstUpdate); - esm.writeHNT(remainingTransitionTimeRecord, mRemainingTransitionTime); - esm.writeHNT(timePassedRecord, mTimePassed); - } -} +#include "weatherstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace +{ + const char* currentRegionRecord = "CREG"; + const char* timePassedRecord = "TMPS"; + const char* fastForwardRecord = "FAST"; + const char* weatherUpdateTimeRecord = "WUPD"; + const char* transitionFactorRecord = "TRFC"; + const char* currentWeatherRecord = "CWTH"; + const char* nextWeatherRecord = "NWTH"; + const char* queuedWeatherRecord = "QWTH"; + const char* regionNameRecord = "RGNN"; + const char* regionWeatherRecord = "RGNW"; + const char* regionChanceRecord = "RGNC"; +} + +namespace ESM +{ + void WeatherState::load(ESMReader& esm) + { + mCurrentRegion = esm.getHNString(currentRegionRecord); + esm.getHNT(mTimePassed, timePassedRecord); + esm.getHNT(mFastForward, fastForwardRecord); + esm.getHNT(mWeatherUpdateTime, weatherUpdateTimeRecord); + esm.getHNT(mTransitionFactor, transitionFactorRecord); + esm.getHNT(mCurrentWeather, currentWeatherRecord); + esm.getHNT(mNextWeather, nextWeatherRecord); + esm.getHNT(mQueuedWeather, queuedWeatherRecord); + + while(esm.peekNextSub(regionNameRecord)) + { + std::string regionID = esm.getHNString(regionNameRecord); + RegionWeatherState region; + esm.getHNT(region.mWeather, regionWeatherRecord); + while(esm.peekNextSub(regionChanceRecord)) + { + char chance; + esm.getHNT(chance, regionChanceRecord); + region.mChances.push_back(chance); + } + + mRegions.insert(std::make_pair(regionID, region)); + } + } + + void WeatherState::save(ESMWriter& esm) const + { + esm.writeHNCString(currentRegionRecord, mCurrentRegion.c_str()); + esm.writeHNT(timePassedRecord, mTimePassed); + esm.writeHNT(fastForwardRecord, mFastForward); + esm.writeHNT(weatherUpdateTimeRecord, mWeatherUpdateTime); + esm.writeHNT(transitionFactorRecord, mTransitionFactor); + esm.writeHNT(currentWeatherRecord, mCurrentWeather); + esm.writeHNT(nextWeatherRecord, mNextWeather); + esm.writeHNT(queuedWeatherRecord, mQueuedWeather); + + std::map::const_iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + esm.writeHNCString(regionNameRecord, it->first.c_str()); + esm.writeHNT(regionWeatherRecord, it->second.mWeather); + for(size_t i = 0; i < it->second.mChances.size(); ++i) + { + esm.writeHNT(regionChanceRecord, it->second.mChances[i]); + } + } + } +} diff --git a/components/esm/weatherstate.hpp b/components/esm/weatherstate.hpp index d0cd59c16..532a056ac 100644 --- a/components/esm/weatherstate.hpp +++ b/components/esm/weatherstate.hpp @@ -1,27 +1,36 @@ -#ifndef OPENMW_ESM_WEATHERSTATE_H -#define OPENMW_ESM_WEATHERSTATE_H - -#include - -namespace ESM -{ - class ESMReader; - class ESMWriter; - - struct WeatherState - { - float mHour; - float mWindSpeed; - std::string mCurrentWeather; - std::string mNextWeather; - std::string mCurrentRegion; - bool mFirstUpdate; - float mRemainingTransitionTime; - double mTimePassed; - - void load(ESMReader& esm); - void save(ESMWriter& esm) const; - }; -} - -#endif +#ifndef OPENMW_ESM_WEATHERSTATE_H +#define OPENMW_ESM_WEATHERSTATE_H + +#include +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct RegionWeatherState + { + int mWeather; + std::vector mChances; + }; + + struct WeatherState + { + std::string mCurrentRegion; + float mTimePassed; + bool mFastForward; + float mWeatherUpdateTime; + float mTransitionFactor; + int mCurrentWeather; + int mNextWeather; + int mQueuedWeather; + std::map mRegions; + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; + }; +} + +#endif diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 1811f58e6..ccfe6d9ee 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -5,8 +5,6 @@ #include #include -#include - #include #include @@ -20,6 +18,14 @@ namespace ESMTerrain { } + const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags) + { + if (const ESM::Land *land = getLand (cellX, cellY)) + return land->getLandData (flags); + + return 0; + } + bool Storage::getMinMaxHeights(float size, const osg::Vec2f ¢er, float &min, float &max) { assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); @@ -34,24 +40,25 @@ namespace ESMTerrain int cellX = static_cast(origin.x()); int cellY = static_cast(origin.y()); - const ESM::Land* land = getLand(cellX, cellY); - if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) - return false; - - min = std::numeric_limits::max(); - max = -std::numeric_limits::max(); - for (int row=0; row::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; + for (int col=0; colmHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } } + return true; } - return true; + + return false; } void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row) @@ -76,12 +83,12 @@ namespace ESMTerrain --cellX; row += ESM::Land::LAND_SIZE-1; } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML)) { - 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.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.normalize(); } else @@ -111,12 +118,12 @@ namespace ESMTerrain ++cellX; row = 0; } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR)) { - 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; + color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } else { @@ -160,9 +167,9 @@ namespace ESMTerrain 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->mDataTypes&ESM::Land::DATA_VHGT)) - land = NULL; + const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); + const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); + const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR); int rowStart = 0; int colStart = 0; @@ -179,20 +186,22 @@ namespace ESMTerrain vertX = vertX_; for (int row=rowStart; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE + row]; + if (heightData) + height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; (*positions)[static_cast(vertX*numVerts + vertY)] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192, (vertY / float(numVerts - 1) - 0.5f) * size * 8192, height); - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + if (normalData) { - 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]; + for (int i=0; i<3; ++i) + normal[i] = normalData->mNormals[arrayIndex+i]; + normal.normalize(); } else @@ -210,11 +219,10 @@ namespace ESMTerrain (*normals)[static_cast(vertX*numVerts + vertY)] = normal; - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + if (colourData) { - 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; + for (int i=0; i<3; ++i) + color[i] = colourData->mColours[arrayIndex+i] / 255.f; } else { @@ -264,13 +272,12 @@ namespace ESMTerrain assert(xmDataTypes&ESM::Land::DATA_VTEX)) + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VTEX)) { - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + int tex = data->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); + return std::make_pair(tex, getLand (cellX, cellY)->mPlugin); } else return std::make_pair(0,0); @@ -370,7 +377,7 @@ namespace ESMTerrain int cellX = static_cast(std::floor(worldPos.x() / 8192.f)); int cellY = static_cast(std::floor(worldPos.y() / 8192.f)); - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) return -2048; @@ -449,7 +456,7 @@ namespace ESMTerrain { assert(x < ESM::Land::LAND_SIZE); assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x]; } Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 8f4a3aa92..5b8ca953d 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -21,12 +21,17 @@ namespace ESMTerrain private: // Not implemented in this class, because we need different Store implementations for game and editor - virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::Land* getLand (int cellX, int cellY)= 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: Storage(const VFS::Manager* vfs); + /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for + /// any of the data types specified via \a flags. Will also return a 0-pointer if there + /// is no land record for the coordinates \a cellX / \a cellY. + const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags); + // Not implemented in this class, because we need different Store implementations for game and editor /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 5f0062c2e..102f7c3cb 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -52,11 +52,11 @@ struct ConfigurationManager typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; - #if defined HAVE_UNORDERED_MAP - typedef std::unordered_map TokensMappingContainer; - #else - typedef std::tr1::unordered_map TokensMappingContainer; - #endif + #if defined HAVE_UNORDERED_MAP + typedef std::unordered_map TokensMappingContainer; + #else + typedef std::tr1::unordered_map TokensMappingContainer; + #endif void loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index 3e5d0c245..cd9a3c8f5 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -1,7 +1,6 @@ #include "constrainedfilestream.hpp" #include -#include #include "lowlevelfile.hpp" diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 1abbae3ae..7b3b0c440 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -1,4 +1,3 @@ - #include "multidircollection.hpp" #include diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index ece4049a8..4b7c6c50a 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -4,9 +4,9 @@ #include -#include #include #include +#include #include namespace bconv = boost::locale::conv; diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index d705a109c..31e911f8b 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -1,4 +1,3 @@ - #include "installopcodes.hpp" #include diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index ea1e9fc91..263cea9a5 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -1,8 +1,6 @@ - #include "interpreter.hpp" #include -#include #include #include diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index dc3da07a8..6599882f1 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -1,4 +1,3 @@ - #include "runtime.hpp" #include diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 4979d6451..160d659bd 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -119,7 +119,7 @@ class Drawable : public osg::Drawable { // VBOs disabled due to crash in OSG: http://forum.openscenegraph.org/viewtopic.php?t=14909 osg::GLBufferObject* bufferobject = 0;//state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : 0; - if (bufferobject) + if (0)//bufferobject) { state->bindVertexBufferObject(bufferobject); @@ -523,12 +523,8 @@ void RenderManager::destroyAllResources() bool RenderManager::checkTexture(MyGUI::ITexture* _texture) { - for (MapTexture::const_iterator item = mTextures.begin(); item != mTextures.end(); ++item) - { - if (item->second == _texture) - return true; - } - return false; + // We support external textures that aren't registered via this manager, so can't implement this method sensibly. + return true; } } diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 0a846b227..50ac5c1f3 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -87,6 +87,8 @@ namespace osgMyGUI throw std::runtime_error("No texturemanager set"); mTexture = mTextureManager->getTexture2D(fname, osg::Texture2D::CLAMP_TO_EDGE, osg::Texture2D::CLAMP_TO_EDGE); + // disable mip-maps + mTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR); // FIXME mFormat = MyGUI::PixelFormat::R8G8B8; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index adf961dc2..2496c68cd 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 5e7e55004..bb698ed63 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -353,6 +353,13 @@ AlphaController::AlphaController(const AlphaController ©, const osg::CopyOp { } +void AlphaController::setDefaults(osg::StateSet *stateset) +{ + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); +} + void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (hasInput()) @@ -380,6 +387,13 @@ MaterialColorController::MaterialColorController(const MaterialColorController & { } +void MaterialColorController::setDefaults(osg::StateSet *stateset) +{ + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); +} + void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (hasInput()) @@ -407,7 +421,8 @@ FlipController::FlipController(int texSlot, float delta, std::vectorsetFreezeOnCull(true); - osg::ref_ptr emitter = handleParticleEmitter(partctrl); - emitter->setParticleSystem(partsys); - emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); - - // Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph. - // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. - // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. - - FindRecIndexVisitor find (partctrl->emitter->recIndex); - rootNode->accept(find); - if (!find.mFound) + if (!partctrl->emitter.empty()) { - std::cerr << "can't find emitter node, wrong node order? in " << mFilename << std::endl; - return; + osg::ref_ptr emitter = handleParticleEmitter(partctrl); + emitter->setParticleSystem(partsys); + emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); + + // Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph. + // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. + // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. + + FindRecIndexVisitor find (partctrl->emitter->recIndex); + rootNode->accept(find); + if (!find.mFound) + { + std::cerr << "can't find emitter node, wrong node order? in " << mFilename << std::endl; + return; + } + osg::Group* emitterNode = find.mFound; + + // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node + // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! + emitterNode->addChild(emitter); + + osg::ref_ptr callback(new ParticleSystemController(partctrl)); + setupController(partctrl, callback, animflags); + emitter->setUpdateCallback(callback); } - osg::Group* emitterNode = find.mFound; - - // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node - // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! - emitterNode->addChild(emitter); - - osg::ref_ptr callback(new ParticleSystemController(partctrl)); - setupController(partctrl, callback, animflags); - emitter->setUpdateCallback(callback); // affectors must be attached *after* the emitter in the scene graph for correct update order // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct @@ -903,7 +906,7 @@ namespace NifOsg int uvSet = *it; if (uvSet >= (int)data->uvlist.size()) { - std::cerr << "Warning: using an undefined UV set " << uvSet << " on TriShape " << triShape->name << " in " << mFilename << std::endl; + std::cerr << "Warning: using an undefined UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; continue; } @@ -1159,11 +1162,11 @@ namespace NifOsg osg::FrontFace* frontFace = new osg::FrontFace; switch (stencilprop->data.drawMode) { - case 1: + case 2: frontFace->setMode(osg::FrontFace::CLOCKWISE); break; case 0: - case 2: + case 1: default: frontFace->setMode(osg::FrontFace::COUNTER_CLOCKWISE); break; @@ -1264,13 +1267,34 @@ namespace NifOsg { if (texprop->textures[i].inUse) { - if (i != Nif::NiTexturingProperty::BaseTexture - && i != Nif::NiTexturingProperty::GlowTexture - && i != Nif::NiTexturingProperty::DarkTexture - && i != Nif::NiTexturingProperty::DetailTexture) + switch(i) { - std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; - continue; + //These are handled later on + case Nif::NiTexturingProperty::BaseTexture: + case Nif::NiTexturingProperty::GlowTexture: + case Nif::NiTexturingProperty::DarkTexture: + case Nif::NiTexturingProperty::DetailTexture: + break; + case Nif::NiTexturingProperty::GlossTexture: + { + std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::BumpTexture: + { + std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::DecalTexture: + { + std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + default: + { + std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; + continue; + } } const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index efaccec13..08fa7bc9b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -100,11 +100,20 @@ namespace Resource Index::iterator it = mIndex.find(normalized); if (it == mIndex.end()) { - Files::IStreamPtr file = mVFS->get(normalized); - // TODO: add support for non-NIF formats + osg::ref_ptr loaded; + try + { + Files::IStreamPtr file = mVFS->get(normalized); - osg::ref_ptr loaded = NifOsg::Loader::load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)), mTextureManager); + loaded = NifOsg::Loader::load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)), mTextureManager); + } + catch (std::exception& e) + { + std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error.nif instead" << std::endl; + Files::IStreamPtr file = mVFS->get("meshes/marker_error.nif"); + loaded = NifOsg::Loader::load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)), mTextureManager); + } osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); // TODO: run SharedStateManager::prune on unload diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 1dabe45e0..168247a15 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -38,12 +38,16 @@ namespace Resource ~SceneManager(); /// Get a read-only copy of this scene "template" + /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. + /// If even the error marker mesh can not be found, an exception is thrown. osg::ref_ptr getTemplate(const std::string& name); /// Create an instance of the given scene template + /// @see getTemplate osg::ref_ptr createInstance(const std::string& name); /// Create an instance of the given scene template and immediately attach it to a parent node + /// @see getTemplate osg::ref_ptr createInstance(const std::string& name, osg::Group* parentNode); /// Attach the given scene instance to the given parent node diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp index 62cbd6bb3..c2f76a527 100644 --- a/components/resource/texturemanager.cpp +++ b/components/resource/texturemanager.cpp @@ -1,11 +1,21 @@ #include "texturemanager.hpp" #include +#include +#include #include #include +#ifdef OSG_LIBRARY_STATIC +// This list of plugins should match with the list in the top-level CMakelists.txt. +USE_OSGPLUGIN(png) +USE_OSGPLUGIN(tga) +USE_OSGPLUGIN(dds) +USE_OSGPLUGIN(jpeg) +#endif + namespace { @@ -64,7 +74,30 @@ namespace Resource for (std::map >::iterator it = mTextures.begin(); it != mTextures.end(); ++it) { osg::ref_ptr tex = it->second; - tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + + // Keep mip-mapping disabled if the texture creator explicitely requested no mipmapping. + osg::Texture::FilterMode oldMin = tex->getFilter(osg::Texture::MIN_FILTER); + if (oldMin == osg::Texture::LINEAR || oldMin == osg::Texture::NEAREST) + { + osg::Texture::FilterMode newMin = osg::Texture::LINEAR; + switch (mMinFilter) + { + case osg::Texture::LINEAR: + case osg::Texture::LINEAR_MIPMAP_LINEAR: + case osg::Texture::LINEAR_MIPMAP_NEAREST: + newMin = osg::Texture::LINEAR; + break; + case osg::Texture::NEAREST: + case osg::Texture::NEAREST_MIPMAP_LINEAR: + case osg::Texture::NEAREST_MIPMAP_NEAREST: + newMin = osg::Texture::NEAREST; + break; + } + tex->setFilter(osg::Texture::MIN_FILTER, newMin); + } + else + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(static_cast(mMaxAnisotropy)); } @@ -77,6 +110,39 @@ namespace Resource } */ + bool checkSupported(osg::Image* image, const std::string& filename) + { + switch(image->getPixelFormat()) + { + case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): + { +#if OSG_MIN_VERSION_REQUIRED(3,3,3) + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + if (exts && !exts->isTextureCompressionS3TCSupported + // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. + && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) +#else + osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false); + if (exts && !exts->isTextureCompressionS3TCSupported() + // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. + && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) +#endif + { + std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; + return false; + } + break; + } + // not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway + default: + return true; + } + return true; + } + osg::ref_ptr TextureManager::getTexture2D(const std::string &filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT) { std::string normalized = filename; @@ -116,11 +182,15 @@ namespace Resource osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); if (!result.success()) { - std::cerr << "Error loading " << filename << ": " << result.message() << std::endl; + std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; return mWarningTexture; } osg::Image* image = result.getImage(); + if (!checkSupported(image, filename)) + { + return mWarningTexture; + } // We need to flip images, because the Morrowind texture coordinates use the DirectX convention (top-left image origin), // but OpenGL uses bottom left as the image origin. diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 2432b5eb2..d8cfa428a 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -1,7 +1,6 @@ #include "attach.hpp" #include -#include #include #include diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 36c5c02a1..e4b4f63bb 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -52,6 +52,32 @@ namespace SceneUtil { osg::CopyOp copyop = *this; copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS); + + /* + + Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor: + + if ((copyop.getCopyFlags() & osg::CopyOp::DEEP_COPY_ARRAYS)) + { + if (_useVertexBufferObjects) + { + // copying of arrays doesn't set up buffer objects so we'll need to force + // Geometry to assign these, we'll do this by switching off VBO's then renabling them. + setUseVertexBufferObjects(false); + setUseVertexBufferObjects(true); + } + } + + In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from, + causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation. + + Possible fix submitted to osg-submissions ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ). + + */ + + copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES); + + osg::Drawable* cloned = osg::clone(drawable, copyop); if (cloned->getUpdateCallback()) cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 039d556d1..07ec0aff6 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,5 +1,7 @@ #include "lightmanager.hpp" +#include + #include #include @@ -9,9 +11,6 @@ #include -#include -#include - namespace SceneUtil { diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index a4fcd7866..37d08e025 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -6,7 +6,7 @@ namespace SceneUtil { - /// @brief Implements efficient pre-frame updating of StateSets. + /// @brief Implements efficient per-frame updating of StateSets. /// @par With a naive update there would be race conditions when the OSG draw thread of the last frame /// queues up a StateSet that we want to modify for the next frame. To solve this we could set the StateSet to /// DYNAMIC data variance but that would undo all the benefits of the threading model - having the cull and draw diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index a8a48f4f8..e1a67aff8 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -10,9 +10,16 @@ #include #include #include +#include #include "imagetosurface.hpp" +#ifdef OSG_LIBRARY_STATIC +// Sets the default windowing system interface according to the OS. +// Necessary for OpenSceneGraph to do some things, like decompression. +USE_GRAPHICSWINDOW() +#endif + namespace { @@ -119,7 +126,17 @@ namespace glViewport(0, 0, width, height); - osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3(-1,-1,0), osg::Vec3(2,0,0), osg::Vec3(0,2,0)); + osg::ref_ptr geom; + +#if defined(__APPLE__) + // Extra flip needed on Intel graphics OS X systems due to a driver bug + std::string vendorString = (const char*)glGetString(GL_VENDOR); + if (vendorString.find("Intel") != std::string::npos) + geom = osg::createTexturedQuadGeometry(osg::Vec3(-1,1,0), osg::Vec3(2,0,0), osg::Vec3(0,-2,0)); + else +#endif + geom = osg::createTexturedQuadGeometry(osg::Vec3(-1,-1,0), osg::Vec3(2,0,0), osg::Vec3(0,2,0)); + geom->drawImplementation(renderInfo); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, resultImage->data()); @@ -175,23 +192,16 @@ namespace SDLUtil } } - bool SDLCursorManager::cursorChanged(const std::string& name) + void SDLCursorManager::cursorChanged(const std::string& name) { mCurrentCursor = name; CursorMap::const_iterator curs_iter = mCursorMap.find(name); - //we have this cursor if(curs_iter != mCursorMap.end()) { + //we have this cursor _setGUICursor(name); - - return false; - } - else - { - //they should get back to us with more info - return true; } } @@ -200,7 +210,7 @@ namespace SDLUtil SDL_SetCursor(mCursorMap.find(name)->second); } - void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) { _createCursorFromResource(name, rotDegrees, image, size_x, size_y, hotspot_x, hotspot_y); } diff --git a/components/sdlutil/sdlcursormanager.hpp b/components/sdlutil/sdlcursormanager.hpp index 646f548e3..0db578039 100644 --- a/components/sdlutil/sdlcursormanager.hpp +++ b/components/sdlutil/sdlcursormanager.hpp @@ -27,11 +27,9 @@ namespace SDLUtil /// \brief Tell the manager that the cursor has changed, giving the /// name of the cursor we changed to ("arrow", "ibeam", etc) - /// \return Whether the manager is interested in more information about the cursor - virtual bool cursorChanged(const std::string &name); + virtual void cursorChanged(const std::string &name); - /// \brief Follow up a cursorChanged() call with enough info to create an cursor. - virtual void receiveCursorInfo(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); private: void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index f607b7046..200d19bd8 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -138,6 +138,16 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v break; case SDL_CLIPBOARDUPDATE: break; // We don't need this event, clipboard is retrieved on demand + + case SDL_FINGERDOWN: + case SDL_FINGERUP: + case SDL_FINGERMOTION: + case SDL_DOLLARGESTURE: + case SDL_DOLLARRECORD: + case SDL_MULTIGESTURE: + // No use for touch & gesture events + break; + default: std::ios::fmtflags f(std::cerr.flags()); std::cerr << "Unhandled SDL event of type 0x" << std::hex << evt.type << std::endl; diff --git a/components/sdlutil/sdlinputwrapper.hpp b/components/sdlutil/sdlinputwrapper.hpp index bdb5842ae..a821b9012 100644 --- a/components/sdlutil/sdlinputwrapper.hpp +++ b/components/sdlutil/sdlinputwrapper.hpp @@ -30,8 +30,8 @@ namespace SDLUtil void setControllerEventCallback(ControllerListener* listen) { mConListener = listen; } void capture(bool windowEventsOnly); - bool isModifierHeld(SDL_Keymod mod); - bool isKeyDown(SDL_Scancode key); + bool isModifierHeld(SDL_Keymod mod); + bool isKeyDown(SDL_Scancode key); void setMouseVisible (bool visible); void setMouseRelative(bool relative); diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 2034883ed..df45b6ec2 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,7 +1,5 @@ #include "material.hpp" -#include - #include #include #include diff --git a/components/version/version.cpp b/components/version/version.cpp new file mode 100644 index 000000000..c87943f9e --- /dev/null +++ b/components/version/version.cpp @@ -0,0 +1,41 @@ +#include "version.hpp" + +#include +#include + +namespace Version +{ + +Version getOpenmwVersion(const std::string &resourcePath) +{ + boost::filesystem::path path (resourcePath + "/version"); + + boost::filesystem::ifstream stream (path); + + Version v; + std::getline(stream, v.mVersion); + std::getline(stream, v.mCommitHash); + std::getline(stream, v.mTagHash); + return v; +} + +std::string Version::describe() +{ + std::string str = "OpenMW version " + mVersion; + std::string rev = mCommitHash; + std::string tag = mTagHash; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0, 10); + str += "\nRevision: " + rev; + } + return str; +} + +std::string getOpenmwVersionDescription(const std::string &resourcePath) +{ + Version v = getOpenmwVersion(resourcePath); + return v.describe(); +} + +} diff --git a/components/version/version.hpp b/components/version/version.hpp new file mode 100644 index 000000000..7371e786e --- /dev/null +++ b/components/version/version.hpp @@ -0,0 +1,28 @@ +#ifndef VERSION_HPP +#define VERSION_HPP + +#include + +namespace Version +{ + + struct Version + { + std::string mVersion; + std::string mCommitHash; + std::string mTagHash; + + std::string describe(); + }; + + /// Read OpenMW version from the version file located in resourcePath. + Version getOpenmwVersion(const std::string& resourcePath); + + /// Helper function to getOpenmwVersion and describe() it + std::string getOpenmwVersionDescription(const std::string& resourcePath); + +} + + +#endif // VERSION_HPP + diff --git a/components/version/version.hpp.cmake b/components/version/version.hpp.cmake deleted file mode 100644 index 4cdfa32f0..000000000 --- a/components/version/version.hpp.cmake +++ /dev/null @@ -1,13 +0,0 @@ -#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@" - -#define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@" -#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@" - -#endif // VERSION_HPP - diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 961746947..25ad60e0a 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -1,3 +1,6 @@ +#ifndef VFS_BSAARCHIVE_HPP_ +#define VFS_BSAARCHIVE_HPP_ + #include "archive.hpp" #include @@ -30,3 +33,5 @@ namespace VFS }; } + +#endif \ No newline at end of file diff --git a/files/mac/Info.plist b/files/mac/openmw-Info.plist.in similarity index 95% rename from files/mac/Info.plist rename to files/mac/openmw-Info.plist.in index f832b09c9..efd55ee5c 100644 --- a/files/mac/Info.plist +++ b/files/mac/openmw-Info.plist.in @@ -26,5 +26,7 @@ NSHumanReadableCopyright + NSHighResolutionCapable + diff --git a/files/mac/openmw-cs-Info.plist.in b/files/mac/openmw-cs-Info.plist.in new file mode 100644 index 000000000..684ad7908 --- /dev/null +++ b/files/mac/openmw-cs-Info.plist.in @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSHighResolutionCapable + + + diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 602397743..dc9e8ea84 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,4 +1,3 @@ - # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index dd114097e..97b018469 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -122,8 +122,7 @@ - - + diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index 4e6349768..33199d6ae 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -31,5 +31,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index f4c7142ce..c4d88ff85 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -3,10 +3,12 @@ - + + + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index e74038391..682d89ebc 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -128,6 +128,14 @@ + + + + + + + + diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml new file mode 100644 index 000000000..1c16cb9a4 --- /dev/null +++ b/files/openmw.appdata.xml @@ -0,0 +1,39 @@ + + + + openmw.desktop + CC0-1.0 + GPL-3.0 and MIT and zlib + OpenMW +

    Unofficial open source engine re-implementation of the game Morrowind + +

    + OpenMW is a new engine for 2002's Game of the Year, The Elder Scrolls 3: Morrowind. +

    +

    + It aims to be a fully playable (and improved!), open source implementation of the game's engine and functionality (including mods). +

    +

    + You will still need the original game data to play OpenMW. +

    +
    + + + + http://wiki.openmw.org/images/b/b2/Openmw_0.11.1_launcher_1.png + The OpenMW launcher + + + http://wiki.openmw.org/images/f/f1/Screenshot_mournhold_plaza_0.35.png + The Mournhold's plaza on OpenMW + + + http://wiki.openmw.org/images/5/5b/Screenshot_Vivec_seen_from_Ebonheart_0.35.png + Vivec seen from Ebonheart on OpenMW + + +nobrakal@gmail.com + https://openmw.org + https://bugs.openmw.org/ + https://openmw.org/faq/ + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 0581d7356..274a31315 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -20,6 +20,9 @@ vsync = false gamma = 1.00 contrast = 1.00 +# Maximum framerate in frames per second, 0 = unlimited +framerate limit = 0 + [GUI] scaling factor = 1.0 @@ -36,6 +39,10 @@ werewolf overlay = true stretch menu background = false +# colour definitions (red green blue alpha) +color background owned = 0.15 0 0 1 +color crosshair owned = 1 0.15 0.15 1 + [General] # Camera field of view field of view = 55 @@ -97,7 +104,7 @@ local map widget size = 512 local map hud widget size = 256 [Cells] -exterior grid size = 3 +exterior cell load distance = 1 [Camera] near clip = 5 @@ -162,10 +169,21 @@ best attack = false difficulty = 0 +# Change crosshair/toolTip color when pointing on owned object +#0: nothing changed +#1: tint toolTip +#2: tint crosshair +#3: both +show owned = 0 +# Show the remaining duration of magic effects and lights +show effect duration = false + [Saves] character = # Save when resting autosave = true +# display time played +timeplayed = false [Windows] inventory x = 0 diff --git a/files/version.in b/files/version.in new file mode 100644 index 000000000..f894d3b4a --- /dev/null +++ b/files/version.in @@ -0,0 +1,3 @@ +@OPENMW_VERSION@ +@OPENMW_VERSION_COMMITHASH@ +@OPENMW_VERSION_TAGHASH@