diff --git a/.gitignore b/.gitignore index 08bf0bad62..944fb84181 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ resources /omwlauncher /openmw /opencs +/niftest ## generated objects apps/openmw/config.hpp diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..50ff0928df --- /dev/null +++ b/.mailmap @@ -0,0 +1,139 @@ +Adam Hogan +Aleksandar Jovanov +Alexander Olofsson +Alex McKibben +Alex "rainChu" Haddad +Ardekantur +Armin Preiml +Artem Kotsynyak +Arthur Moore +Arthur Moore +athile +athile +Stefan Galowicz +Bret Curtis +Britt Mathis +Sandy Carter +Sandy Carter +Carl Maxwell +cc9cii +Cory F. Cohen +Chris Robinson +Cris Mihalache +darkf +Diggory Hardy +Thomas Luppi +Thomas Luppi +Dmitriy 'Endorph' Shkurskiy +Dmitry Marakasov +Douglas Diniz +Douglas Mencken +Edmondo Tommasina +Eduard Cot +Eli2 +Emanuel Guével +Leon Saunders +Fil Krynicki +John Blomberg +Gašper Sedej +Michał Bień +Joel Graff +Paul McElroy +Artem Kotsynyak +Artem Kotsynyak +gugus +guidoj +gus +Hallfaer Tuilinn +Julian Ospald +Jacob Essex +Jan Borsodi +Jan-Peter Nilsson +Jason Hooks +Jason Hooks +Jason Hooks +Jeffrey Haines +Jeffrey Haines +Jordan Ayers +Jordan Milne +Josua Grawitter +Julien Voisin +Karl-Felix Glatzer +Chris Robinson +Kevin Poitra +Roman Proskuryakov +Lars Söderberg +lazydev +lazydev +Lukasz Gromanowski +Marc Bouvier +Marcin Hulist +Marc Zinnschlag +Marek Kochanowicz +Marek Kochanowicz +Marek Kochanowicz +Mark Siewert +Mark Siewert +megaton <9megaton6@gmail.com> +Michael Mc Donnell +Michael Papageorgiou +Michal Sciubidlo +Michał Ściubidło +Nathan Jeffords +Nicolay Korslund +Nicolay Korslund +Nikolay Kasyanov +pchan3 +Pieter van der Kloet +Mateusz Kołaczek +Bret Curtis +Pieter van der Kloet +Rohit Nirmal +Roman Melnik +Radu-Marius Popovici +Sandy Carter +Scott Howard +Jannik Heller +Jannik Heller +Sebastian Wick +Sebastian Wick +Sergey Shambir +sergoz +Chris Boyce +Star-Demon +Sylvain Thesnieres +Thomas Luppi +Thomas Luppi +Thoronador +TomKoenderink +Tom Mason +Torben Carrington +Vincent Heuken +Manuel Edelmann +Manuel Edelmann +Alexander Nadeau +Michael Hogan +Jacob Essex +Yuri Krupenin +Bret Curtis + +sirherrbatka +sirherrbatka +sergei +riothamus +nobrakal +nkorslund +mrcheko +Miroslav Puda +MiroslavR +mckibbenta +jeaye +eroen +eroen +dreamer-dead +crysthala +Berulacks +Axujen +root +unknown + diff --git a/.travis.yml b/.travis.yml index e09fa46dc7..9308ccf871 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,10 @@ before_install: - 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 - sudo apt-get update -qq - - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock - - sudo apt-get install -qq libqt4-dev - - sudo apt-get install -qq libopenal-dev - - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev - - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev + - sudo 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 libboost-wave-dev + - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev + - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 @@ -25,7 +24,7 @@ before_script: - cd - - mkdir build - cd build - - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1 + - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE script: - make -j4 after_script: diff --git a/CMakeLists.txt b/CMakeLists.txt index cb9a54a6cd..542ea3e94e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 31) +set(OPENMW_VERSION_MINOR 32) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -54,6 +54,10 @@ endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) +if (ANDROID) + set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") +endif (ANDROID) + # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/docs/mainpage.hpp") @@ -66,16 +70,15 @@ option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) # Apps and tools -option(BUILD_BSATOOL "build BSA extractor" OFF) +option(BUILD_BSATOOL "build BSA extractor" ON) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) option(BUILD_OPENCS "build OpenMW Construction Set" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) -option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) - -# Sound source selection -option(USE_FFMPEG "use ffmpeg for sound" ON) +option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest and GMock frameworks" 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 option(OPENMW_OSX_DEPLOYMENT OFF) @@ -101,33 +104,32 @@ cmake_minimum_required(VERSION 2.6) # source directory: libs -set(LIBDIR ${CMAKE_SOURCE_DIR}/libs) +set(LIBS_DIR ${CMAKE_SOURCE_DIR}/libs) set(OENGINE_OGRE - ${LIBDIR}/openengine/ogre/renderer.cpp - ${LIBDIR}/openengine/ogre/fader.cpp - ${LIBDIR}/openengine/ogre/lights.cpp - ${LIBDIR}/openengine/ogre/selectionbuffer.cpp - ${LIBDIR}/openengine/ogre/imagerotate.cpp + ${LIBS_DIR}/openengine/ogre/renderer.cpp + ${LIBS_DIR}/openengine/ogre/lights.cpp + ${LIBS_DIR}/openengine/ogre/selectionbuffer.cpp + ${LIBS_DIR}/openengine/ogre/imagerotate.cpp ) set(OENGINE_GUI - ${LIBDIR}/openengine/gui/loglistener.cpp - ${LIBDIR}/openengine/gui/manager.cpp - ${LIBDIR}/openengine/gui/layout.hpp + ${LIBS_DIR}/openengine/gui/loglistener.cpp + ${LIBS_DIR}/openengine/gui/manager.cpp + ${LIBS_DIR}/openengine/gui/layout.hpp ) set(OENGINE_BULLET - ${LIBDIR}/openengine/bullet/BtOgre.cpp - ${LIBDIR}/openengine/bullet/BtOgreExtras.h - ${LIBDIR}/openengine/bullet/BtOgreGP.h - ${LIBDIR}/openengine/bullet/BtOgrePG.h - ${LIBDIR}/openengine/bullet/physic.cpp - ${LIBDIR}/openengine/bullet/physic.hpp - ${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp - ${LIBDIR}/openengine/bullet/BulletShapeLoader.h - ${LIBDIR}/openengine/bullet/trace.cpp - ${LIBDIR}/openengine/bullet/trace.h + ${LIBS_DIR}/openengine/bullet/BtOgre.cpp + ${LIBS_DIR}/openengine/bullet/BtOgreExtras.h + ${LIBS_DIR}/openengine/bullet/BtOgreGP.h + ${LIBS_DIR}/openengine/bullet/BtOgrePG.h + ${LIBS_DIR}/openengine/bullet/physic.cpp + ${LIBS_DIR}/openengine/bullet/physic.hpp + ${LIBS_DIR}/openengine/bullet/BulletShapeLoader.cpp + ${LIBS_DIR}/openengine/bullet/BulletShapeLoader.h + ${LIBS_DIR}/openengine/bullet/trace.cpp + ${LIBS_DIR}/openengine/bullet/trace.h ) @@ -138,32 +140,24 @@ set(OPENMW_LIBS ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup -set(GOT_SOUND_INPUT 0) -set(SOUND_INPUT_INCLUDES "") -set(SOUND_INPUT_LIBRARY "") -set(SOUND_DEFINE "") -if (USE_FFMPEG) - set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) - find_package(FFmpeg) - if (FFMPEG_FOUND) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) - set(GOT_SOUND_INPUT 1) - endif (FFMPEG_FOUND) -endif (USE_FFMPEG) - -if (NOT GOT_SOUND_INPUT) - message(WARNING "--------------------") - message(WARNING "Failed to find any sound input packages") - message(WARNING "--------------------") -endif (NOT GOT_SOUND_INPUT) - -if (NOT FFMPEG_FOUND) - message(WARNING "--------------------") - message(WARNING "FFmpeg not found, video playback will be disabled") - message(WARNING "--------------------") -endif (NOT FFMPEG_FOUND) +set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE) +unset(FFMPEG_LIBRARIES CACHE) +find_package(FFmpeg) +if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) + message(FATAL_ERROR "FFmpeg component required, but not found!") +endif() +set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIRS}) +set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) +if( SWRESAMPLE_FOUND ) + add_definitions(-DHAVE_LIBSWRESAMPLE) + set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) +else() + if( AVRESAMPLE_FOUND ) + set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) + else() + message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") + endif() +endif() # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) @@ -227,7 +221,15 @@ IF(BOOST_STATIC) endif() find_package(OGRE REQUIRED) +if (${OGRE_VERSION} VERSION_LESS "1.9") + message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org") +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") +endif() + find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(SDL2 REQUIRED) find_package(OpenAL REQUIRED) @@ -258,7 +260,12 @@ if(OGRE_STATIC) list(APPEND OGRE_STATIC_PLUGINS ${Cg_LIBRARIES}) endif(Cg_FOUND) +if (ANDROID) + add_static_ogre_plugin(RenderSystem_GLES2) +else () add_static_ogre_plugin(RenderSystem_GL) +endif () + if(WIN32) add_static_ogre_plugin(RenderSystem_Direct3D9) endif(WIN32) @@ -272,7 +279,7 @@ include_directories("." ${MYGUI_INCLUDE_DIRS} ${MYGUI_PLATFORM_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} - ${LIBDIR} + ${LIBS_DIR} ) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR}) @@ -325,12 +332,14 @@ else () add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") endif() -add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") -add_definitions(-DOGRE_PLUGIN_DIR_DBG="${OGRE_PLUGIN_DIR_DBG}") if (APPLE AND OPENMW_OSX_DEPLOYMENT) # make it empty so plugin loading code can check this and try to find plugins inside app bundle add_definitions(-DOGRE_PLUGIN_DIR="") else() + if (NOT DEFINED ${OGRE_PLUGIN_DIR}) + set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) + endif() + add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") endif() @@ -342,8 +351,10 @@ add_subdirectory(files/mygui) if (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") else (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") endif (APPLE) # Other files @@ -377,9 +388,6 @@ endif() if (CMAKE_COMPILER_IS_GNUCC) SET(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused-parameter -Wno-reorder -std=c++98 -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}") - # Silence warnings in OGRE headers. Remove once OGRE got fixed! - SET(CMAKE_CXX_FLAGS "-Wno-ignored-qualifiers ${CMAKE_CXX_FLAGS}") - execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) @@ -391,11 +399,18 @@ IF(NOT WIN32 AND NOT APPLE) # Linux building # Paths SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") + SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Where to install libraries") SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") + SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") - SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") + IF("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr") + SET(GLOBAL_CONFIG_PATH "/etc/" CACHE PATH "Set config dir prefix") + ELSE() + SET(GLOBAL_CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/etc/" CACHE PATH "Set config dir prefix") + ENDIF() + SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir") # Install binaries INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) @@ -414,6 +429,12 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENCS) + IF(BUILD_NIFTEST) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_NIFTEST) + if(BUILD_MYGUI_PLUGIN) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Plugin_MyGUI_OpenMW_Resources.so" DESTINATION "${LIBDIR}" ) + ENDIF(BUILD_MYGUI_PLUGIN) # Install licenses INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) @@ -463,6 +484,9 @@ if(WIN32) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".") ENDIF(BUILD_OPENCS) + if(BUILD_MYGUI_PLUGIN) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION ".") + ENDIF(BUILD_MYGUI_PLUGIN) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") @@ -492,8 +516,8 @@ if(WIN32) SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe") - SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.ico") - SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.ico") + SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") + SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe") @@ -530,6 +554,16 @@ add_subdirectory (extern/sdl4ogre) # Components add_subdirectory (components) +# Plugins +if (BUILD_MYGUI_PLUGIN) + add_subdirectory(plugins/mygui_resource_plugin) +endif() + +#Testing +if (BUILD_NIFTEST) + add_subdirectory(components/nif/tests/) +endif(BUILD_NIFTEST) + # Apps and tools add_subdirectory( apps/openmw ) @@ -567,6 +601,16 @@ endif() if (WIN32) if (MSVC) + if (MULTITHREADED_BUILD) + set( MT_BUILD "/MP") + endif (MULTITHREADED_BUILD) + + foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) + string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) + set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" ) + set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(ProjectDir)$(Configuration)" ) + endforeach( OUTPUTCONFIG ) + if (USE_DEBUG_CONSOLE) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") @@ -607,6 +651,9 @@ if (WIN32) 4251 # class 'XXXX' needs to have dll-interface to be used by clients of class 'YYYY' 4275 # non dll-interface struct 'XXXX' used as base for dll-interface class 'YYYY' + # caused by boost + 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off) + # OpenMW specific warnings 4099 # Type mismatch, declared class or struct is defined with other type 4100 # Unreferenced formal parameter (-Wunused-parameter) @@ -630,30 +677,32 @@ if (WIN32) # boost::wave has a few issues with signed / unsigned conversions, so we suppress those here set(SHINY_WARNINGS "${WARNINGS} /wd4245") - set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${SHINY_WARNINGS}) + set_target_properties(shiny PROPERTIES COMPILE_FLAGS "${SHINY_WARNINGS} ${MT_BUILD}") # there's an unreferenced local variable in the ogre platform, suppress it set(SHINY_OGRE_WARNINGS "${WARNINGS} /wd4101") - set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${SHINY_OGRE_WARNINGS}) - set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS "${SHINY_OGRE_WARNINGS} ${MT_BUILD}") + set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") # oics uses tinyxml, which has an initialized but unused variable set(OICS_WARNINGS "${WARNINGS} /wd4189") - set_target_properties(oics PROPERTIES COMPILE_FLAGS ${OICS_WARNINGS}) - set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(oics PROPERTIES COMPILE_FLAGS "${OICS_WARNINGS} ${MT_BUILD}") + set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") if (BUILD_LAUNCHER) - set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_LAUNCHER) - set_target_properties(openmw PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") if (BUILD_BSATOOL) - set_target_properties(bsatool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_BSATOOL) if (BUILD_ESMTOOL) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_ESMTOOL) if (BUILD_OPENCS) - set_target_properties(opencs PROPERTIES COMPILE_FLAGS ${WARNINGS}) + # QT triggers an informational warning that the object layout may differ when compiled with /vd2 + set(OPENCS_WARNINGS "${WARNINGS} ${MT_BUILD} /wd4435") + set_target_properties(opencs PROPERTIES COMPILE_FLAGS ${OPENCS_WARNINGS}) endif (BUILD_OPENCS) if (BUILD_MWINIIMPORTER) - set_target_properties(mwiniimport PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(mwiniimport PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_MWINIIMPORTER) endif(MSVC) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index eef96c8c99..ea908590a4 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -249,6 +249,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl; std::cout << " ID: '" << ref.mRefID << "'\n"; std::cout << " Owner: '" << ref.mOwner << "'\n"; + std::cout << " Global: '" << ref.mGlobalVariable << "'" << std::endl; + std::cout << " Faction: '" << ref.mFaction << "'" << std::endl; + std::cout << " Faction rank: '" << ref.mFactionRank << "'" << std::endl; std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n"; std::cout << " Uses/health: '" << ref.mCharge << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index ef45989efe..9543628f51 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -18,137 +18,143 @@ std::string bodyPartLabel(int idx) { - const char *bodyPartLabels[] = { - "Head", - "Hair", - "Neck", - "Cuirass", - "Groin", - "Skirt", - "Right Hand", - "Left Hand", - "Right Wrist", - "Left Wrist", - "Shield", - "Right Forearm", - "Left Forearm", - "Right Upperarm", - "Left Upperarm", - "Right Foot", - "Left Foot", - "Right Ankle", - "Left Ankle", - "Right Knee", - "Left Knee", - "Right Leg", - "Left Leg", - "Right Shoulder", - "Left Shoulder", - "Weapon", - "Tail" - }; - if (idx >= 0 && idx <= 26) + { + const char *bodyPartLabels[] = { + "Head", + "Hair", + "Neck", + "Cuirass", + "Groin", + "Skirt", + "Right Hand", + "Left Hand", + "Right Wrist", + "Left Wrist", + "Shield", + "Right Forearm", + "Left Forearm", + "Right Upperarm", + "Left Upperarm", + "Right Foot", + "Left Foot", + "Right Ankle", + "Left Ankle", + "Right Knee", + "Left Knee", + "Right Leg", + "Left Leg", + "Right Shoulder", + "Left Shoulder", + "Weapon", + "Tail" + }; return bodyPartLabels[idx]; + } else return "Invalid"; } std::string meshPartLabel(int idx) { - const char *meshPartLabels[] = { - "Head", - "Hair", - "Neck", - "Chest", - "Groin", - "Hand", - "Wrist", - "Forearm", - "Upperarm", - "Foot", - "Ankle", - "Knee", - "Upper Leg", - "Clavicle", - "Tail" - }; - if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail) + { + const char *meshPartLabels[] = { + "Head", + "Hair", + "Neck", + "Chest", + "Groin", + "Hand", + "Wrist", + "Forearm", + "Upperarm", + "Foot", + "Ankle", + "Knee", + "Upper Leg", + "Clavicle", + "Tail" + }; return meshPartLabels[idx]; + } else return "Invalid"; } std::string meshTypeLabel(int idx) { - const char *meshTypeLabels[] = { - "Skin", - "Clothing", - "Armor" - }; - if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor) + { + const char *meshTypeLabels[] = { + "Skin", + "Clothing", + "Armor" + }; return meshTypeLabels[idx]; + } else return "Invalid"; } std::string clothingTypeLabel(int idx) { - const char *clothingTypeLabels[] = { - "Pants", - "Shoes", - "Shirt", - "Belt", - "Robe", - "Right Glove", - "Left Glove", - "Skirt", - "Ring", - "Amulet" - }; - if (idx >= 0 && idx <= 9) + { + const char *clothingTypeLabels[] = { + "Pants", + "Shoes", + "Shirt", + "Belt", + "Robe", + "Right Glove", + "Left Glove", + "Skirt", + "Ring", + "Amulet" + }; return clothingTypeLabels[idx]; + } else return "Invalid"; } std::string armorTypeLabel(int idx) -{ - const char *armorTypeLabels[] = { - "Helmet", - "Cuirass", - "Left Pauldron", - "Right Pauldron", - "Greaves", - "Boots", - "Left Gauntlet", - "Right Gauntlet", - "Shield", - "Left Bracer", - "Right Bracer" - }; - +{ if (idx >= 0 && idx <= 10) + { + const char *armorTypeLabels[] = { + "Helmet", + "Cuirass", + "Left Pauldron", + "Right Pauldron", + "Greaves", + "Boots", + "Left Gauntlet", + "Right Gauntlet", + "Shield", + "Left Bracer", + "Right Bracer" + }; return armorTypeLabels[idx]; + } else return "Invalid"; } std::string dialogTypeLabel(int idx) { - const char *dialogTypeLabels[] = { - "Topic", - "Voice", - "Greeting", - "Persuasion", - "Journal" - }; - if (idx >= 0 && idx <= 4) + { + const char *dialogTypeLabels[] = { + "Topic", + "Voice", + "Greeting", + "Persuasion", + "Journal" + }; return dialogTypeLabels[idx]; + } else if (idx == -1) return "Deleted"; else @@ -157,75 +163,79 @@ std::string dialogTypeLabel(int idx) std::string questStatusLabel(int idx) { - const char *questStatusLabels[] = { - "None", - "Name", - "Finished", - "Restart", - "Deleted" - }; - if (idx >= 0 && idx <= 4) + { + const char *questStatusLabels[] = { + "None", + "Name", + "Finished", + "Restart", + "Deleted" + }; return questStatusLabels[idx]; + } else return "Invalid"; } std::string creatureTypeLabel(int idx) { - const char *creatureTypeLabels[] = { - "Creature", - "Daedra", - "Undead", - "Humanoid", - }; - if (idx >= 0 && idx <= 3) + { + const char *creatureTypeLabels[] = { + "Creature", + "Daedra", + "Undead", + "Humanoid", + }; return creatureTypeLabels[idx]; + } else return "Invalid"; } std::string soundTypeLabel(int idx) { - const char *soundTypeLabels[] = { - "Left Foot", - "Right Foot", - "Swim Left", - "Swim Right", - "Moan", - "Roar", - "Scream", - "Land" - }; - if (idx >= 0 && idx <= 7) + { + const char *soundTypeLabels[] = { + "Left Foot", + "Right Foot", + "Swim Left", + "Swim Right", + "Moan", + "Roar", + "Scream", + "Land" + }; return soundTypeLabels[idx]; + } else return "Invalid"; } std::string weaponTypeLabel(int idx) { - const char *weaponTypeLabels[] = { - "Short Blade One Hand", - "Long Blade One Hand", - "Long Blade Two Hand", - "Blunt One Hand", - "Blunt Two Close", - "Blunt Two Wide", - "Spear Two Wide", - "Axe One Hand", - "Axe Two Hand", - "Marksman Bow", - "Marksman Crossbow", - "Marksman Thrown", - "Arrow", - "Bolt" - }; - if (idx >= 0 && idx <= 13) + { + const char *weaponTypeLabels[] = { + "Short Blade One Hand", + "Long Blade One Hand", + "Long Blade Two Hand", + "Blunt One Hand", + "Blunt Two Close", + "Blunt Two Wide", + "Spear Two Wide", + "Axe One Hand", + "Axe Two Hand", + "Marksman Bow", + "Marksman Crossbow", + "Marksman Thrown", + "Arrow", + "Bolt" + }; return weaponTypeLabels[idx]; + } else return "Invalid"; } @@ -242,377 +252,397 @@ std::string aiTypeLabel(int type) std::string magicEffectLabel(int idx) { - const char* magicEffectLabels [] = { - "Water Breathing", - "Swift Swim", - "Water Walking", - "Shield", - "Fire Shield", - "Lightning Shield", - "Frost Shield", - "Burden", - "Feather", - "Jump", - "Levitate", - "SlowFall", - "Lock", - "Open", - "Fire Damage", - "Shock Damage", - "Frost Damage", - "Drain Attribute", - "Drain Health", - "Drain Magicka", - "Drain Fatigue", - "Drain Skill", - "Damage Attribute", - "Damage Health", - "Damage Magicka", - "Damage Fatigue", - "Damage Skill", - "Poison", - "Weakness to Fire", - "Weakness to Frost", - "Weakness to Shock", - "Weakness to Magicka", - "Weakness to Common Disease", - "Weakness to Blight Disease", - "Weakness to Corprus Disease", - "Weakness to Poison", - "Weakness to Normal Weapons", - "Disintegrate Weapon", - "Disintegrate Armor", - "Invisibility", - "Chameleon", - "Light", - "Sanctuary", - "Night Eye", - "Charm", - "Paralyze", - "Silence", - "Blind", - "Sound", - "Calm Humanoid", - "Calm Creature", - "Frenzy Humanoid", - "Frenzy Creature", - "Demoralize Humanoid", - "Demoralize Creature", - "Rally Humanoid", - "Rally Creature", - "Dispel", - "Soultrap", - "Telekinesis", - "Mark", - "Recall", - "Divine Intervention", - "Almsivi Intervention", - "Detect Animal", - "Detect Enchantment", - "Detect Key", - "Spell Absorption", - "Reflect", - "Cure Common Disease", - "Cure Blight Disease", - "Cure Corprus Disease", - "Cure Poison", - "Cure Paralyzation", - "Restore Attribute", - "Restore Health", - "Restore Magicka", - "Restore Fatigue", - "Restore Skill", - "Fortify Attribute", - "Fortify Health", - "Fortify Magicka", - "Fortify Fatigue", - "Fortify Skill", - "Fortify Maximum Magicka", - "Absorb Attribute", - "Absorb Health", - "Absorb Magicka", - "Absorb Fatigue", - "Absorb Skill", - "Resist Fire", - "Resist Frost", - "Resist Shock", - "Resist Magicka", - "Resist Common Disease", - "Resist Blight Disease", - "Resist Corprus Disease", - "Resist Poison", - "Resist Normal Weapons", - "Resist Paralysis", - "Remove Curse", - "Turn Undead", - "Summon Scamp", - "Summon Clannfear", - "Summon Daedroth", - "Summon Dremora", - "Summon Ancestral Ghost", - "Summon Skeletal Minion", - "Summon Bonewalker", - "Summon Greater Bonewalker", - "Summon Bonelord", - "Summon Winged Twilight", - "Summon Hunger", - "Summon Golden Saint", - "Summon Flame Atronach", - "Summon Frost Atronach", - "Summon Storm Atronach", - "Fortify Attack", - "Command Creature", - "Command Humanoid", - "Bound Dagger", - "Bound Longsword", - "Bound Mace", - "Bound Battle Axe", - "Bound Spear", - "Bound Longbow", - "EXTRA SPELL", - "Bound Cuirass", - "Bound Helm", - "Bound Boots", - "Bound Shield", - "Bound Gloves", - "Corprus", - "Vampirism", - "Summon Centurion Sphere", - "Sun Damage", - "Stunted Magicka", - "Summon Fabricant", - "sEffectSummonCreature01", - "sEffectSummonCreature02", - "sEffectSummonCreature03", - "sEffectSummonCreature04", - "sEffectSummonCreature05" - }; if (idx >= 0 && idx <= 142) + { + const char* magicEffectLabels [] = { + "Water Breathing", + "Swift Swim", + "Water Walking", + "Shield", + "Fire Shield", + "Lightning Shield", + "Frost Shield", + "Burden", + "Feather", + "Jump", + "Levitate", + "SlowFall", + "Lock", + "Open", + "Fire Damage", + "Shock Damage", + "Frost Damage", + "Drain Attribute", + "Drain Health", + "Drain Magicka", + "Drain Fatigue", + "Drain Skill", + "Damage Attribute", + "Damage Health", + "Damage Magicka", + "Damage Fatigue", + "Damage Skill", + "Poison", + "Weakness to Fire", + "Weakness to Frost", + "Weakness to Shock", + "Weakness to Magicka", + "Weakness to Common Disease", + "Weakness to Blight Disease", + "Weakness to Corprus Disease", + "Weakness to Poison", + "Weakness to Normal Weapons", + "Disintegrate Weapon", + "Disintegrate Armor", + "Invisibility", + "Chameleon", + "Light", + "Sanctuary", + "Night Eye", + "Charm", + "Paralyze", + "Silence", + "Blind", + "Sound", + "Calm Humanoid", + "Calm Creature", + "Frenzy Humanoid", + "Frenzy Creature", + "Demoralize Humanoid", + "Demoralize Creature", + "Rally Humanoid", + "Rally Creature", + "Dispel", + "Soultrap", + "Telekinesis", + "Mark", + "Recall", + "Divine Intervention", + "Almsivi Intervention", + "Detect Animal", + "Detect Enchantment", + "Detect Key", + "Spell Absorption", + "Reflect", + "Cure Common Disease", + "Cure Blight Disease", + "Cure Corprus Disease", + "Cure Poison", + "Cure Paralyzation", + "Restore Attribute", + "Restore Health", + "Restore Magicka", + "Restore Fatigue", + "Restore Skill", + "Fortify Attribute", + "Fortify Health", + "Fortify Magicka", + "Fortify Fatigue", + "Fortify Skill", + "Fortify Maximum Magicka", + "Absorb Attribute", + "Absorb Health", + "Absorb Magicka", + "Absorb Fatigue", + "Absorb Skill", + "Resist Fire", + "Resist Frost", + "Resist Shock", + "Resist Magicka", + "Resist Common Disease", + "Resist Blight Disease", + "Resist Corprus Disease", + "Resist Poison", + "Resist Normal Weapons", + "Resist Paralysis", + "Remove Curse", + "Turn Undead", + "Summon Scamp", + "Summon Clannfear", + "Summon Daedroth", + "Summon Dremora", + "Summon Ancestral Ghost", + "Summon Skeletal Minion", + "Summon Bonewalker", + "Summon Greater Bonewalker", + "Summon Bonelord", + "Summon Winged Twilight", + "Summon Hunger", + "Summon Golden Saint", + "Summon Flame Atronach", + "Summon Frost Atronach", + "Summon Storm Atronach", + "Fortify Attack", + "Command Creature", + "Command Humanoid", + "Bound Dagger", + "Bound Longsword", + "Bound Mace", + "Bound Battle Axe", + "Bound Spear", + "Bound Longbow", + "EXTRA SPELL", + "Bound Cuirass", + "Bound Helm", + "Bound Boots", + "Bound Shield", + "Bound Gloves", + "Corprus", + "Vampirism", + "Summon Centurion Sphere", + "Sun Damage", + "Stunted Magicka", + "Summon Fabricant", + "sEffectSummonCreature01", + "sEffectSummonCreature02", + "sEffectSummonCreature03", + "sEffectSummonCreature04", + "sEffectSummonCreature05" + }; return magicEffectLabels[idx]; + } else return "Invalid"; } std::string attributeLabel(int idx) { - const char* attributeLabels [] = { - "Strength", - "Intelligence", - "Willpower", - "Agility", - "Speed", - "Endurance", - "Personality", - "Luck" - }; if (idx >= 0 && idx <= 7) + { + const char* attributeLabels [] = { + "Strength", + "Intelligence", + "Willpower", + "Agility", + "Speed", + "Endurance", + "Personality", + "Luck" + }; return attributeLabels[idx]; + } else return "Invalid"; } std::string spellTypeLabel(int idx) { - const char* spellTypeLabels [] = { - "Spells", - "Abilities", - "Blight Disease", - "Disease", - "Curse", - "Powers" - }; if (idx >= 0 && idx <= 5) + { + const char* spellTypeLabels [] = { + "Spells", + "Abilities", + "Blight Disease", + "Disease", + "Curse", + "Powers" + }; return spellTypeLabels[idx]; + } else return "Invalid"; } std::string specializationLabel(int idx) { - const char* specializationLabels [] = { - "Combat", - "Magic", - "Stealth" - }; if (idx >= 0 && idx <= 2) + { + const char* specializationLabels [] = { + "Combat", + "Magic", + "Stealth" + }; return specializationLabels[idx]; + } else return "Invalid"; } std::string skillLabel(int idx) { - const char* skillLabels [] = { - "Block", - "Armorer", - "Medium Armor", - "Heavy Armor", - "Blunt Weapon", - "Long Blade", - "Axe", - "Spear", - "Athletics", - "Enchant", - "Destruction", - "Alteration", - "Illusion", - "Conjuration", - "Mysticism", - "Restoration", - "Alchemy", - "Unarmored", - "Security", - "Sneak", - "Acrobatics", - "Light Armor", - "Short Blade", - "Marksman", - "Mercantile", - "Speechcraft", - "Hand-to-hand" - }; if (idx >= 0 && idx <= 26) + { + const char* skillLabels [] = { + "Block", + "Armorer", + "Medium Armor", + "Heavy Armor", + "Blunt Weapon", + "Long Blade", + "Axe", + "Spear", + "Athletics", + "Enchant", + "Destruction", + "Alteration", + "Illusion", + "Conjuration", + "Mysticism", + "Restoration", + "Alchemy", + "Unarmored", + "Security", + "Sneak", + "Acrobatics", + "Light Armor", + "Short Blade", + "Marksman", + "Mercantile", + "Speechcraft", + "Hand-to-hand" + }; return skillLabels[idx]; + } else return "Invalid"; } std::string apparatusTypeLabel(int idx) { - const char* apparatusTypeLabels [] = { - "Mortar", - "Alembic", - "Calcinator", - "Retort", - }; if (idx >= 0 && idx <= 3) + { + const char* apparatusTypeLabels [] = { + "Mortar", + "Alembic", + "Calcinator", + "Retort", + }; return apparatusTypeLabels[idx]; + } else return "Invalid"; } std::string rangeTypeLabel(int idx) { - const char* rangeTypeLabels [] = { - "Self", - "Touch", - "Target" - }; if (idx >= 0 && idx <= 2) + { + const char* rangeTypeLabels [] = { + "Self", + "Touch", + "Target" + }; return rangeTypeLabels[idx]; + } else return "Invalid"; } std::string schoolLabel(int idx) { - const char* schoolLabels [] = { - "Alteration", - "Conjuration", - "Destruction", - "Illusion", - "Mysticism", - "Restoration" - }; if (idx >= 0 && idx <= 5) + { + const char* schoolLabels [] = { + "Alteration", + "Conjuration", + "Destruction", + "Illusion", + "Mysticism", + "Restoration" + }; return schoolLabels[idx]; + } else return "Invalid"; } std::string enchantTypeLabel(int idx) { - const char* enchantTypeLabels [] = { - "Cast Once", - "Cast When Strikes", - "Cast When Used", - "Constant Effect" - }; if (idx >= 0 && idx <= 3) + { + const char* enchantTypeLabels [] = { + "Cast Once", + "Cast When Strikes", + "Cast When Used", + "Constant Effect" + }; return enchantTypeLabels[idx]; + } else return "Invalid"; } std::string ruleFunction(int idx) { - std::string ruleFunctions[] = { - "Reaction Low", - "Reaction High", - "Rank Requirement", - "NPC? Reputation", - "Health Percent", - "Player Reputation", - "NPC Level", - "Player Health Percent", - "Player Magicka", - "Player Fatigue", - "Player Attribute Strength", - "Player Skill Block", - "Player Skill Armorer", - "Player Skill Medium Armor", - "Player Skill Heavy Armor", - "Player Skill Blunt Weapon", - "Player Skill Long Blade", - "Player Skill Axe", - "Player Skill Spear", - "Player Skill Athletics", - "Player Skill Enchant", - "Player Skill Destruction", - "Player Skill Alteration", - "Player Skill Illusion", - "Player Skill Conjuration", - "Player Skill Mysticism", - "Player SKill Restoration", - "Player Skill Alchemy", - "Player Skill Unarmored", - "Player Skill Security", - "Player Skill Sneak", - "Player Skill Acrobatics", - "Player Skill Light Armor", - "Player Skill Short Blade", - "Player Skill Marksman", - "Player Skill Mercantile", - "Player Skill Speechcraft", - "Player Skill Hand to Hand", - "Player Gender", - "Player Expelled from Faction", - "Player Diseased (Common)", - "Player Diseased (Blight)", - "Player Clothing Modifier", - "Player Crime Level", - "Player Same Sex", - "Player Same Race", - "Player Same Faction", - "Faction Rank Difference", - "Player Detected", - "Alarmed", - "Choice Selected", - "Player Attribute Intelligence", - "Player Attribute Willpower", - "Player Attribute Agility", - "Player Attribute Speed", - "Player Attribute Endurance", - "Player Attribute Personality", - "Player Attribute Luck", - "Player Diseased (Corprus)", - "Weather", - "Player is a Vampire", - "Player Level", - "Attacked", - "NPC Talked to Player", - "Player Health", - "Creature Target", - "Friend Hit", - "Fight", - "Hello", - "Alarm", - "Flee", - "Should Attack", - "Werewolf" - }; if (idx >= 0 && idx <= 72) + { + std::string ruleFunctions[] = { + "Reaction Low", + "Reaction High", + "Rank Requirement", + "NPC? Reputation", + "Health Percent", + "Player Reputation", + "NPC Level", + "Player Health Percent", + "Player Magicka", + "Player Fatigue", + "Player Attribute Strength", + "Player Skill Block", + "Player Skill Armorer", + "Player Skill Medium Armor", + "Player Skill Heavy Armor", + "Player Skill Blunt Weapon", + "Player Skill Long Blade", + "Player Skill Axe", + "Player Skill Spear", + "Player Skill Athletics", + "Player Skill Enchant", + "Player Skill Destruction", + "Player Skill Alteration", + "Player Skill Illusion", + "Player Skill Conjuration", + "Player Skill Mysticism", + "Player SKill Restoration", + "Player Skill Alchemy", + "Player Skill Unarmored", + "Player Skill Security", + "Player Skill Sneak", + "Player Skill Acrobatics", + "Player Skill Light Armor", + "Player Skill Short Blade", + "Player Skill Marksman", + "Player Skill Mercantile", + "Player Skill Speechcraft", + "Player Skill Hand to Hand", + "Player Gender", + "Player Expelled from Faction", + "Player Diseased (Common)", + "Player Diseased (Blight)", + "Player Clothing Modifier", + "Player Crime Level", + "Player Same Sex", + "Player Same Race", + "Player Same Faction", + "Faction Rank Difference", + "Player Detected", + "Alarmed", + "Choice Selected", + "Player Attribute Intelligence", + "Player Attribute Willpower", + "Player Attribute Agility", + "Player Attribute Speed", + "Player Attribute Endurance", + "Player Attribute Personality", + "Player Attribute Luck", + "Player Diseased (Corprus)", + "Weather", + "Player is a Vampire", + "Player Level", + "Attacked", + "NPC Talked to Player", + "Player Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf" + }; return ruleFunctions[idx]; + } else return "Invalid"; } @@ -787,6 +817,10 @@ std::string magicEffectFlags(int flags) if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable "; if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable "; if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked "; + if (flags & ESM::MagicEffect::AllowSpellmaking) properties += "AllowSpellmaking "; + if (flags & ESM::MagicEffect::AllowEnchanting) properties += "AllowEnchanting "; + if (flags & ESM::MagicEffect::NegativeLight) properties += "NegativeLight "; + if (flags & 0xFFFC0000) properties += "Invalid "; properties += str(boost::format("(0x%08X)") % flags); return properties; diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e4ada7d189..3f2ebc2ba5 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -412,7 +412,7 @@ void Record::print() std::cout << " Armor: " << mData.mData.mArmor << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; std::vector::iterator pit; - for (pit = mData.mParts.mParts.begin(); pit != mData.mParts.mParts.end(); pit++) + for (pit = mData.mParts.mParts.begin(); pit != mData.mParts.mParts.end(); ++pit) { std::cout << " Body Part: " << bodyPartLabel(pit->mPart) << " (" << (int)(pit->mPart) << ")" << std::endl; @@ -484,7 +484,7 @@ void Record::print() std::cout << " Texture: " << mData.mTexture << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; std::vector::iterator pit; - for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); pit++) + for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) std::cout << " Power: " << *pit << std::endl; } @@ -513,7 +513,7 @@ void Record::print() else std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; - std::cout << " NAM0: " << mData.mNAM0 << std::endl; + std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; } @@ -554,7 +554,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; std::vector::iterator pit; - for (pit = mData.mParts.mParts.begin(); pit != mData.mParts.mParts.end(); pit++) + for (pit = mData.mParts.mParts.begin(); pit != mData.mParts.mParts.end(); ++pit) { std::cout << " Body Part: " << bodyPartLabel(pit->mPart) << " (" << (int)(pit->mPart) << ")" << std::endl; @@ -574,7 +574,7 @@ void Record::print() std::cout << " Flags: " << containerFlags(mData.mFlags) << std::endl; std::cout << " Weight: " << mData.mWeight << std::endl; std::vector::iterator cit; - for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); cit++) + for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; } @@ -619,12 +619,12 @@ void Record::print() std::cout << " Gold: " << mData.mData.mGold << std::endl; std::vector::iterator cit; - for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); cit++) + for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; std::vector::iterator sit; - for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); sit++) + for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); ++sit) std::cout << " Spell: " << *sit << std::endl; std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl; @@ -639,7 +639,7 @@ void Record::print() std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl; std::vector::iterator pit; - for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); pit++) + for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); } @@ -706,7 +706,7 @@ void Record::print() << mData.mData.mRankData[i].mFactReaction << std::endl; } std::map::iterator rit; - for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); rit++) + for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; } @@ -763,7 +763,7 @@ void Record::print() std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; std::vector::iterator sit; - for (sit = mData.mSelects.begin(); sit != mData.mSelects.end(); sit++) + for (sit = mData.mSelects.begin(); sit != mData.mSelects.end(); ++sit) std::cout << " Select Rule: " << ruleString(*sit) << std::endl; if (mData.mResultScript != "") @@ -835,7 +835,7 @@ void Record::print() std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; std::vector::iterator iit; - for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++) + for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; } @@ -847,7 +847,7 @@ void Record::print() std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; std::vector::iterator iit; - for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++) + for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; } @@ -950,9 +950,9 @@ void Record::print() std::cout << " School: " << schoolLabel(mData.mData.mSchool) << " (" << mData.mData.mSchool << ")" << std::endl; std::cout << " Base Cost: " << mData.mData.mBaseCost << std::endl; + std::cout << " Unknown 1: " << mData.mData.mUnknown1 << std::endl; std::cout << " Speed: " << mData.mData.mSpeed << std::endl; - std::cout << " Size: " << mData.mData.mSize << std::endl; - std::cout << " Size Cap: " << mData.mData.mSizeCap << std::endl; + std::cout << " Unknown 2: " << mData.mData.mUnknown2 << std::endl; std::cout << " RGB Color: " << "(" << mData.mData.mRed << "," << mData.mData.mGreen << "," @@ -992,7 +992,6 @@ void Record::print() std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; std::cout << " Disposition: " << (int)mData.mNpdt12.mDisposition << std::endl; - std::cout << " Faction: " << (int)mData.mNpdt52.mFactionID << std::endl; std::cout << " Rank: " << (int)mData.mNpdt12.mRank << std::endl; std::cout << " Unknown1: " << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl; @@ -1007,6 +1006,7 @@ void Record::print() std::cout << " Reputation: " << (int)mData.mNpdt52.mReputation << std::endl; std::cout << " Disposition: " << (int)mData.mNpdt52.mDisposition << std::endl; std::cout << " Rank: " << (int)mData.mNpdt52.mRank << std::endl; + std::cout << " FactionID: " << (int)mData.mNpdt52.mFactionID << std::endl; std::cout << " Attributes:" << std::endl; std::cout << " Strength: " << (int)mData.mNpdt52.mStrength << std::endl; @@ -1031,16 +1031,16 @@ void Record::print() } std::vector::iterator cit; - for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); cit++) + for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; std::vector::iterator sit; - for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); sit++) + for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); ++sit) std::cout << " Spell: " << *sit << std::endl; std::vector::iterator dit; - for (dit = mData.mTransport.begin(); dit != mData.mTransport.end(); dit++) + for (dit = mData.mTransport.begin(); dit != mData.mTransport.end(); ++dit) { std::cout << " Destination Position: " << boost::format("%12.3f") % dit->mPos.pos[0] << "," @@ -1066,7 +1066,7 @@ void Record::print() std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl; std::vector::iterator pit; - for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); pit++) + for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); } @@ -1140,7 +1140,7 @@ void Record::print() << mData.mData.mBonus[i].mBonus << std::endl; std::vector::iterator sit; - for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); sit++) + for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) std::cout << " Power: " << *sit << std::endl; } @@ -1164,7 +1164,7 @@ void Record::print() if (mData.mSleepList != "") std::cout << " Sleep List: " << mData.mSleepList << std::endl; std::vector::iterator sit; - for (sit = mData.mSoundList.begin(); sit != mData.mSoundList.end(); sit++) + for (sit = mData.mSoundList.begin(); sit != mData.mSoundList.end(); ++sit) std::cout << " Sound: " << (int)sit->mChance << " = " << sit->mSound.toString() << std::endl; } @@ -1181,12 +1181,12 @@ void Record::print() std::vector::iterator vit; - for (vit = mData.mVarNames.begin(); vit != mData.mVarNames.end(); vit++) + for (vit = mData.mVarNames.begin(); vit != mData.mVarNames.end(); ++vit) std::cout << " Variable: " << *vit << std::endl; std::cout << " ByteCode: "; std::vector::iterator cit; - for (cit = mData.mScriptData.begin(); cit != mData.mScriptData.end(); cit++) + for (cit = mData.mScriptData.begin(); cit != mData.mScriptData.end(); ++cit) std::cout << boost::format("%02X") % (int)(*cit); std::cout << std::endl; diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index d7733ba0e1..4051480c73 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -15,7 +15,7 @@ set(LAUNCHER utils/textinputdialog.cpp utils/lineedit.cpp - ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc + ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc ) if(NOT WIN32) LIST(APPEND LAUNCHER unshieldthread.cpp) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 3a52592ae8..80f186b1f4 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -15,6 +15,7 @@ namespace bfs = boost::filesystem; MwIniImporter::MwIniImporter() : mVerbose(false) + , mEncoding(ToUTF8::WINDOWS_1250) { const char *map[][2] = { @@ -709,8 +710,7 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filenam continue; } - multistrmap::iterator it; - if((it = map.find(key)) == map.end()) { + if(map.find(key) == map.end()) { map.insert( std::make_pair (key, std::vector() ) ); } map[key].push_back(value); @@ -746,8 +746,7 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::string& filenam std::string key(line.substr(0,pos)); std::string value(line.substr(pos+1)); - multistrmap::iterator it; - if((it = map.find(key)) == map.end()) { + if(map.find(key) == map.end()) { map.insert( std::make_pair (key, std::vector() ) ); } map[key].push_back(value); diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index c10103cd62..fdf6db804c 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -37,6 +37,8 @@ public: char **get() const { return const_cast(argv); } private: + utf8argv(const utf8argv&); + utf8argv& operator=(const utf8argv&); const char **argv; std::vector args; diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c03cc3138b..7094f8799e 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -1,15 +1,17 @@ -set (OPENCS_SRC main.cpp) +set (OPENCS_SRC main.cpp + ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc + ) opencs_units (. editor) set (CMAKE_BUILD_TYPE DEBUG) opencs_units (model/doc - document operation saving documentmanager loader + document operation saving documentmanager loader runner ) opencs_units_noqt (model/doc - stage savingstate savingstages + stage savingstate savingstages blacklist ) opencs_hdrs_noqt (model/doc @@ -24,7 +26,7 @@ opencs_units (model/world opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection - refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager + refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope ) opencs_hdrs_noqt (model/world @@ -38,13 +40,13 @@ opencs_units (model/tools opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck - birthsigncheck spellcheck referenceablecheck scriptcheck + birthsigncheck spellcheck referenceablecheck scriptcheck bodypartcheck ) opencs_units (view/doc viewmanager view operations operation subview startup filedialog newgame - filewidget adjusterwidget loader + filewidget adjusterwidget loader globaldebugprofilemenu runlogsubview ) @@ -69,7 +71,7 @@ opencs_units_noqt (view/world ) opencs_units (view/widget - scenetoolbar scenetool scenetoolmode pushbutton + scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun ) opencs_units (view/render @@ -82,6 +84,10 @@ opencs_units_noqt (view/render lightingbright object cell ) +opencs_hdrs_noqt (view/render + elements + ) + opencs_units (view/tools reportsubview @@ -122,12 +128,8 @@ opencs_units_noqt (model/filter node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode ) -opencs_hdrs_noqt (model/filter - filter - ) - opencs_units (view/filter - filtercreator filterbox recordfilterbox editwidget + filterbox recordfilterbox editwidget ) set (OPENCS_US diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index b3513a7f15..794ca3d7b4 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -80,13 +80,17 @@ std::pair > CS::Editor::readConfi boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) ("data-local", boost::program_options::value()->default_value("")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) ("encoding", boost::program_options::value()->default_value("win1252")) ("resources", boost::program_options::value()->default_value("resources")) ("fallback-archive", boost::program_options::value >()-> - default_value(std::vector(), "fallback-archive")->multitoken()); + default_value(std::vector(), "fallback-archive")->multitoken()) + ("script-blacklist", boost::program_options::value >()->default_value(std::vector(), "") + ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") + ("script-blacklist-use", boost::program_options::value()->implicit_value(true) + ->default_value(true), "enable script blacklisting"); boost::program_options::notify(variables); @@ -97,6 +101,10 @@ std::pair > CS::Editor::readConfi mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + if (variables["script-blacklist-use"].as()) + mDocumentManager.setBlacklistedScripts ( + variables["script-blacklist"].as >()); + mFsStrict = variables["fs-strict"].as(); Files::PathContainer dataDirs, dataLocal; @@ -181,7 +189,7 @@ void CS::Editor::createNewFile (const boost::filesystem::path &savePath) files.push_back(path.toUtf8().constData()); } - files.push_back(mFileDialog.filename().toUtf8().constData()); + files.push_back (savePath); mDocumentManager.addDocument (files, savePath, true); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index d88da9865d..c87e24e77a 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -16,6 +16,8 @@ #include +#include + #include "model/settings/usersettings.hpp" #include "model/doc/documentmanager.hpp" @@ -37,6 +39,7 @@ namespace CS { Q_OBJECT + Nif::Cache mNifCache; Files::ConfigurationManager mCfgMgr; CSMSettings::UserSettings mUserSettings; CSMDoc::DocumentManager mDocumentManager; diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp new file mode 100644 index 0000000000..9b37a43024 --- /dev/null +++ b/apps/opencs/model/doc/blacklist.cpp @@ -0,0 +1,31 @@ + +#include "blacklist.hpp" + +#include + +#include + +bool CSMDoc::Blacklist::isBlacklisted (const CSMWorld::UniversalId& id) const +{ + std::map >::const_iterator iter = + mIds.find (id.getType()); + + if (iter==mIds.end()) + return false; + + return std::binary_search (iter->second.begin(), iter->second.end(), + Misc::StringUtils::lowerCase (id.getId())); +} + +void CSMDoc::Blacklist::add (CSMWorld::UniversalId::Type type, + const std::vector& ids) +{ + std::vector& list = mIds[type]; + + int size = list.size(); + + list.resize (size+ids.size()); + + std::transform (ids.begin(), ids.end(), list.begin()+size, Misc::StringUtils::lowerCase); + std::sort (list.begin(), list.end()); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/blacklist.hpp b/apps/opencs/model/doc/blacklist.hpp new file mode 100644 index 0000000000..9bf7f1d86f --- /dev/null +++ b/apps/opencs/model/doc/blacklist.hpp @@ -0,0 +1,25 @@ +#ifndef CSM_DOC_BLACKLIST_H +#define CSM_DOC_BLACKLIST_H + +#include +#include +#include + +#include "../world/universalid.hpp" + +namespace CSMDoc +{ + /// \brief ID blacklist sorted by UniversalId type + class Blacklist + { + std::map > mIds; + + public: + + bool isBlacklisted (const CSMWorld::UniversalId& id) const; + + void add (CSMWorld::UniversalId::Type type, const std::vector& ids); + }; +} + +#endif diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 6dc361c1eb..040a4462d3 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1,6 +1,7 @@ #include "document.hpp" #include +#include #include #include @@ -2206,28 +2207,32 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager) + ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, + const std::vector& blacklistedScripts) : mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), - mTools (mData), mResDir(resDir), + mTools (*this), mResDir(resDir), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), - mSaving (*this, mProjectPath, encoding) + mSaving (*this, mProjectPath, encoding), + mRunner (mProjectPath) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); if (!boost::filesystem::exists (mProjectPath)) { - boost::filesystem::path locCustomFiltersPath (configuration.getUserDataPath()); - locCustomFiltersPath /= "defaultfilters"; + boost::filesystem::path customFiltersPath (configuration.getUserDataPath()); + customFiltersPath /= "defaultfilters"; - if (boost::filesystem::exists (locCustomFiltersPath)) + std::ofstream destination (mProjectPath.string().c_str(), std::ios::binary); + + if (boost::filesystem::exists (customFiltersPath)) { - boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath); + destination << std::ifstream(customFiltersPath.c_str(), std::ios::binary).rdbuf(); } else { - boost::filesystem::copy_file (mResDir / "defaultfilters", mProjectPath); + destination << std::ifstream(std::string(mResDir.string() + "/defaultfilters").c_str(), std::ios::binary).rdbuf(); } } @@ -2240,20 +2245,24 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, createBase(); } + mBlacklist.add (CSMWorld::UniversalId::Type_Script, blacklistedScripts); + addOptionalGmsts(); addOptionalGlobals(); connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mTools, SIGNAL (done (int)), this, SLOT (operationDone (int))); + connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int))); + connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect ( &mSaving, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, int)), this, SLOT (reportMessage (const CSMWorld::UniversalId&, const std::string&, int))); + + connect (&mRunner, SIGNAL (runStateChanged()), this, SLOT (runStateChanged())); } CSMDoc::Document::~Document() @@ -2275,6 +2284,9 @@ int CSMDoc::Document::getState() const if (mSaving.isRunning()) state |= State_Locked | State_Saving | State_Operation; + if (mRunner.isRunning()) + state |= State_Locked | State_Running; + if (int operations = mTools.getRunningOperations()) state |= State_Locked | State_Operation | operations; @@ -2339,7 +2351,7 @@ void CSMDoc::Document::reportMessage (const CSMWorld::UniversalId& id, const std std::cout << message << std::endl; } -void CSMDoc::Document::operationDone (int type) +void CSMDoc::Document::operationDone (int type, bool failed) { emit stateChanged (getState(), this); } @@ -2359,6 +2371,55 @@ CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& return mTools.getReport (id); } +bool CSMDoc::Document::isBlacklisted (const CSMWorld::UniversalId& id) + const +{ + return mBlacklist.isBlacklisted (id); +} + +void CSMDoc::Document::startRunning (const std::string& profile, + const std::string& startupInstruction) +{ + std::vector contentFiles; + + for (std::vector::const_iterator iter (mContentFiles.begin()); + iter!=mContentFiles.end(); ++iter) + contentFiles.push_back (iter->filename().string()); + + mRunner.configure (getData().getDebugProfiles().getRecord (profile).get(), contentFiles, + startupInstruction); + + int state = getState(); + + if (state & State_Modified) + { + // need to save first + mRunner.start (true); + + new SaveWatcher (&mRunner, &mSaving); // no, that is not a memory leak. Qt is weird. + + if (!(state & State_Saving)) + save(); + } + else + mRunner.start(); +} + +void CSMDoc::Document::stopRunning() +{ + mRunner.stop(); +} + +QTextDocument *CSMDoc::Document::getRunLog() +{ + return mRunner.getLog(); +} + +void CSMDoc::Document::runStateChanged() +{ + emit stateChanged (getState(), this); +} + void CSMDoc::Document::progress (int current, int max, int type) { emit progress (current, max, type, 1, this); diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index d092b47c8a..29235e6e4a 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -17,6 +17,8 @@ #include "state.hpp" #include "saving.hpp" +#include "blacklist.hpp" +#include "runner.hpp" class QAbstractItemModel; @@ -52,6 +54,8 @@ namespace CSMDoc boost::filesystem::path mProjectPath; Saving mSaving; boost::filesystem::path mResDir; + Blacklist mBlacklist; + Runner mRunner; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. @@ -78,7 +82,8 @@ namespace CSMDoc Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager); + ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, + const std::vector& blacklistedScripts); ~Document(); @@ -110,6 +115,15 @@ namespace CSMDoc CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id); ///< The ownership of the returned report is not transferred. + bool isBlacklisted (const CSMWorld::UniversalId& id) const; + + void startRunning (const std::string& profile, + const std::string& startupInstruction = ""); + + void stopRunning(); + + QTextDocument *getRunLog(); + signals: void stateChanged (int state, CSMDoc::Document *document); @@ -123,7 +137,9 @@ namespace CSMDoc void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, int type); - void operationDone (int type); + void operationDone (int type, bool failed); + + void runStateChanged(); public slots: diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 6953db0edb..9b807225c9 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -52,7 +52,7 @@ CSMDoc::DocumentManager::~DocumentManager() void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager); + Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); mDocuments.push_back (document); @@ -85,6 +85,11 @@ void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) mEncoding = encoding; } +void CSMDoc::DocumentManager::setBlacklistedScripts (const std::vector& scriptIds) +{ + mBlacklistedScripts = scriptIds; +} + void CSMDoc::DocumentManager::listResources() { mResourcesManager.listResources(); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index cebae6f6d4..c545b9a9f0 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -34,6 +34,7 @@ namespace CSMDoc Loader mLoader; ToUTF8::FromType mEncoding; CSMWorld::ResourcesManager mResourcesManager; + std::vector mBlacklistedScripts; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); @@ -53,6 +54,8 @@ namespace CSMDoc void setEncoding (ToUTF8::FromType encoding); + void setBlacklistedScripts (const std::vector& scriptIds); + /// Ask OGRE for a list of available resources. void listResources(); diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 42a432043c..533ee20a99 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -119,5 +119,5 @@ void CSMDoc::Operation::executeStage() void CSMDoc::Operation::operationDone() { - emit done (mType); + emit done (mType, mError); } \ No newline at end of file diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 6512838806..d5a7d4e098 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -54,7 +54,7 @@ namespace CSMDoc void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, int type); - void done (int type); + void done (int type, bool failed); public slots: diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp new file mode 100644 index 0000000000..d679c18907 --- /dev/null +++ b/apps/opencs/model/doc/runner.cpp @@ -0,0 +1,162 @@ + +#include "runner.hpp" + +#include +#include +#include +#include + +#include "operation.hpp" + +CSMDoc::Runner::Runner (const boost::filesystem::path& projectPath) +: mRunning (false), mStartup (0), mProjectPath (projectPath) +{ + connect (&mProcess, SIGNAL (finished (int, QProcess::ExitStatus)), + this, SLOT (finished (int, QProcess::ExitStatus))); + + connect (&mProcess, SIGNAL (readyReadStandardOutput()), + this, SLOT (readyReadStandardOutput())); + + mProcess.setProcessChannelMode (QProcess::MergedChannels); + + mProfile.blank(); +} + +CSMDoc::Runner::~Runner() +{ + if (mRunning) + { + disconnect (&mProcess, 0, this, 0); + mProcess.kill(); + mProcess.waitForFinished(); + } +} + +void CSMDoc::Runner::start (bool delayed) +{ + if (mStartup) + { + delete mStartup; + mStartup = 0; + } + + if (!delayed) + { + mLog.clear(); + + QString path = "openmw"; +#ifdef Q_OS_WIN + path.append(QString(".exe")); +#elif defined(Q_OS_MAC) + QDir dir(QCoreApplication::applicationDirPath()); + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + path = dir.absoluteFilePath(path.prepend("OpenMW.app/Contents/MacOS/")); +#else + path.prepend(QString("./")); +#endif + + mStartup = new QTemporaryFile (this); + mStartup->open(); + + { + QTextStream stream (mStartup); + + if (!mStartupInstruction.empty()) + stream << QString::fromUtf8 (mStartupInstruction.c_str()) << '\n'; + + stream << QString::fromUtf8 (mProfile.mScriptText.c_str()); + } + + mStartup->close(); + + QStringList arguments; + arguments << "--skip-menu"; + + if (mProfile.mFlags & ESM::DebugProfile::Flag_BypassNewGame) + arguments << "--new-game=0"; + else + arguments << "--new-game=1"; + + arguments << ("--script-run="+mStartup->fileName());; + + arguments << + QString::fromUtf8 (("--data="+mProjectPath.parent_path().string()).c_str()); + + for (std::vector::const_iterator iter (mContentFiles.begin()); + iter!=mContentFiles.end(); ++iter) + { + arguments << QString::fromUtf8 (("--content="+*iter).c_str()); + } + + arguments + << QString::fromUtf8 (("--content="+mProjectPath.filename().string()).c_str()); + + mProcess.start (path, arguments); + } + + mRunning = true; + emit runStateChanged(); +} + +void CSMDoc::Runner::stop() +{ + delete mStartup; + mStartup = 0; + + if (mProcess.state()==QProcess::NotRunning) + { + mRunning = false; + emit runStateChanged(); + } + else + mProcess.kill(); +} + +bool CSMDoc::Runner::isRunning() const +{ + return mRunning; +} + +void CSMDoc::Runner::configure (const ESM::DebugProfile& profile, + const std::vector& contentFiles, const std::string& startupInstruction) +{ + mProfile = profile; + mContentFiles = contentFiles; + mStartupInstruction = startupInstruction; +} + +void CSMDoc::Runner::finished (int exitCode, QProcess::ExitStatus exitStatus) +{ + mRunning = false; + emit runStateChanged(); +} + +QTextDocument *CSMDoc::Runner::getLog() +{ + return &mLog; +} + +void CSMDoc::Runner::readyReadStandardOutput() +{ + mLog.setPlainText ( + mLog.toPlainText() + QString::fromUtf8 (mProcess.readAllStandardOutput())); +} + + +CSMDoc::SaveWatcher::SaveWatcher (Runner *runner, Operation *operation) +: QObject (runner), mRunner (runner) +{ + connect (operation, SIGNAL (done (int, bool)), this, SLOT (saveDone (int, bool))); +} + +void CSMDoc::SaveWatcher::saveDone (int type, bool failed) +{ + if (failed) + mRunner->stop(); + else + mRunner->start(); + + deleteLater(); +} diff --git a/apps/opencs/model/doc/runner.hpp b/apps/opencs/model/doc/runner.hpp new file mode 100644 index 0000000000..38b52a73bd --- /dev/null +++ b/apps/opencs/model/doc/runner.hpp @@ -0,0 +1,85 @@ +#ifndef CSM_DOC_RUNNER_H +#define CSM_DOC_RUNNER_H + +#include +#include + +#include + +#include +#include +#include + +#include + +class QTemporaryFile; + +namespace CSMDoc +{ + class Runner : public QObject + { + Q_OBJECT + + QProcess mProcess; + bool mRunning; + ESM::DebugProfile mProfile; + std::vector mContentFiles; + std::string mStartupInstruction; + QTemporaryFile *mStartup; + QTextDocument mLog; + boost::filesystem::path mProjectPath; + + public: + + Runner (const boost::filesystem::path& projectPath); + + ~Runner(); + + /// \param delayed Flag as running but do not start the OpenMW process yet (the + /// process must be started by another call of start with delayed==false) + void start (bool delayed = false); + + void stop(); + + /// \note Running state is entered when the start function is called. This + /// is not necessarily identical to the moment the child process is started. + bool isRunning() const; + + void configure (const ESM::DebugProfile& profile, + const std::vector& contentFiles, + const std::string& startupInstruction); + + QTextDocument *getLog(); + + signals: + + void runStateChanged(); + + private slots: + + void finished (int exitCode, QProcess::ExitStatus exitStatus); + + void readyReadStandardOutput(); + }; + + class Operation; + + /// \brief Watch for end of save operation and restart or stop runner + class SaveWatcher : public QObject + { + Q_OBJECT + + Runner *mRunner; + + public: + + /// *this attaches itself to runner + SaveWatcher (Runner *runner, Operation *operation); + + private slots: + + void saveDone (int type, bool failed); + }; +} + +#endif diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 95631eea9c..9c6932941f 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -17,7 +17,14 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteHeaderStage (mDocument, mState, true)); - appendStage (new WriteFilterStage (mDocument, mState, CSMFilter::Filter::Scope_Project)); + appendStage (new WriteCollectionStage > ( + mDocument.getData().getFilters(), mState, CSMWorld::Scope_Project)); + + appendStage (new WriteCollectionStage > ( + mDocument.getData().getDebugProfiles(), mState, CSMWorld::Scope_Project)); + + appendStage (new WriteCollectionStage > ( + mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project)); appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index d36c2fb860..d62947df6a 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -201,23 +201,6 @@ void CSMDoc::WriteRefIdCollectionStage::perform (int stage, Messages& messages) } -CSMDoc::WriteFilterStage::WriteFilterStage (Document& document, SavingState& state, - CSMFilter::Filter::Scope scope) -: WriteCollectionStage > (document.getData().getFilters(), - state), - mDocument (document), mScope (scope) -{} - -void CSMDoc::WriteFilterStage::perform (int stage, Messages& messages) -{ - const CSMWorld::Record& record = - mDocument.getData().getFilters().getRecord (stage); - - if (record.get().mScope==mScope) - WriteCollectionStage >::perform (stage, messages); -} - - CSMDoc::CollectionReferencesStage::CollectionReferencesStage (Document& document, SavingState& state) : mDocument (document), mState (state) @@ -301,20 +284,6 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) // write references if (references!=mState.getSubRecords().end()) { - // first pass: find highest RefNum - int lastRefNum = -1; - - for (std::vector::const_iterator iter (references->second.begin()); - iter!=references->second.end(); ++iter) - { - const CSMWorld::Record& ref = - mDocument.getData().getReferences().getRecord (*iter); - - if (ref.get().mRefNum.mContentFile==0 && ref.get().mRefNum.mIndex>lastRefNum) - lastRefNum = ref.get().mRefNum.mIndex; - } - - // second pass: write for (std::vector::const_iterator iter (references->second.begin()); iter!=references->second.end(); ++iter) { @@ -324,20 +293,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) if (ref.mState==CSMWorld::RecordBase::State_Modified || ref.mState==CSMWorld::RecordBase::State_ModifiedOnly) { - if (ref.get().mRefNum.mContentFile==-2) - { - if (lastRefNum>=0xffffff) - throw std::runtime_error ( - "RefNums exhausted in cell: " + cell.get().mId); - - ESM::CellRef ref2 = ref.get(); - ref2.mRefNum.mContentFile = 0; - ref2.mRefNum.mIndex = ++lastRefNum; - - ref2.save (mState.getWriter()); - } - else - ref.get().save (mState.getWriter()); + ref.get().save (mState.getWriter()); } else if (ref.mState==CSMWorld::RecordBase::State_Deleted) { diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index dcb1a86500..54e877ef37 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -5,8 +5,7 @@ #include "../world/record.hpp" #include "../world/idcollection.hpp" - -#include "../filter/filter.hpp" +#include "../world/scope.hpp" #include "savingstate.hpp" @@ -67,10 +66,12 @@ namespace CSMDoc { const CollectionT& mCollection; SavingState& mState; + CSMWorld::Scope mScope; public: - WriteCollectionStage (const CollectionT& collection, SavingState& state); + WriteCollectionStage (const CollectionT& collection, SavingState& state, + CSMWorld::Scope scope = CSMWorld::Scope_Content); virtual int setup(); ///< \return number of steps @@ -81,8 +82,8 @@ namespace CSMDoc template WriteCollectionStage::WriteCollectionStage (const CollectionT& collection, - SavingState& state) - : mCollection (collection), mState (state) + SavingState& state, CSMWorld::Scope scope) + : mCollection (collection), mState (state), mScope (scope) {} template @@ -94,16 +95,14 @@ namespace CSMDoc template void WriteCollectionStage::perform (int stage, Messages& messages) { + if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) + return; + CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; if (state==CSMWorld::RecordBase::State_Modified || state==CSMWorld::RecordBase::State_ModifiedOnly) { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic (change ESMWriter interface?) - type += reinterpret_cast (&mCollection.getRecord (stage).mModified.sRecordId)[i]; - mState.getWriter().startRecord (mCollection.getRecord (stage).mModified.sRecordId); mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); mCollection.getRecord (stage).mModified.save (mState.getWriter()); @@ -152,20 +151,6 @@ namespace CSMDoc }; - class WriteFilterStage : public WriteCollectionStage > - { - Document& mDocument; - CSMFilter::Filter::Scope mScope; - - public: - - WriteFilterStage (Document& document, SavingState& state, CSMFilter::Filter::Scope scope); - - virtual void perform (int stage, Messages& messages); - ///< Messages resulting from this stage will be appended to \a messages. - - }; - class CollectionReferencesStage : public Stage { Document& mDocument; diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 6e1a1c4f41..287439a8bb 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -8,12 +8,13 @@ namespace CSMDoc State_Modified = 1, State_Locked = 2, State_Operation = 4, + State_Running = 8, - State_Saving = 8, - State_Verifying = 16, - State_Compiling = 32, // not implemented yet - State_Searching = 64, // not implemented yet - State_Loading = 128 // pseudo-state; can not be encountered in a loaded document + State_Saving = 16, + State_Verifying = 32, + State_Compiling = 64, // not implemented yet + State_Searching = 128, // not implemented yet + State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; } diff --git a/apps/opencs/model/filter/filter.hpp b/apps/opencs/model/filter/filter.hpp deleted file mode 100644 index 62170ca802..0000000000 --- a/apps/opencs/model/filter/filter.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CSM_FILTER_FILTER_H -#define CSM_FILTER_FILTER_H - -#include -#include - -#include - -namespace CSMFilter -{ - /// \brief Wrapper for Filter record - struct Filter : public ESM::Filter - { - enum Scope - { - Scope_Project = 0, // per project - Scope_Session = 1, // exists only for one editing session; not saved - Scope_Content = 2 // embedded in the edited content file - }; - - Scope mScope; - }; -} - -#endif \ No newline at end of file diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index bec445cbc4..51338dfc98 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -596,7 +596,7 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) return false; } - const CSMWorld::Record& record = mData.getFilters().getRecord (index); + const CSMWorld::Record& record = mData.getFilters().getRecord (index); if (record.isDeleted()) { diff --git a/apps/opencs/model/tools/bodypartcheck.cpp b/apps/opencs/model/tools/bodypartcheck.cpp new file mode 100644 index 0000000000..a26945acf6 --- /dev/null +++ b/apps/opencs/model/tools/bodypartcheck.cpp @@ -0,0 +1,51 @@ +#include "bodypartcheck.hpp" + +CSMTools::BodyPartCheckStage::BodyPartCheckStage( + const CSMWorld::IdCollection &bodyParts, + const CSMWorld::Resources &meshes, + const CSMWorld::IdCollection &races ) : + mBodyParts(bodyParts), + mMeshes(meshes), + mRaces(races) +{ } + +int CSMTools::BodyPartCheckStage::setup() +{ + return mBodyParts.getSize(); +} + +void CSMTools::BodyPartCheckStage::perform ( int stage, Messages &messages ) +{ + const CSMWorld::Record &record = mBodyParts.getRecord(stage); + + if ( record.isDeleted() ) + return; + + const ESM::BodyPart &bodyPart = record.get(); + + CSMWorld::UniversalId id( CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId ); + + // Check BYDT + if (bodyPart.mData.mPart > 14 ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range part value." )); + + if (bodyPart.mData.mFlags > 3 ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range flags value." )); + + if (bodyPart.mData.mType > 2 ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range type value." )); + + // Check MODL + + if ( bodyPart.mModel.empty() ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has no model." )); + else if ( mMeshes.searchId( bodyPart.mModel ) == -1 ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has invalid model." )); + + // Check FNAM + + if ( bodyPart.mRace.empty() ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has no race." )); + else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) + messages.push_back(std::make_pair( id, bodyPart.mId + " has invalid race." )); +} diff --git a/apps/opencs/model/tools/bodypartcheck.hpp b/apps/opencs/model/tools/bodypartcheck.hpp new file mode 100644 index 0000000000..d72badfdf7 --- /dev/null +++ b/apps/opencs/model/tools/bodypartcheck.hpp @@ -0,0 +1,35 @@ +#ifndef CSM_TOOLS_BODYPARTCHECK_H +#define CSM_TOOLS_BODYPARTCHECK_H + +#include +#include + +#include "../world/resources.hpp" +#include "../world/idcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that body part records are internally consistent + class BodyPartCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection &mBodyParts; + const CSMWorld::Resources &mMeshes; + const CSMWorld::IdCollection &mRaces; + + public: + BodyPartCheckStage( + const CSMWorld::IdCollection &bodyParts, + const CSMWorld::Resources &meshes, + const CSMWorld::IdCollection &races ); + + virtual int setup(); + ///< \return number of steps + + virtual void perform( int stage, Messages &messages ); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index b989e22a23..d2c647bdab 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -7,6 +7,8 @@ #include #include +#include "../doc/document.hpp" + #include "../world/data.hpp" void CSMTools::ScriptCheckStage::report (const std::string& message, const Compiler::TokenLoc& loc, @@ -37,8 +39,8 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) (type==ErrorMessage ? "error: " : "warning: ") + message)); } -CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMWorld::Data& data) -: mData (data), mContext (data), mMessages (0) +CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) +: mDocument (document), mContext (document.getData()), mMessages (0) { /// \todo add an option to configure warning mode setWarningsMode (0); @@ -53,18 +55,25 @@ int CSMTools::ScriptCheckStage::setup() mMessages = 0; mId.clear(); - return mData.getScripts().getSize(); + return mDocument.getData().getScripts().getSize(); } void CSMTools::ScriptCheckStage::perform (int stage, Messages& messages) { + mId = mDocument.getData().getScripts().getId (stage); + + if (mDocument.isBlacklisted ( + CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId))) + return; + mMessages = &messages; - mId = mData.getScripts().getId (stage); try { - mFile = mData.getScripts().getRecord (stage).get().mId; - std::istringstream input (mData.getScripts().getRecord (stage).get().mScriptText); + const CSMWorld::Data& data = mDocument.getData(); + + mFile = data.getScripts().getRecord (stage).get().mId; + std::istringstream input (data.getScripts().getRecord (stage).get().mScriptText); Compiler::Scanner scanner (*this, input, mContext.getExtensions()); diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index ecf8d61b79..75f11b9d4f 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -8,12 +8,17 @@ #include "../world/scriptcontext.hpp" +namespace CSMDoc +{ + class Document; +} + namespace CSMTools { /// \brief VerifyStage: make sure that scripts compile class ScriptCheckStage : public CSMDoc::Stage, private Compiler::ErrorHandler { - const CSMWorld::Data& mData; + const CSMDoc::Document& mDocument; Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; std::string mId; @@ -28,7 +33,7 @@ namespace CSMTools public: - ScriptCheckStage (const CSMWorld::Data& data); + ScriptCheckStage (const CSMDoc::Document& document); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 2f93db48ea..79b0b18b0e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -5,6 +5,7 @@ #include "../doc/state.hpp" #include "../doc/operation.hpp" +#include "../doc/document.hpp" #include "../world/data.hpp" #include "../world/universalid.hpp" @@ -21,6 +22,7 @@ #include "spellcheck.hpp" #include "referenceablecheck.hpp" #include "scriptcheck.hpp" +#include "bodypartcheck.hpp" CSMDoc::Operation *CSMTools::Tools::get (int type) { @@ -44,7 +46,7 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mVerifier = new CSMDoc::Operation (CSMDoc::State_Verifying, false); connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (mVerifier, SIGNAL (done (int)), this, SIGNAL (done (int))); + connect (mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (mVerifier, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, int)), this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, int))); @@ -78,15 +80,23 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mVerifier->appendStage (new SpellCheckStage (mData.getSpells())); - mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); + mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); - mVerifier->appendStage (new ScriptCheckStage (mData)); + mVerifier->appendStage (new ScriptCheckStage (mDocument)); + + mVerifier->appendStage( + new BodyPartCheckStage( + mData.getBodyParts(), + mData.getResources( + CSMWorld::UniversalId( CSMWorld::UniversalId::Type_Meshes )), + mData.getRaces() )); } return mVerifier; } -CSMTools::Tools::Tools (CSMWorld::Data& data) : mData (data), mVerifier (0), mNextReportNumber (0) +CSMTools::Tools::Tools (CSMDoc::Document& document) +: mDocument (document), mData (document.getData()), mVerifier (0), mNextReportNumber (0) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 3394d3f626..7ca30e6b97 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -14,6 +14,7 @@ namespace CSMWorld namespace CSMDoc { class Operation; + class Document; } namespace CSMTools @@ -24,6 +25,7 @@ namespace CSMTools { Q_OBJECT + CSMDoc::Document& mDocument; CSMWorld::Data& mData; CSMDoc::Operation *mVerifier; std::map mReports; @@ -44,7 +46,7 @@ namespace CSMTools public: - Tools (CSMWorld::Data& data); + Tools (CSMDoc::Document& document); virtual ~Tools(); @@ -68,7 +70,7 @@ namespace CSMTools void progress (int current, int max, int type); - void done (int type); + void done (int type, bool failed); }; } diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 1444f70f1d..45cb47ba02 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -68,6 +68,7 @@ namespace CSMWorld Display_TopicInfo, Display_JournalInfo, Display_Scene, + Display_GlobalVariable, //CONCRETE TYPES ENDS HERE Display_Integer, @@ -99,7 +100,8 @@ namespace CSMWorld Display_SoundRes, Display_Texture, Display_Video, - Display_Colour + Display_Colour, + Display_ScriptLines // console context }; int mColumnId; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 0c033593c8..b93deccb72 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -500,6 +500,47 @@ namespace CSMWorld } }; + template + struct FlagColumn2 : public Column + { + int mMask; + bool mInverted; + + FlagColumn2 (int columnId, int mask, bool inverted = false) + : Column (columnId, ColumnBase::Display_Boolean), mMask (mask), + mInverted (inverted) + {} + + virtual QVariant get (const Record& record) const + { + bool flag = (record.get().mFlags & mMask)!=0; + + if (mInverted) + flag = !flag; + + return flag; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + int flags = record2.mFlags & ~mMask; + + if ((data.toInt()!=0)!=mInverted) + flags |= mMask; + + record2.mFlags = flags; + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + template struct WeightHeightColumn : public Column { @@ -766,8 +807,18 @@ namespace CSMWorld template struct ScriptColumn : public Column { - ScriptColumn() - : Column (Columns::ColumnId_ScriptText, ColumnBase::Display_Script, 0) {} + enum Type + { + Type_File, // regular script record + Type_Lines, // console context + Type_Info // dialogue context (not implemented yet) + }; + + ScriptColumn (Type type) + : Column (Columns::ColumnId_ScriptText, + type==Type_File ? ColumnBase::Display_Script : ColumnBase::Display_ScriptLines, + type==Type_File ? 0 : ColumnBase::Flag_Dialogue) + {} virtual QVariant get (const Record& record) const { @@ -977,13 +1028,13 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return record.get().mFactIndex; + return record.get().mFactionRank; } virtual void set (Record& record, const QVariant& data) { ESXRecordT record2 = record.get(); - record2.mFactIndex = data.toInt(); + record2.mFactionRank = data.toInt(); record.setModified (record2); } @@ -1224,36 +1275,6 @@ namespace CSMWorld } }; - template - struct ScopeColumn : public Column - { - ScopeColumn() - : Column (Columns::ColumnId_Scope, ColumnBase::Display_Integer, 0) - {} - - virtual QVariant get (const Record& record) const - { - return static_cast (record.get().mScope); - } - - virtual void set (Record& record, const QVariant& data) - { - ESXRecordT record2 = record.get(); - record2.mScope = static_cast (data.toInt()); - record.setModified (record2); - } - - virtual bool isEditable() const - { - return true; - } - - virtual bool isUserEditable() const - { - return false; - } - }; - template struct PosColumn : public Column @@ -1870,6 +1891,97 @@ namespace CSMWorld return true; } }; + + template + struct OwnerGlobalColumn : public Column + { + OwnerGlobalColumn() + : Column (Columns::ColumnId_OwnerGlobal, ColumnBase::Display_GlobalVariable) + {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mGlobalVariable.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mGlobalVariable = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct RefNumCounterColumn : public Column + { + RefNumCounterColumn() + : Column (Columns::ColumnId_RefNumCounter, ColumnBase::Display_Integer, 0) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mRefNumCounter); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mRefNumCounter = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + + template + struct RefNumColumn : public Column + { + RefNumColumn() + : Column (Columns::ColumnId_RefNum, ColumnBase::Display_Integer, 0) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mRefNum.mIndex); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mRefNum.mIndex = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 05df0b7358..90af200594 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -172,7 +172,6 @@ namespace CSMWorld { ColumnId_Rank, "Rank" }, { ColumnId_Gender, "Gender" }, { ColumnId_PcRank, "PC Rank" }, - { ColumnId_Scope, "Scope" }, { ColumnId_ReferenceableId, "Referenceable ID" }, { ColumnId_CombatState, "Combat" }, { ColumnId_MagicState, "Magic" }, @@ -181,6 +180,12 @@ namespace CSMWorld { ColumnId_Vampire, "Vampire" }, { ColumnId_BodyPartType, "Bodypart Type" }, { ColumnId_MeshType, "Mesh Type" }, + { ColumnId_OwnerGlobal, "Owner Global" }, + { ColumnId_DefaultProfile, "Default Profile" }, + { ColumnId_BypassNewGame, "Bypass New Game" }, + { ColumnId_GlobalProfile, "Global Profile" }, + { ColumnId_RefNumCounter, "RefNum Counter" }, + { ColumnId_RefNum, "RefNum" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 326a025965..5da1765a4d 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -165,7 +165,6 @@ namespace CSMWorld ColumnId_Rank = 152, ColumnId_Gender = 153, ColumnId_PcRank = 154, - ColumnId_Scope = 155, ColumnId_ReferenceableId = 156, ColumnId_CombatState = 157, ColumnId_MagicState = 158, @@ -174,6 +173,12 @@ namespace CSMWorld ColumnId_Vampire = 161, ColumnId_BodyPartType = 162, ColumnId_MeshType = 163, + ColumnId_OwnerGlobal = 164, + ColumnId_DefaultProfile = 165, + ColumnId_BypassNewGame = 166, + ColumnId_GlobalProfile = 167, + ColumnId_RefNumCounter = 168, + ColumnId_RefNum = 169, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index b60ffeb29c..de0e9a4e53 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -10,13 +10,12 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_) { - mOld = mModel.data (mIndex, Qt::EditRole); - setText ("Modify " + mModel.headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } void CSMWorld::ModifyCommand::redo() { + mOld = mModel.data (mIndex, Qt::EditRole); mModel.setData (mIndex, mNew); } @@ -25,6 +24,13 @@ void CSMWorld::ModifyCommand::undo() mModel.setData (mIndex, mOld); } + +void CSMWorld::CreateCommand::applyModifications() +{ + for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) + mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); +} + CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) { @@ -44,9 +50,7 @@ void CSMWorld::CreateCommand::setType (UniversalId::Type type) void CSMWorld::CreateCommand::redo() { mModel.addRecord (mId, mType); - - for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) - mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); + applyModifications(); } void CSMWorld::CreateCommand::undo() @@ -148,27 +152,22 @@ void CSMWorld::ReorderRowsCommand::undo() CSMWorld::CloneCommand::CloneCommand (CSMWorld::IdTable& model, const std::string& idOrigin, - const std::string& IdDestination, + const std::string& idDestination, const CSMWorld::UniversalId::Type type, - QUndoCommand* parent) : - QUndoCommand (parent), - mModel (model), - mIdOrigin (idOrigin), - mIdDestination (Misc::StringUtils::lowerCase (IdDestination)), - mType (type) + QUndoCommand* parent) +: CreateCommand (model, idDestination, parent), mIdOrigin (idOrigin) { - setText ( ("Clone record " + idOrigin + " to the " + IdDestination).c_str()); + setType (type); + setText ( ("Clone record " + idOrigin + " to the " + idDestination).c_str()); } void CSMWorld::CloneCommand::redo() { - mModel.cloneRecord (mIdOrigin, mIdDestination, mType); - - for (std::map::const_iterator iter (mValues.begin()); iter != mValues.end(); ++iter) - mModel.setData (mModel.getModelIndex (mIdDestination, iter->first), iter->second); + mModel.cloneRecord (mIdOrigin, mId, mType); + applyModifications(); } void CSMWorld::CloneCommand::undo() { - mModel.removeRow (mModel.getModelIndex (mIdDestination, 0).row()); + mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } \ No newline at end of file diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 947be68fd4..a15c071a8b 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -39,32 +39,20 @@ namespace CSMWorld virtual void undo(); }; - class CloneCommand : public QUndoCommand - { - IdTable& mModel; - std::string mIdOrigin; - std::string mIdDestination; - UniversalId::Type mType; - std::map mValues; - - public: - - CloneCommand (IdTable& model, const std::string& idOrigin, - const std::string& IdDestination, - const UniversalId::Type type, - QUndoCommand* parent = 0); - - virtual void redo(); - - virtual void undo(); - }; - class CreateCommand : public QUndoCommand { + std::map mValues; + + protected: + IdTable& mModel; std::string mId; UniversalId::Type mType; - std::map mValues; + + protected: + + /// Apply modifications set via addValue. + void applyModifications(); public: @@ -79,6 +67,22 @@ namespace CSMWorld virtual void undo(); }; + class CloneCommand : public CreateCommand + { + std::string mIdOrigin; + + public: + + CloneCommand (IdTable& model, const std::string& idOrigin, + const std::string& IdDestination, + const UniversalId::Type type, + QUndoCommand* parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + class RevertCommand : public QUndoCommand { IdTable& mModel; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index b9f6c6cf96..bb4fa0b436 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -131,7 +131,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mScripts.addColumn (new StringIdColumn); mScripts.addColumn (new RecordStateColumn); mScripts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Script)); - mScripts.addColumn (new ScriptColumn); + mScripts.addColumn (new ScriptColumn (ScriptColumn::Type_File)); mRegions.addColumn (new StringIdColumn); mRegions.addColumn (new RecordStateColumn); @@ -186,7 +186,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mJournalInfos.addColumn (new StringIdColumn (true)); mJournalInfos.addColumn (new RecordStateColumn); - mJournalInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); + mJournalInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_JournalInfo)); mJournalInfos.addColumn (new TopicColumn (true)); mJournalInfos.addColumn (new QuestStatusTypeColumn); mJournalInfos.addColumn (new QuestIndexColumn); @@ -200,6 +200,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater)); mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorSky, ESM::Cell::QuasiEx)); mCells.addColumn (new RegionColumn); + mCells.addColumn (new RefNumCounterColumn); mEnchantments.addColumn (new StringIdColumn); mEnchantments.addColumn (new RecordStateColumn); @@ -250,13 +251,27 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRefs.addColumn (new LockLevelColumn); mRefs.addColumn (new KeyColumn); mRefs.addColumn (new TrapColumn); + mRefs.addColumn (new OwnerGlobalColumn); + mRefs.addColumn (new RefNumColumn); - mFilters.addColumn (new StringIdColumn); - mFilters.addColumn (new RecordStateColumn); - mFilters.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Filter)); - mFilters.addColumn (new FilterColumn); - mFilters.addColumn (new DescriptionColumn); - mFilters.addColumn (new ScopeColumn); + mFilters.addColumn (new StringIdColumn); + mFilters.addColumn (new RecordStateColumn); + mFilters.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Filter)); + mFilters.addColumn (new FilterColumn); + mFilters.addColumn (new DescriptionColumn); + + mDebugProfiles.addColumn (new StringIdColumn); + mDebugProfiles.addColumn (new RecordStateColumn); + mDebugProfiles.addColumn (new FixedRecordTypeColumn (UniversalId::Type_DebugProfile)); + mDebugProfiles.addColumn (new FlagColumn2 ( + Columns::ColumnId_DefaultProfile, ESM::DebugProfile::Flag_Default)); + mDebugProfiles.addColumn (new FlagColumn2 ( + Columns::ColumnId_BypassNewGame, ESM::DebugProfile::Flag_BypassNewGame)); + mDebugProfiles.addColumn (new FlagColumn2 ( + Columns::ColumnId_GlobalProfile, ESM::DebugProfile::Flag_Global)); + mDebugProfiles.addColumn (new DescriptionColumn); + mDebugProfiles.addColumn (new ScriptColumn ( + ScriptColumn::Type_Lines)); addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); @@ -280,17 +295,18 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc UniversalId::Type_Referenceable); addModel (new IdTable (&mRefs, IdTable::Feature_ViewCell | IdTable::Feature_Preview), UniversalId::Type_Reference); addModel (new IdTable (&mFilters), UniversalId::Type_Filter); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Mesh)), + addModel (new IdTable (&mDebugProfiles), UniversalId::Type_DebugProfile); + addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Meshes)), UniversalId::Type_Mesh); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Icon)), + addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Icons)), UniversalId::Type_Icon); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Music)), + addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Musics)), UniversalId::Type_Music); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_SoundRes)), + addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_SoundsRes)), UniversalId::Type_SoundRes); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Texture)), + addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Textures)), UniversalId::Type_Texture); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Video)), + addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); } @@ -483,12 +499,12 @@ CSMWorld::RefCollection& CSMWorld::Data::getReferences() return mRefs; } -const CSMWorld::IdCollection& CSMWorld::Data::getFilters() const +const CSMWorld::IdCollection& CSMWorld::Data::getFilters() const { return mFilters; } -CSMWorld::IdCollection& CSMWorld::Data::getFilters() +CSMWorld::IdCollection& CSMWorld::Data::getFilters() { return mFilters; } @@ -513,9 +529,19 @@ CSMWorld::IdCollection& CSMWorld::Data::getBodyParts() return mBodyParts; } +const CSMWorld::IdCollection& CSMWorld::Data::getDebugProfiles() const +{ + return mDebugProfiles; +} + +CSMWorld::IdCollection& CSMWorld::Data::getDebugProfiles() +{ + return mDebugProfiles; +} + const CSMWorld::Resources& CSMWorld::Data::getResources (const UniversalId& id) const { - return mResourcesManager.get (UniversalId::getParentType (id.getType())); + return mResourcesManager.get (id.getType()); } QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) @@ -582,6 +608,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) ESM::NAME n = mReader->getRecName(); mReader->getRecHeader(); + bool unhandledRecord = false; + switch (n.val) { case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; @@ -692,23 +720,37 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) case ESM::REC_FILT: - if (mProject) + if (!mProject) { - mFilters.load (*mReader, mBase); - mFilters.setData (mFilters.getSize()-1, - mFilters.findColumnIndex (CSMWorld::Columns::ColumnId_Scope), - static_cast (CSMFilter::Filter::Scope_Project)); + unhandledRecord = true; break; } - // fall through (filter record in a content file is an error with format 0) + mFilters.load (*mReader, mBase); + break; + + case ESM::REC_DBGP: + + if (!mProject) + { + unhandledRecord = true; + break; + } + + mDebugProfiles.load (*mReader, mBase); + break; default: - messages.push_back (std::make_pair (UniversalId::Type_None, - "Unsupported record type: " + n.toString())); + unhandledRecord = true; + } - mReader->skipRecord(); + if (unhandledRecord) + { + messages.push_back (std::make_pair (UniversalId::Type_None, + "Unsupported record type: " + n.toString())); + + mReader->skipRecord(); } return false; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index cbf13d8b1b..d0a07c6777 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -23,11 +23,11 @@ #include #include #include +#include +#include #include -#include "../filter/filter.hpp" - #include "../doc/stage.hpp" #include "idcollection.hpp" @@ -70,12 +70,13 @@ namespace CSMWorld IdCollection mJournals; IdCollection mEnchantments; IdCollection mBodyParts; + IdCollection mDebugProfiles; InfoCollection mTopicInfos; InfoCollection mJournalInfos; IdCollection mCells; RefIdCollection mReferenceables; RefCollection mRefs; - IdCollection mFilters; + IdCollection mFilters; const ResourcesManager& mResourcesManager; std::vector mModels; std::map mModelIndex; @@ -178,9 +179,9 @@ namespace CSMWorld RefCollection& getReferences(); - const IdCollection& getFilters() const; + const IdCollection& getFilters() const; - IdCollection& getFilters(); + IdCollection& getFilters(); const IdCollection& getEnchantments() const; @@ -190,6 +191,10 @@ namespace CSMWorld IdCollection& getBodyParts(); + const IdCollection& getDebugProfiles() const; + + IdCollection& getDebugProfiles(); + /// Throws an exception, if \a id does not match a resources list. const Resources& getResources (const UniversalId& id) const; diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index d1bd771f80..f3c1b0b73f 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -4,7 +4,5 @@ CSMWorld::CellRef::CellRef() { mRefNum.mIndex = 0; - - // special marker: This reference does not have a RefNum assign to it yet. - mRefNum.mContentFile = -2; + mRefNum.mContentFile = 0; } \ No newline at end of file diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 5692d30ac3..ec67465213 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -12,12 +12,12 @@ void CSMWorld::ResourcesManager::listResources() { static const char * const sMeshTypes[] = { "nif", 0 }; - addResources (Resources ("meshes", UniversalId::Type_Mesh, sMeshTypes)); - addResources (Resources ("icons", UniversalId::Type_Icon)); - addResources (Resources ("music", UniversalId::Type_Music)); - addResources (Resources ("sound", UniversalId::Type_SoundRes)); - addResources (Resources ("textures", UniversalId::Type_Texture)); - addResources (Resources ("videos", UniversalId::Type_Video)); + addResources (Resources ("meshes", UniversalId::Type_Meshes, sMeshTypes)); + addResources (Resources ("icons", UniversalId::Type_Icons)); + addResources (Resources ("music", UniversalId::Type_Musics)); + addResources (Resources ("sound", UniversalId::Type_SoundsRes)); + addResources (Resources ("textures", UniversalId::Type_Textures)); + addResources (Resources ("videos", UniversalId::Type_Videos)); } const CSMWorld::Resources& CSMWorld::ResourcesManager::get (UniversalId::Type type) const diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 86de0a6a63..a7180434af 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -50,7 +50,7 @@ QVariant CSMWorld::ResourceTable::headerData (int section, Qt::Orientation orien return QVariant(); if (role==ColumnBase::Role_Flags) - return ColumnBase::Flag_Table; + return section==0 ? ColumnBase::Flag_Table : 0; switch (section) { @@ -86,7 +86,7 @@ bool CSMWorld::ResourceTable::setData ( const QModelIndex &index, const QVariant Qt::ItemFlags CSMWorld::ResourceTable::flags (const QModelIndex & index) const { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled;; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QModelIndex CSMWorld::ResourceTable::index (int row, int column, const QModelIndex& parent) diff --git a/apps/opencs/model/world/scope.cpp b/apps/opencs/model/world/scope.cpp new file mode 100644 index 0000000000..e3ebf5ebd1 --- /dev/null +++ b/apps/opencs/model/world/scope.cpp @@ -0,0 +1,25 @@ + +#include "scope.hpp" + +#include + +#include + +CSMWorld::Scope CSMWorld::getScopeFromId (const std::string& id) +{ + // get root namespace + std::string namespace_; + + std::string::size_type i = id.find ("::"); + + if (i!=std::string::npos) + namespace_ = Misc::StringUtils::lowerCase (id.substr (0, i)); + + if (namespace_=="project") + return Scope_Project; + + if (namespace_=="session") + return Scope_Session; + + return Scope_Content; +} \ No newline at end of file diff --git a/apps/opencs/model/world/scope.hpp b/apps/opencs/model/world/scope.hpp new file mode 100644 index 0000000000..3983d91f50 --- /dev/null +++ b/apps/opencs/model/world/scope.hpp @@ -0,0 +1,23 @@ +#ifndef CSM_WOLRD_SCOPE_H +#define CSM_WOLRD_SCOPE_H + +#include + +namespace CSMWorld +{ + enum Scope + { + // record stored in content file + Scope_Content = 1, + + // record stored in project file + Scope_Project = 2, + + // record that exists only for the duration of one editing session + Scope_Session = 4 + }; + + Scope getScopeFromId (const std::string& id); +} + +#endif diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index 9b80650ab9..d40e0c217c 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -91,7 +91,7 @@ return ( type == CSMWorld::ColumnBase::Display_Activator || type == CSMWorld::ColumnBase::Display_Static || type == CSMWorld::ColumnBase::Display_Weapon); } -bool CSMWorld::TableMimeData::isReferencable(CSMWorld::UniversalId::Type type) const +bool CSMWorld::TableMimeData::isReferencable(CSMWorld::UniversalId::Type type) { return ( type == CSMWorld::UniversalId::Type_Activator || type == CSMWorld::UniversalId::Type_Potion @@ -222,7 +222,6 @@ namespace { CSMWorld::UniversalId::Type_Race, CSMWorld::ColumnBase::Display_Race }, { CSMWorld::UniversalId::Type_Skill, CSMWorld::ColumnBase::Display_Skill }, { CSMWorld::UniversalId::Type_Class, CSMWorld::ColumnBase::Display_Class }, - { CSMWorld::UniversalId::Type_Class, CSMWorld::ColumnBase::Display_Class }, { CSMWorld::UniversalId::Type_Faction, CSMWorld::ColumnBase::Display_Faction }, { CSMWorld::UniversalId::Type_Sound, CSMWorld::ColumnBase::Display_Sound }, { CSMWorld::UniversalId::Type_Region, CSMWorld::ColumnBase::Display_Region }, @@ -264,6 +263,7 @@ namespace { CSMWorld::UniversalId::Type_SoundRes, CSMWorld::ColumnBase::Display_SoundRes }, { CSMWorld::UniversalId::Type_Texture, CSMWorld::ColumnBase::Display_Texture }, { CSMWorld::UniversalId::Type_Video, CSMWorld::ColumnBase::Display_Video }, + { CSMWorld::UniversalId::Type_Global, CSMWorld::ColumnBase::Display_GlobalVariable }, { CSMWorld::UniversalId::Type_None, CSMWorld::ColumnBase::Display_None } // end marker }; diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 85c243944c..06d252435f 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -59,10 +59,10 @@ namespace CSMWorld static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); + static bool isReferencable(CSMWorld::UniversalId::Type type); private: - bool isReferencable(CSMWorld::UniversalId::Type type) const; bool isReferencable(CSMWorld::ColumnBase::Display type) const; }; } -#endif // TABLEMIMEDATA_H \ No newline at end of file +#endif // TABLEMIMEDATA_H diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 7ee7673545..816dbc4fbc 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -46,10 +46,12 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Musics", 0 }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", 0 }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -109,6 +111,7 @@ namespace { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 3d3f215d65..069beed4b1 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -119,10 +119,13 @@ namespace CSMWorld Type_Textures, Type_Texture, Type_Videos, - Type_Video + Type_Video, + Type_DebugProfiles, + Type_DebugProfile, + Type_RunLog }; - enum { NumberOfTypes = Type_BodyPart+1 }; + enum { NumberOfTypes = Type_DebugProfile+1 }; private: diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index ab56415a14..300656f334 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -18,7 +18,7 @@ #include "adjusterwidget.hpp" CSVDoc::FileDialog::FileDialog(QWidget *parent) : - QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0) + QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0), mDialogBuilt(false) { ui.setupUi (this); resize(400, 400); @@ -70,11 +70,15 @@ void CSVDoc::FileDialog::showDialog (ContentAction action) mAdjusterWidget->setFilenameCheck (mAction == ContentAction_New); - //connections common to both dialog view flavors - connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), - this, SLOT (slotUpdateAcceptButton (int))); + if(!mDialogBuilt) + { + //connections common to both dialog view flavors + connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), + this, SLOT (slotUpdateAcceptButton (int))); - connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); + connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); + mDialogBuilt = true; + } show(); raise(); @@ -85,23 +89,27 @@ void CSVDoc::FileDialog::buildNewFileView() { setWindowTitle(tr("Create a new addon")); - QPushButton* createButton = ui.projectButtonBox->button (QDialogButtonBox::Ok); - createButton->setText ("Create"); - createButton->setEnabled (false); + QPushButton* createButton = ui.projectButtonBox->button (QDialogButtonBox::Ok); + createButton->setText ("Create"); + createButton->setEnabled (false); - mFileWidget = new FileWidget (this); + if(!mFileWidget) + { + mFileWidget = new FileWidget (this); - mFileWidget->setType (true); - mFileWidget->extensionLabelIsVisible(true); + mFileWidget->setType (true); + mFileWidget->extensionLabelIsVisible(true); + + connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), + mAdjusterWidget, SLOT (setName (const QString&, bool))); + + connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), + this, SLOT (slotUpdateAcceptButton(const QString &, bool))); + + } ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); - connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), - mAdjusterWidget, SLOT (setName (const QString&, bool))); - - connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), - this, SLOT (slotUpdateAcceptButton(const QString &, bool))); - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); } @@ -109,13 +117,18 @@ void CSVDoc::FileDialog::buildOpenFileView() { setWindowTitle(tr("Open")); ui.projectGroupBox->setTitle (QString("")); + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setText ("Open"); + if(mSelector->isGamefileSelected()) + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (true); + else + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); - ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); - - connect (mSelector, SIGNAL (signalAddonFileSelected (int)), this, SLOT (slotUpdateAcceptButton (int))); - connect (mSelector, SIGNAL (signalAddonFileUnselected (int)), this, SLOT (slotUpdateAcceptButton (int))); - - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); + if(!mDialogBuilt) + { + connect (mSelector, SIGNAL (signalAddonFileSelected (int)), this, SLOT (slotUpdateAcceptButton (int))); + connect (mSelector, SIGNAL (signalAddonFileUnselected (int)), this, SLOT (slotUpdateAcceptButton (int))); + } + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); } void CSVDoc::FileDialog::slotUpdateAcceptButton (int) @@ -156,12 +169,26 @@ QString CSVDoc::FileDialog::filename() const void CSVDoc::FileDialog::slotRejected() { emit rejected(); + disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); + disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); + if(mFileWidget) + { + delete mFileWidget; + mFileWidget = NULL; + } close(); } void CSVDoc::FileDialog::slotNewFile() { emit signalCreateNewFile (mAdjusterWidget->getPath()); + if(mFileWidget) + { + delete mFileWidget; + mFileWidget = NULL; + } + disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); + close(); } void CSVDoc::FileDialog::slotOpenFile() @@ -171,4 +198,6 @@ void CSVDoc::FileDialog::slotOpenFile() mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); emit signalOpenFiles (mAdjusterWidget->getPath()); + disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); + close(); } diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index d9fd569435..111cc0d807 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -37,6 +37,7 @@ namespace CSVDoc ContentAction mAction; FileWidget *mFileWidget; AdjusterWidget *mAdjusterWidget; + bool mDialogBuilt; public: diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index 9cd2fad422..f18fe695a0 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -17,7 +17,7 @@ CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (fal QHBoxLayout *layout = new QHBoxLayout (this); mInput = new QLineEdit (this); - mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$"))); + mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9_-\\s]*$"))); layout->addWidget (mInput, 1); diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp new file mode 100644 index 0000000000..82bd963269 --- /dev/null +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -0,0 +1,93 @@ + +#include "globaldebugprofilemenu.hpp" + +#include +#include + +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/record.hpp" + +void CSVDoc::GlobalDebugProfileMenu::rebuild() +{ + clear(); + + delete mActions; + mActions = 0; + + int idColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int stateColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int globalColumn = mDebugProfiles->findColumnIndex ( + CSMWorld::Columns::ColumnId_GlobalProfile); + + int size = mDebugProfiles->rowCount(); + + std::vector ids; + + for (int i=0; idata (mDebugProfiles->index (i, stateColumn)).toInt(); + + bool global = mDebugProfiles->data (mDebugProfiles->index (i, globalColumn)).toInt(); + + if (state!=CSMWorld::RecordBase::State_Deleted && global) + ids.push_back ( + mDebugProfiles->data (mDebugProfiles->index (i, idColumn)).toString()); + } + + mActions = new QActionGroup (this); + connect (mActions, SIGNAL (triggered (QAction *)), this, SLOT (actionTriggered (QAction *))); + + std::sort (ids.begin(), ids.end()); + + for (std::vector::const_iterator iter (ids.begin()); iter!=ids.end(); ++iter) + { + mActions->addAction (addAction (*iter)); + } +} + +CSVDoc::GlobalDebugProfileMenu::GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, + QWidget *parent) +: QMenu (parent), mDebugProfiles (debugProfiles), mActions (0) +{ + rebuild(); + + connect (mDebugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (profileAboutToBeRemoved (const QModelIndex&, int, int))); + + connect (mDebugProfiles, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (profileInserted (const QModelIndex&, int, int))); + + connect (mDebugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (profileChanged (const QModelIndex&, const QModelIndex&))); +} + +void CSVDoc::GlobalDebugProfileMenu::updateActions (bool running) +{ + if (mActions) + mActions->setEnabled (!running); +} + +void CSVDoc::GlobalDebugProfileMenu::profileAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + rebuild(); +} + +void CSVDoc::GlobalDebugProfileMenu::profileInserted (const QModelIndex& parent, int start, + int end) +{ + rebuild(); +} + +void CSVDoc::GlobalDebugProfileMenu::profileChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + rebuild(); +} + +void CSVDoc::GlobalDebugProfileMenu::actionTriggered (QAction *action) +{ + emit triggered (std::string (action->text().toUtf8().constData())); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.hpp b/apps/opencs/view/doc/globaldebugprofilemenu.hpp new file mode 100644 index 0000000000..0d7906ccef --- /dev/null +++ b/apps/opencs/view/doc/globaldebugprofilemenu.hpp @@ -0,0 +1,49 @@ +#ifndef CSV_DOC_GLOBALDEBUGPROFILEMENU_H +#define CSV_DOC_GLOBALDEBUGPROFILEMENU_H + +#include + +class QModelIndex; +class QActionGroup; + +namespace CSMWorld +{ + class IdTable; +} + +namespace CSVDoc +{ + class GlobalDebugProfileMenu : public QMenu + { + Q_OBJECT + + CSMWorld::IdTable *mDebugProfiles; + QActionGroup *mActions; + + private: + + void rebuild(); + + public: + + GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent = 0); + + void updateActions (bool running); + + private slots: + + void profileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + void profileInserted (const QModelIndex& parent, int start, int end); + + void profileChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void actionTriggered (QAction *action); + + signals: + + void triggered (const std::string& profile); + }; +} + +#endif diff --git a/apps/opencs/view/doc/runlogsubview.cpp b/apps/opencs/view/doc/runlogsubview.cpp new file mode 100644 index 0000000000..68e888e8d8 --- /dev/null +++ b/apps/opencs/view/doc/runlogsubview.cpp @@ -0,0 +1,20 @@ + +#include "runlogsubview.hpp" + +#include + +CSVDoc::RunLogSubView::RunLogSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) +: SubView (id) +{ + QTextEdit *edit = new QTextEdit (this); + edit->setDocument (document.getRunLog()); + edit->setReadOnly (true); + + setWidget (edit); +} + +void CSVDoc::RunLogSubView::setEditLock (bool locked) +{ + // ignored since this SubView does not have editing +} \ No newline at end of file diff --git a/apps/opencs/view/doc/runlogsubview.hpp b/apps/opencs/view/doc/runlogsubview.hpp new file mode 100644 index 0000000000..cfb676a375 --- /dev/null +++ b/apps/opencs/view/doc/runlogsubview.hpp @@ -0,0 +1,20 @@ +#ifndef CSV_DOC_RUNLOGSUBVIEW_H +#define CSV_DOC_RUNLOGSUBVIEW_H + +#include "subview.hpp" + +namespace CSVDoc +{ + class RunLogSubView : public SubView + { + Q_OBJECT + + public: + + RunLogSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + + virtual void setEditLock (bool locked); + }; +} + +#endif diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index a80d21cb2d..9c3c4e5e9a 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -5,6 +5,7 @@ CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) : mUniversalId (id) /// \todo add a button to the title bar that clones this sub view setWindowTitle (QString::fromUtf8 (mUniversalId.toString().c_str())); + setAttribute(Qt::WA_DeleteOnClose); } CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 4868f20ff1..1ed75b0b4e 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -12,6 +12,8 @@ #include "../../model/doc/document.hpp" #include "../../model/settings/usersettings.hpp" +#include "../../model/world/idtable.hpp" + #include "../world/subviews.hpp" #include "../tools/subviews.hpp" @@ -19,11 +21,19 @@ #include "viewmanager.hpp" #include "operations.hpp" #include "subview.hpp" +#include "globaldebugprofilemenu.hpp" +#include "runlogsubview.hpp" +#include "subviewfactoryimp.hpp" void CSVDoc::View::closeEvent (QCloseEvent *event) { if (!mViewManager.closeRequest (this)) event->ignore(); + else + { + // closeRequest() returns true if last document + mViewManager.removeDocAndView(mDocument); + } } void CSVDoc::View::setupFileMenu() @@ -232,6 +242,35 @@ void CSVDoc::View::setupAssetsMenu() assets->addAction (videos); } +void CSVDoc::View::setupDebugMenu() +{ + QMenu *debug = menuBar()->addMenu (tr ("Debug")); + + QAction *profiles = new QAction (tr ("Debug Profiles"), this); + connect (profiles, SIGNAL (triggered()), this, SLOT (addDebugProfilesSubView())); + debug->addAction (profiles); + + debug->addSeparator(); + + mGlobalDebugProfileMenu = new GlobalDebugProfileMenu ( + &dynamic_cast (*mDocument->getData().getTableModel ( + CSMWorld::UniversalId::Type_DebugProfiles)), this); + + connect (mGlobalDebugProfileMenu, SIGNAL (triggered (const std::string&)), + this, SLOT (run (const std::string&))); + + QAction *runDebug = debug->addMenu (mGlobalDebugProfileMenu); + runDebug->setText (tr ("Run OpenMW")); + + mStopDebug = new QAction (tr ("Shutdown OpenMW"), this); + connect (mStopDebug, SIGNAL (triggered()), this, SLOT (stop())); + debug->addAction (mStopDebug); + + QAction *runLog = new QAction (tr ("Run Log"), this); + connect (runLog, SIGNAL (triggered()), this, SLOT (addRunLogSubView())); + debug->addAction (runLog); +} + void CSVDoc::View::setupUi() { setupFileMenu(); @@ -241,6 +280,7 @@ void CSVDoc::View::setupUi() setupMechanicsMenu(); setupCharacterMenu(); setupAssetsMenu(); + setupDebugMenu(); } void CSVDoc::View::updateTitle() @@ -261,6 +301,7 @@ void CSVDoc::View::updateTitle() void CSVDoc::View::updateActions() { bool editing = !(mDocument->getState() & CSMDoc::State_Locked); + bool running = mDocument->getState() & CSMDoc::State_Running; for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) (*iter)->setEnabled (editing); @@ -268,8 +309,11 @@ void CSVDoc::View::updateActions() mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo()); mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo()); - mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving)); + mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running); mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); + + mGlobalDebugProfileMenu->updateActions (running); + mStopDebug->setEnabled (running); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) @@ -295,9 +339,13 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to setupUi(); + updateActions(); + CSVWorld::addSubViewFactories (mSubViewFactory); CSVTools::addSubViewFactories (mSubViewFactory); + mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); + connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); } @@ -543,6 +591,16 @@ void CSVDoc::View::addVideosSubView() addSubView (CSMWorld::UniversalId::Type_Videos); } +void CSVDoc::View::addDebugProfilesSubView() +{ + addSubView (CSMWorld::UniversalId::Type_DebugProfiles); +} + +void CSVDoc::View::addRunLogSubView() +{ + addSubView (CSMWorld::UniversalId::Type_RunLog); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); @@ -588,3 +646,13 @@ void CSVDoc::View::loadErrorLog() { addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_LoadErrorLog, 0)); } + +void CSVDoc::View::run (const std::string& profile, const std::string& startupInstruction) +{ + mDocument->startRunning (profile, startupInstruction); +} + +void CSVDoc::View::stop() +{ + mDocument->stopRunning(); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 19171ff425..9b4f2099bd 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -25,6 +25,7 @@ namespace CSVDoc { class ViewManager; class Operations; + class GlobalDebugProfileMenu; class View : public QMainWindow { @@ -39,10 +40,12 @@ namespace CSVDoc QAction *mSave; QAction *mVerify; QAction *mShowStatusBar; + QAction *mStopDebug; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; QMainWindow mSubViewWindow; + GlobalDebugProfileMenu *mGlobalDebugProfileMenu; // not implemented @@ -67,6 +70,8 @@ namespace CSVDoc void setupAssetsMenu(); + void setupDebugMenu(); + void setupUi(); void updateTitle(); @@ -194,9 +199,17 @@ namespace CSVDoc void addVideosSubView(); + void addDebugProfilesSubView(); + + void addRunLogSubView(); + void toggleShowStatusBar (bool show); void loadErrorLog(); + + void run (const std::string& profile, const std::string& startupInstruction = ""); + + void stop(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 6f4217aa8c..638b42d5fe 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -172,7 +172,7 @@ bool CSVDoc::ViewManager::closeRequest (View *view) { std::vector::iterator iter = std::find (mViews.begin(), mViews.end(), view); - bool continueWithClose = true; + bool continueWithClose = false; if (iter!=mViews.end()) { @@ -192,6 +192,24 @@ bool CSVDoc::ViewManager::closeRequest (View *view) return continueWithClose; } +// NOTE: This method assumes that it is called only if the last document +void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) +{ + for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + { + // the first match should also be the only match + if((*iter)->getDocument() == document) + { + mDocumentManager.removeDocument(document); + (*iter)->deleteLater(); + mViews.erase (iter); + + updateIndices(); + return; + } + } +} + bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view) { bool result = true; @@ -210,13 +228,19 @@ bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view) bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view) { - QMessageBox messageBox; + emit closeMessageBox(); + + QMessageBox messageBox(view); CSMDoc::Document *document = view->getDocument(); + messageBox.setWindowTitle (document->getSavePath().filename().string().c_str()); messageBox.setText ("The document has been modified."); messageBox.setInformativeText ("Do you want to save your changes?"); messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); messageBox.setDefaultButton (QMessageBox::Save); + messageBox.setWindowModality (Qt::NonModal); + messageBox.hide(); + messageBox.show(); bool retVal = true; @@ -341,8 +365,40 @@ void CSVDoc::ViewManager::onExitWarningHandler (int state, CSMDoc::Document *doc } } +bool CSVDoc::ViewManager::removeDocument (CSVDoc::View *view) +{ + if(!notifySaveOnClose(view)) + return false; + else + { + // don't bother closing views or updating indicies, but remove from mViews + CSMDoc::Document * document = view->getDocument(); + std::vector remainingViews; + std::vector::const_iterator iter = mViews.begin(); + for (; iter!=mViews.end(); ++iter) + { + if(document == (*iter)->getDocument()) + (*iter)->setVisible(false); + else + remainingViews.push_back(*iter); + } + mDocumentManager.removeDocument(document); + mViews = remainingViews; + } + return true; +} + void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view) { - if (notifySaveOnClose (view)) - QApplication::instance()->exit(); + if(!removeDocument(view)) // close the current document first + return; + + while(!mViews.empty()) // attempt to close all other documents + { + mViews.back()->activateWindow(); + mViews.back()->raise(); // raise the window to alert the user + if(!removeDocument(mViews.back())) + return; + } + // Editor exits (via a signal) when the last document is deleted } diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 8cc92774b0..753d7f0cb5 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -41,6 +41,7 @@ namespace CSVDoc bool notifySaveOnClose (View *view = 0); bool showModifiedDocumentMessageBox (View *view); bool showSaveInProgressMessageBox (View *view); + bool removeDocument(View *view); public: @@ -55,6 +56,7 @@ namespace CSVDoc ///< Return number of views for \a document. bool closeRequest (View *view); + void removeDocAndView (CSMDoc::Document *document); signals: diff --git a/apps/opencs/view/filter/filtercreator.cpp b/apps/opencs/view/filter/filtercreator.cpp deleted file mode 100644 index 640c9fe785..0000000000 --- a/apps/opencs/view/filter/filtercreator.cpp +++ /dev/null @@ -1,77 +0,0 @@ - -#include "filtercreator.hpp" - -#include -#include - -#include "../../model/filter/filter.hpp" - -#include "../../model/world/data.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/world/idtable.hpp" - -std::string CSVFilter::FilterCreator::getNamespace() const -{ - switch (mScope->currentIndex()) - { - case CSMFilter::Filter::Scope_Project: return "project::"; - case CSMFilter::Filter::Scope_Session: return "session::"; - } - - return ""; -} - -void CSVFilter::FilterCreator::update() -{ - mNamespace->setText (QString::fromUtf8 (getNamespace().c_str())); - GenericCreator::update(); -} - -std::string CSVFilter::FilterCreator::getId() const -{ - return getNamespace() + GenericCreator::getId(); -} - -void CSVFilter::FilterCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const -{ - int index = - dynamic_cast (*getData().getTableModel (getCollectionId())). - findColumnIndex (CSMWorld::Columns::ColumnId_Scope); - - command.addValue (index, mScope->currentIndex()); -} - -CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) -: GenericCreator (data, undoStack, id) -{ - mNamespace = new QLabel ("::", this); - insertAtBeginning (mNamespace, false); - - mScope = new QComboBox (this); - - mScope->addItem ("Project"); - mScope->addItem ("Session"); - /// \todo re-enable for OpenMW 1.1 - // mScope->addItem ("Content"); - - connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (setScope (int))); - - insertAtBeginning (mScope, false); - - QLabel *label = new QLabel ("Scope", this); - insertAtBeginning (label, false); - - mScope->setCurrentIndex (1); -} - -void CSVFilter::FilterCreator::reset() -{ - GenericCreator::reset(); -} - -void CSVFilter::FilterCreator::setScope (int index) -{ - update(); -} diff --git a/apps/opencs/view/filter/filtercreator.hpp b/apps/opencs/view/filter/filtercreator.hpp deleted file mode 100644 index 437d01c8da..0000000000 --- a/apps/opencs/view/filter/filtercreator.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef CSV_FILTER_FILTERCREATOR_H -#define CSV_FILTER_FILTERCREATOR_H - -class QComboBox; -class QLabel; - -#include "../world/genericcreator.hpp" - -namespace CSVFilter -{ - class FilterCreator : public CSVWorld::GenericCreator - { - Q_OBJECT - - QComboBox *mScope; - QLabel *mNamespace; - - private: - - std::string getNamespace() const; - - protected: - - void update(); - - virtual std::string getId() const; - - virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; - - public: - - FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id); - - virtual void reset(); - - private slots: - - void setScope (int index); - }; -} - -#endif diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index ec56476185..ef2aeb4d32 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -13,7 +13,9 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare layout->setContentsMargins (0, 0, 0, 0); - layout->addWidget (new QLabel ("Record Filter", this)); + QLabel *label = new QLabel("Record Filter", this); + label->setIndent(2); + layout->addWidget (label); mEdit = new EditWidget (data, this); diff --git a/apps/opencs/view/render/elements.hpp b/apps/opencs/view/render/elements.hpp new file mode 100644 index 0000000000..784e412125 --- /dev/null +++ b/apps/opencs/view/render/elements.hpp @@ -0,0 +1,23 @@ +#ifndef CSV_RENDER_ELEMENTS_H +#define CSV_RENDER_ELEMENTS_H + +namespace CSVRender +{ + /// Visual elements in a scene + enum Elements + { + // elements that are part of the actual scene + Element_Reference = 0x1, + Element_Terrain = 0x2, + Element_Water = 0x4, + Element_Pathgrid = 0x8, + Element_Fog = 0x10, + + // control elements + Element_CellMarker = 0x10000, + Element_CellArrow = 0x20000, + Element_CellBorder = 0x40000 + }; +} + +#endif diff --git a/apps/opencs/view/render/navigation.cpp b/apps/opencs/view/render/navigation.cpp index 14ae7f0b7e..7057591041 100644 --- a/apps/opencs/view/render/navigation.cpp +++ b/apps/opencs/view/render/navigation.cpp @@ -11,9 +11,14 @@ float CSVRender::Navigation::getFactor (bool mouse) const return factor; } +CSVRender::Navigation::Navigation() + : mFastModeFactor(1) +{ +} + CSVRender::Navigation::~Navigation() {} void CSVRender::Navigation::setFastModeFactor (float factor) { mFastModeFactor = factor; -} \ No newline at end of file +} diff --git a/apps/opencs/view/render/navigation.hpp b/apps/opencs/view/render/navigation.hpp index 8735196095..ead8f3e135 100644 --- a/apps/opencs/view/render/navigation.hpp +++ b/apps/opencs/view/render/navigation.hpp @@ -20,6 +20,7 @@ namespace CSVRender public: + Navigation(); virtual ~Navigation(); void setFastModeFactor (float factor); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index bb7c2f386d..359392d810 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -9,6 +9,8 @@ #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" +#include "elements.hpp" + void CSVRender::Object::clearSceneNode (Ogre::SceneNode *node) { for (Ogre::SceneNode::ObjectIterator iter = node->getAttachedObjectIterator(); @@ -63,12 +65,14 @@ void CSVRender::Object::update() { Ogre::Entity* entity = mBase->getCreator()->createEntity (Ogre::SceneManager::PT_CUBE); entity->setMaterialName("BaseWhite"); /// \todo adjust material according to error + entity->setVisibilityFlags (Element_Reference); mBase->attachObject (entity); } else { mObject = NifOgre::Loader::createObjects (mBase, "Meshes\\" + model); + mObject->setVisibilityFlags (Element_Reference); } } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 9862c12109..22af7cd86c 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -19,6 +19,10 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" +#include "../widget/scenetooltoggle.hpp" + +#include "elements.hpp" + void CSVRender::PagedWorldspaceWidget::displayCellCoord(bool display) { mDisplayCellCoord = display; @@ -210,6 +214,20 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent flagAsModified(); } +std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() +{ + Ogre::Vector3 position = getCamera()->getPosition(); + + std::ostringstream stream; + + stream + << "player->position " + << position.x << ", " << position.y << ", " << position.z + << ", 0"; + + return stream.str(); +} + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) : WorldspaceWidget(document, parent), mDocument(document), mWorldspace("std::default"), mDisplayCellCoord(true) { @@ -283,8 +301,15 @@ std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (co return std::make_pair(x, y); } -void CSVRender::PagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +bool CSVRender::PagedWorldspaceWidget::handleDrop ( + const std::vector< CSMWorld::UniversalId >& data, DropType type) { + if (WorldspaceWidget::handleDrop (data, type)) + return true; + + if (type!=Type_CellsExterior) + return false; + bool selectionChanged = false; for (unsigned i = 0; i < data.size(); ++i) { @@ -301,16 +326,23 @@ void CSVRender::PagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld:: emit cellSelectionChanged(mSelection); } + + return true; } -CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::dropType type) const +CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::DropType type) const { + dropRequirments requirements = WorldspaceWidget::getDropRequirements (type); + + if (requirements!=ignored) + return requirements; + switch (type) { - case cellsExterior: + case Type_CellsExterior: return canHandle; - case cellsInterior: + case Type_CellsInterior: return needUnpaged; default: @@ -318,6 +350,31 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::g } } + +unsigned int CSVRender::PagedWorldspaceWidget::getElementMask() const +{ + return WorldspaceWidget::getElementMask() | mControlElements->getSelection(); +} + +CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( + CSVWidget::SceneToolbar *parent) +{ + mControlElements = new CSVWidget::SceneToolToggle (parent, + "Controls & Guides Visibility", ":door.png"); + + mControlElements->addButton (":activator.png", Element_CellMarker, ":activator.png", + "Cell marker"); + mControlElements->addButton (":armor.png", Element_CellArrow, ":armor.png", "Cell arrows"); + mControlElements->addButton (":armor.png", Element_CellBorder, ":armor.png", "Cell border"); + + mControlElements->setSelection (0xffffffff); + + connect (mControlElements, SIGNAL (selectionChanged()), + this, SLOT (elementSelectionChanged())); + + return mControlElements; +} + void CSVRender::PagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 8350ded1ed..1b43510ff9 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -18,6 +18,7 @@ namespace CSVRender CSMWorld::CellSelection mSelection; std::map mCells; std::string mWorldspace; + CSVWidget::SceneToolToggle *mControlElements; bool mDisplayCellCoord; private: @@ -42,6 +43,8 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + virtual std::string getStartupInstruction(); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -57,9 +60,19 @@ namespace CSVRender void displayCellCoord(bool display); - virtual void handleDrop(const std::vector& data); - virtual dropRequirments getDropRequirements(dropType type) const; + /// \return Drop handled? + virtual bool handleDrop (const std::vector& data, + DropType type); + + virtual dropRequirments getDropRequirements(DropType type) const; + + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + virtual CSVWidget::SceneToolToggle *makeControlVisibilitySelector ( + CSVWidget::SceneToolbar *parent); + + virtual unsigned int getElementMask() const; signals: diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index ebd3eb7644..06b0417866 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -64,19 +64,19 @@ namespace CSVRender CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); /// \todo replace icons - tool->addButton (":door.png", "day", + tool->addButton (":scenetoolbar/day", "day", "Day" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Strong directional light source/lir>" "
  • This mode closely resembles day time in-game
"); - tool->addButton (":GMST.png", "night", + tool->addButton (":scenetoolbar/night", "night", "Night" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Weak directional light source
  • " "
  • This mode closely resembles night time in-game
"); - tool->addButton (":Info.png", "bright", + tool->addButton (":scenetoolbar/bright", "bright", "Bright" "
  • Maximum ambient
  • " "
  • Strong directional light source
"); @@ -126,7 +126,9 @@ namespace CSVRender #endif mWindow = Ogre::Root::getSingleton().createRenderWindow(windowTitle.str(), this->width(), this->height(), false, ¶ms); - mWindow->addViewport(mCamera)->setBackgroundColour(Ogre::ColourValue(0.3,0.3,0.3,1)); + + mViewport = mWindow->addViewport (mCamera); + mViewport->setBackgroundColour (Ogre::ColourValue (0.3,0.3,0.3,1)); Ogre::Real aspectRatio = Ogre::Real(width()) / Ogre::Real(height()); mCamera->setAspectRatio(aspectRatio); @@ -141,6 +143,11 @@ namespace CSVRender Ogre::Root::getSingleton().destroySceneManager (mSceneMgr); } + void SceneWidget::setVisibilityMask (unsigned int mask) + { + mViewport->setVisibilityMask (mask); + } + void SceneWidget::setNavigation (Navigation *navigation) { if ((mNavigation = navigation)) @@ -354,11 +361,6 @@ namespace CSVRender } } - int SceneWidget::getFastFactor() const - { - return mFast ? mFastFactor : 1; - } - void SceneWidget::setLighting (Lighting *lighting) { if (mLighting) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 8f548f4831..6361589a47 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -14,6 +14,7 @@ namespace Ogre class Camera; class SceneManager; class RenderWindow; + class Viewport; } namespace CSVWidget @@ -42,6 +43,8 @@ namespace CSVRender ///< \attention The created tool is not added to the toolbar (via addTool). Doing that /// is the responsibility of the calling function. + virtual void setVisibilityMask (unsigned int mask); + protected: void setNavigation (Navigation *navigation); @@ -77,14 +80,13 @@ namespace CSVRender void updateOgreWindow(); - int getFastFactor() const; - void setLighting (Lighting *lighting); ///< \attention The ownership of \a lighting is not transferred to *this. Ogre::Camera* mCamera; Ogre::SceneManager* mSceneMgr; Ogre::RenderWindow* mWindow; + Ogre::Viewport *mViewport; Navigation *mNavigation; Lighting *mLighting; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 0b656ddc6e..aab3791fce 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -1,7 +1,10 @@ #include "unpagedworldspacewidget.hpp" +#include + #include +#include #include @@ -11,6 +14,10 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../widget/scenetooltoggle.hpp" + +#include "elements.hpp" + void CSVRender::UnpagedWorldspaceWidget::update() { const CSMWorld::Record& record = @@ -25,6 +32,14 @@ void CSVRender::UnpagedWorldspaceWidget::update() flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( + CSVWidget::SceneToolToggle *tool) +{ + WorldspaceWidget::addVisibilitySelectorButtons (tool); + + tool->addButton (":armor.png", Element_Fog, ":armor.png", "Fog"); +} + CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) : WorldspaceWidget (document, parent), mCellId (cellId) { @@ -74,13 +89,21 @@ void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelI emit closeRequest(); } -void CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& data, DropType type) { + if (WorldspaceWidget::handleDrop (data, type)) + return true; + + if (type!=Type_CellsInterior) + return false; + mCellId = data.begin()->getId(); + mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId)); + update(); emit cellChanged(*data.begin()); - /// \todo replace mCell + return true; } void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, @@ -137,14 +160,33 @@ void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& pare flagAsModified(); } -CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::dropType type) const +std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { + Ogre::Vector3 position = getCamera()->getPosition(); + + std::ostringstream stream; + + stream + << "player->positionCell " + << position.x << ", " << position.y << ", " << position.z + << ", 0, \"" << mCellId << "\""; + + return stream.str(); +} + +CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::DropType type) const +{ + dropRequirments requirements = WorldspaceWidget::getDropRequirements (type); + + if (requirements!=ignored) + return requirements; + switch(type) { - case cellsInterior: + case Type_CellsInterior: return canHandle; - case cellsExterior: + case Type_CellsExterior: return needPaged; default: diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index ee8377fae4..5924abaa9e 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -32,14 +32,20 @@ namespace CSVRender void update(); + protected: + + virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle *tool); + public: UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget *parent); - virtual dropRequirments getDropRequirements(dropType type) const; + virtual dropRequirments getDropRequirements(DropType type) const; - virtual void handleDrop(const std::vector& data); + /// \return Drop handled? + virtual bool handleDrop (const std::vector& data, + DropType type); private: @@ -56,6 +62,8 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + virtual std::string getStartupInstruction(); + private slots: void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index d3413a29dd..fa304dd829 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,6 +1,8 @@ #include "worldspacewidget.hpp" +#include + #include #include #include @@ -8,11 +10,16 @@ #include #include "../../model/world/universalid.hpp" +#include "../../model/world/idtable.hpp" #include "../widget/scenetoolmode.hpp" +#include "../widget/scenetooltoggle.hpp" +#include "../widget/scenetoolrun.hpp" + +#include "elements.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (parent), mDocument(document) +: SceneWidget (parent), mDocument(document), mRun (0) { setAcceptDrops(true); @@ -35,6 +42,14 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceAdded (const QModelIndex&, int, int))); + + QAbstractItemModel *debugProfiles = + document.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles); + + connect (debugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (debugProfileDataChanged (const QModelIndex&, const QModelIndex&))); + connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) @@ -61,7 +76,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( /// \todo replace icons /// \todo consider user-defined button-mapping - tool->addButton (":door.png", "1st", + tool->addButton (":scenetoolbar/1st-person", "1st", "First Person" "
  • Mouse-Look while holding the left button
  • " "
  • WASD movement keys
  • " @@ -70,7 +85,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "
  • Camera is held upright
  • " "
  • Hold shift to speed up movement
  • " "
"); - tool->addButton (":GMST.png", "free", + tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" "
  • Mouse-Look while holding the left button
  • " "
  • Stafing (also vertically) via WASD or by holding the left mouse button and control
  • " @@ -78,7 +93,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "
  • Roll camera with Q and E keys
  • " "
  • Hold shift to speed up movement
  • " "
"); - tool->addButton (":Info.png", "orbit", + tool->addButton (":scenetoolbar/orbiting-camera", "orbit", "Orbiting Camera" "
  • Always facing the centre point
  • " "
  • Rotate around the centre point via WASD or by moving the mouse while holding the left button
  • " @@ -94,59 +109,132 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( return tool; } -CSVRender::WorldspaceWidget::dropType CSVRender::WorldspaceWidget::getDropType ( +CSVWidget::SceneToolToggle *CSVRender::WorldspaceWidget::makeSceneVisibilitySelector (CSVWidget::SceneToolbar *parent) +{ + mSceneElements= new CSVWidget::SceneToolToggle (parent, + "Scene Element Visibility", ":door.png"); + + addVisibilitySelectorButtons (mSceneElements); + + mSceneElements->setSelection (0xffffffff); + + connect (mSceneElements, SIGNAL (selectionChanged()), + this, SLOT (elementSelectionChanged())); + + return mSceneElements; +} + +CSVWidget::SceneToolRun *CSVRender::WorldspaceWidget::makeRunTool ( + CSVWidget::SceneToolbar *parent) +{ + CSMWorld::IdTable& debugProfiles = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); + + std::vector profiles; + + int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int defaultColumn = debugProfiles.findColumnIndex ( + CSMWorld::Columns::ColumnId_DefaultProfile); + + int size = debugProfiles.rowCount(); + + for (int i=0; i& data) { - dropType output = notCells; - bool firstIteration = true; + DropType output = Type_Other; - for (unsigned i = 0; i < data.size(); ++i) + for (std::vector::const_iterator iter (data.begin()); + iter!=data.end(); ++iter) { - if (data[i].getType() == CSMWorld::UniversalId::Type_Cell || - data[i].getType() == CSMWorld::UniversalId::Type_Cell_Missing) + DropType type = Type_Other; + + if (iter->getType()==CSMWorld::UniversalId::Type_Cell || + iter->getType()==CSMWorld::UniversalId::Type_Cell_Missing) { - if (*(data[i].getId().begin()) == '#') //exterior - { - if (firstIteration) - { - output = cellsExterior; - firstIteration = false; - continue; - } - - if (output == cellsInterior) - { - output = cellsMixed; - break; - } else { - output = cellsInterior; - } - } else //interior - { - if (firstIteration) - { - output = cellsInterior; - firstIteration = false; - continue; - } - - if (output == cellsExterior) - { - output = cellsMixed; - break; - } else { - output = cellsInterior; - } - } - } else { - output = notCells; - break; + type = iter->getId().substr (0, 1)=="#" ? Type_CellsExterior : Type_CellsInterior; } + else if (iter->getType()==CSMWorld::UniversalId::Type_DebugProfile) + type = Type_DebugProfile; + + if (iter==data.begin()) + output = type; + else if (output!=type) // mixed types -> ignore + return Type_Other; } return output; } +CSVRender::WorldspaceWidget::dropRequirments + CSVRender::WorldspaceWidget::getDropRequirements (DropType type) const +{ + if (type==Type_DebugProfile) + return canHandle; + + return ignored; +} + +bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& data, + DropType type) +{ + if (type==Type_DebugProfile) + { + if (mRun) + { + for (std::vector::const_iterator iter (data.begin()); + iter!=data.end(); ++iter) + mRun->addProfile (iter->getId()); + } + + return true; + } + + return false; +} + +unsigned int CSVRender::WorldspaceWidget::getElementMask() const +{ + return mSceneElements->getSelection(); +} + +void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( + CSVWidget::SceneToolToggle *tool) +{ + tool->addButton (":activator.png", Element_Reference, ":activator.png", "References"); + tool->addButton (":armor.png", Element_Terrain, ":armor.png", "Terrain"); + tool->addButton (":armor.png", Element_Water, ":armor.png", "Water"); + tool->addButton (":armor.png", Element_Pathgrid, ":armor.png", "Pathgrid"); +} + +CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() +{ + return mDocument; +} + void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { event->accept(); @@ -157,7 +245,6 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) event->accept(); } - void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); @@ -169,3 +256,61 @@ void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) emit dataDropped(mime->getData()); } //not handling drops from different documents at the moment } + +void CSVRender::WorldspaceWidget::runRequest (const std::string& profile) +{ + mDocument.startRunning (profile, getStartupInstruction()); +} + +void CSVRender::WorldspaceWidget::debugProfileDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (!mRun) + return; + + CSMWorld::IdTable& debugProfiles = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); + + int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + int state = debugProfiles.data (debugProfiles.index (i, stateColumn)).toInt(); + + // As of version 0.33 this case can not happen because debug profiles exist only in + // project or session scope, which means they will never be in deleted state. But we + // are adding the code for the sake of completeness and to avoid surprises if debug + // profile ever get extended to content scope. + if (state==CSMWorld::RecordBase::State_Deleted) + mRun->removeProfile (debugProfiles.data ( + debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); + } +} + +void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + if (parent.isValid()) + return; + + if (!mRun) + return; + + CSMWorld::IdTable& debugProfiles = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); + + int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (int i=start; i<=end; ++i) + { + mRun->removeProfile (debugProfiles.data ( + debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); + } +} + +void CSVRender::WorldspaceWidget::elementSelectionChanged() +{ + setVisibilityMask (getElementMask()); + flagAsModified(); +} diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 3b96779a8c..0754e9ecff 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -16,7 +16,9 @@ namespace CSMWorld namespace CSVWidget { class SceneToolMode; + class SceneToolToggle; class SceneToolbar; + class SceneToolRun; } namespace CSVRender @@ -28,15 +30,18 @@ namespace CSVRender CSVRender::Navigation1st m1st; CSVRender::NavigationFree mFree; CSVRender::NavigationOrbit mOrbit; + CSVWidget::SceneToolToggle *mSceneElements; + CSVWidget::SceneToolRun *mRun; + CSMDoc::Document& mDocument; public: - enum dropType + enum DropType { - cellsMixed, - cellsInterior, - cellsExterior, - notCells + Type_CellsInterior, + Type_CellsExterior, + Type_Other, + Type_DebugProfile }; enum dropRequirments @@ -53,19 +58,35 @@ namespace CSVRender ///< \attention The created tool is not added to the toolbar (via addTool). Doing that /// is the responsibility of the calling function. + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + CSVWidget::SceneToolToggle *makeSceneVisibilitySelector ( + CSVWidget::SceneToolbar *parent); + + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + CSVWidget::SceneToolRun *makeRunTool (CSVWidget::SceneToolbar *parent); + void selectDefaultNavigationMode(); - static dropType getDropType(const std::vector& data); + static DropType getDropType(const std::vector& data); - virtual dropRequirments getDropRequirements(dropType type) const = 0; + virtual dropRequirments getDropRequirements(DropType type) const; virtual void useViewHint (const std::string& hint); ///< Default-implementation: ignored. - virtual void handleDrop(const std::vector& data) = 0; + /// \return Drop handled? + virtual bool handleDrop (const std::vector& data, + DropType type); + + virtual unsigned int getElementMask() const; protected: - const CSMDoc::Document& mDocument; //for checking if drop comes from same document + + virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle *tool); + + CSMDoc::Document& getDocument(); private: @@ -75,6 +96,8 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); + virtual std::string getStartupInstruction() = 0; + private slots: void selectNavigationMode (const std::string& mode); @@ -92,6 +115,18 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; + virtual void runRequest (const std::string& profile); + + void debugProfileDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + + protected slots: + + void elementSelectionChanged(); + signals: void closeRequest(); diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp index 56bc1fdfee..72a022c9da 100644 --- a/apps/opencs/view/settings/dialog.cpp +++ b/apps/opencs/view/settings/dialog.cpp @@ -114,8 +114,20 @@ void CSVSettings::Dialog::show() setViewValues(); } - QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); - - move (screenCenter - geometry().center()); + QWidget *currView = QApplication::activeWindow(); + if(currView) + { + // place at the center of the window with focus + QSize size = currView->size(); + move(currView->geometry().x()+(size.width() - frameGeometry().width())/2, + currView->geometry().y()+(size.height() - frameGeometry().height())/2); + } + else + { + // something's gone wrong, place at the center of the screen + QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); + move(screenCenter - QPoint(frameGeometry().width()/2, + frameGeometry().height()/2)); + } QWidget::show(); } diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index e84f5cf4ba..5a2523789e 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -21,7 +21,7 @@ CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc: mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate ( - document.getUndoStack(), this); + document, this); mTable->setItemDelegateForColumn (0, mIdTypeDelegate); diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 056e548fbc..f234625859 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -4,9 +4,9 @@ #include #include -void CSVWidget::PushButton::setExtendedToolTip (const QString& text) +void CSVWidget::PushButton::setExtendedToolTip() { - QString tooltip = text; + QString tooltip = mToolTip; if (tooltip.isEmpty()) tooltip = "(Tool tip not implemented yet)"; @@ -20,6 +20,10 @@ void CSVWidget::PushButton::setExtendedToolTip (const QString& text) break; + case Type_TopAction: + + break; + case Type_Mode: tooltip += @@ -27,6 +31,16 @@ void CSVWidget::PushButton::setExtendedToolTip (const QString& text) "
    shift-left click to activate and keep panel open)"; break; + + case Type_Toggle: + + tooltip += "

    (left click to "; + tooltip += isChecked() ? "disable" : "enable"; + tooltip += "

    shift-left click to "; + tooltip += isChecked() ? "disable" : "enable"; + tooltip += " and keep panel open)"; + + break; } setToolTip (tooltip); @@ -42,11 +56,8 @@ void CSVWidget::PushButton::keyPressEvent (QKeyEvent *event) void CSVWidget::PushButton::keyReleaseEvent (QKeyEvent *event) { - if (event->key()==Qt::Key_Return || event->key()==Qt::Key_Enter) - { + if (event->key()==Qt::Key_Space) mKeepOpen = event->modifiers() & Qt::ShiftModifier; - emit clicked(); - } QPushButton::keyReleaseEvent (event); } @@ -61,15 +72,15 @@ CSVWidget::PushButton::PushButton (const QIcon& icon, Type type, const QString& QWidget *parent) : QPushButton (icon, "", parent), mKeepOpen (false), mType (type), mToolTip (tooltip) { - setCheckable (type==Type_Mode); - setExtendedToolTip (tooltip); + setCheckable (type==Type_Mode || type==Type_Toggle); + setExtendedToolTip(); } CSVWidget::PushButton::PushButton (Type type, const QString& tooltip, QWidget *parent) : QPushButton (parent), mKeepOpen (false), mType (type), mToolTip (tooltip) { - setCheckable (type==Type_Mode); - setExtendedToolTip (tooltip); + setCheckable (type==Type_Mode || type==Type_Toggle); + setExtendedToolTip(); } bool CSVWidget::PushButton::hasKeepOpen() const @@ -80,4 +91,9 @@ bool CSVWidget::PushButton::hasKeepOpen() const QString CSVWidget::PushButton::getBaseToolTip() const { return mToolTip; +} + +CSVWidget::PushButton::Type CSVWidget::PushButton::getType() const +{ + return mType; } \ No newline at end of file diff --git a/apps/opencs/view/widget/pushbutton.hpp b/apps/opencs/view/widget/pushbutton.hpp index 9b90bd0c66..35062a137b 100644 --- a/apps/opencs/view/widget/pushbutton.hpp +++ b/apps/opencs/view/widget/pushbutton.hpp @@ -14,7 +14,9 @@ namespace CSVWidget enum Type { Type_TopMode, // top level button for mode selector panel - Type_Mode // mode button + Type_TopAction, // top level button that triggers an action + Type_Mode, // mode button + Type_Toggle }; private: @@ -25,7 +27,7 @@ namespace CSVWidget private: - void setExtendedToolTip (const QString& text); + void setExtendedToolTip(); protected: @@ -49,6 +51,8 @@ namespace CSVWidget /// Return tooltip used at construction (without any button-specific modifications) QString getBaseToolTip() const; + + Type getType() const; }; } diff --git a/apps/opencs/view/widget/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp index e3f2dfd1c8..b8e9f895f4 100644 --- a/apps/opencs/view/widget/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -1,10 +1,12 @@ #include "scenetool.hpp" +#include + #include "scenetoolbar.hpp" -CSVWidget::SceneTool::SceneTool (SceneToolbar *parent) -: PushButton (PushButton::Type_TopMode, "", parent) +CSVWidget::SceneTool::SceneTool (SceneToolbar *parent, Type type) +: PushButton (type, "", parent) { setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); setIconSize (QSize (parent->getIconSize(), parent->getIconSize())); @@ -13,7 +15,20 @@ CSVWidget::SceneTool::SceneTool (SceneToolbar *parent) connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); } +void CSVWidget::SceneTool::activate() {} + +void CSVWidget::SceneTool::mouseReleaseEvent (QMouseEvent *event) +{ + if (getType()==Type_TopAction && event->button()==Qt::RightButton) + showPanel (parentWidget()->mapToGlobal (pos())); + else + PushButton::mouseReleaseEvent (event); +} + void CSVWidget::SceneTool::openRequest() { - showPanel (parentWidget()->mapToGlobal (pos())); + if (getType()==Type_TopAction) + activate(); + else + showPanel (parentWidget()->mapToGlobal (pos())); } diff --git a/apps/opencs/view/widget/scenetool.hpp b/apps/opencs/view/widget/scenetool.hpp index 24099683e0..cdea88096a 100644 --- a/apps/opencs/view/widget/scenetool.hpp +++ b/apps/opencs/view/widget/scenetool.hpp @@ -14,10 +14,18 @@ namespace CSVWidget public: - SceneTool (SceneToolbar *parent); + SceneTool (SceneToolbar *parent, Type type = Type_TopMode); virtual void showPanel (const QPoint& position) = 0; + /// This function will only called for buttons of type Type_TopAction. The default + /// implementation is empty. + virtual void activate(); + + protected: + + void mouseReleaseEvent (QMouseEvent *event); + private slots: void openRequest(); diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp new file mode 100644 index 0000000000..92f3193feb --- /dev/null +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -0,0 +1,151 @@ + +#include "scenetoolrun.hpp" + +#include + +#include +#include +#include +#include +#include + +void CSVWidget::SceneToolRun::adjustToolTips() +{ + QString toolTip = mToolTip; + + if (mSelected==mProfiles.end()) + toolTip += "

    No debug profile selected (function disabled)"; + else + { + toolTip += "

    Debug profile: " + QString::fromUtf8 (mSelected->c_str()); + toolTip += "

    (right click to switch to a different profile)"; + } + + setToolTip (toolTip); +} + +void CSVWidget::SceneToolRun::updateIcon() +{ + setIcon (QIcon (mSelected==mProfiles.end() ? mIconDisabled : mIcon)); +} + +void CSVWidget::SceneToolRun::updatePanel() +{ + mTable->setRowCount (mProfiles.size()); + + int i = 0; + + for (std::set::const_iterator iter (mProfiles.begin()); iter!=mProfiles.end(); + ++iter, ++i) + { + mTable->setItem (i, 0, new QTableWidgetItem (QString::fromUtf8 (iter->c_str()))); + + mTable->setItem (i, 1, new QTableWidgetItem ( + QApplication::style()->standardIcon (QStyle::SP_TitleBarCloseButton), "")); + } +} + +CSVWidget::SceneToolRun::SceneToolRun (SceneToolbar *parent, const QString& toolTip, + const QString& icon, const QString& iconDisabled, const std::vector& profiles) +: SceneTool (parent, Type_TopAction), mProfiles (profiles.begin(), profiles.end()), + mSelected (mProfiles.begin()), mToolTip (toolTip), mIcon (icon), + mIconDisabled (iconDisabled) +{ + updateIcon(); + adjustToolTips(); + + mPanel = new QFrame (this, Qt::Popup); + + QHBoxLayout *layout = new QHBoxLayout (mPanel); + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + mTable = new QTableWidget (0, 2, this); + + mTable->setShowGrid (false); + mTable->verticalHeader()->hide(); + mTable->horizontalHeader()->hide(); + mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); + mTable->setSelectionMode (QAbstractItemView::NoSelection); + + layout->addWidget (mTable); + + connect (mTable, SIGNAL (clicked (const QModelIndex&)), + this, SLOT (clicked (const QModelIndex&))); +} + +void CSVWidget::SceneToolRun::showPanel (const QPoint& position) +{ + updatePanel(); + + mPanel->move (position); + mPanel->show(); +} + +void CSVWidget::SceneToolRun::activate() +{ + if (mSelected!=mProfiles.end()) + emit runRequest (*mSelected); +} + +void CSVWidget::SceneToolRun::removeProfile (const std::string& profile) +{ + std::set::iterator iter = mProfiles.find (profile); + + if (iter!=mProfiles.end()) + { + if (iter==mSelected) + { + if (iter!=mProfiles.begin()) + --mSelected; + else + ++mSelected; + } + + mProfiles.erase (iter); + + if (mSelected==mProfiles.end()) + updateIcon(); + + adjustToolTips(); + } +} + +void CSVWidget::SceneToolRun::addProfile (const std::string& profile) +{ + std::set::iterator iter = mProfiles.find (profile); + + if (iter==mProfiles.end()) + { + mProfiles.insert (profile); + + if (mSelected==mProfiles.end()) + { + mSelected = mProfiles.begin(); + updateIcon(); + } + + adjustToolTips(); + } +} + +void CSVWidget::SceneToolRun::clicked (const QModelIndex& index) +{ + if (index.column()==0) + { + // select profile + mSelected = mProfiles.begin(); + std::advance (mSelected, index.row()); + mPanel->hide(); + adjustToolTips(); + } + else if (index.column()==1) + { + // remove profile from list + std::set::iterator iter = mProfiles.begin(); + std::advance (iter, index.row()); + removeProfile (*iter); + updatePanel(); + } +} \ No newline at end of file diff --git a/apps/opencs/view/widget/scenetoolrun.hpp b/apps/opencs/view/widget/scenetoolrun.hpp new file mode 100644 index 0000000000..4396c22881 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolrun.hpp @@ -0,0 +1,64 @@ +#ifndef CSV_WIDGET_SCENETOOLRUN_H +#define CSV_WIDGET_SCENETOOLRUN_H + +#include +#include + +#include "scenetool.hpp" + +class QFrame; +class QTableWidget; +class QModelIndex; + +namespace CSVWidget +{ + class SceneToolRun : public SceneTool + { + Q_OBJECT + + std::set mProfiles; + std::set::iterator mSelected; + QString mToolTip; + QString mIcon; + QString mIconDisabled; + QFrame *mPanel; + QTableWidget *mTable; + + private: + + void adjustToolTips(); + + void updateIcon(); + + void updatePanel(); + + public: + + SceneToolRun (SceneToolbar *parent, const QString& toolTip, const QString& icon, + const QString& iconDisabled, const std::vector& profiles); + + virtual void showPanel (const QPoint& position); + + virtual void activate(); + + /// \attention This function does not remove the profile from the profile selection + /// panel. + void removeProfile (const std::string& profile); + + /// \attention This function doe not add the profile to the profile selection + /// panel. This only happens when the panel is re-opened. + /// + /// \note Adding profiles that are already listed is a no-op. + void addProfile (const std::string& profile); + + private slots: + + void clicked (const QModelIndex& index); + + signals: + + void runRequest (const std::string& profile); + }; +} + +#endif diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp new file mode 100644 index 0000000000..07c448e453 --- /dev/null +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -0,0 +1,205 @@ + +#include "scenetooltoggle.hpp" + +#include + +#include +#include +#include +#include + +#include "scenetoolbar.hpp" +#include "pushbutton.hpp" + +void CSVWidget::SceneToolToggle::adjustToolTip() +{ + QString toolTip = mToolTip; + + toolTip += "

    Currently enabled: "; + + bool first = true; + + for (std::map::const_iterator iter (mButtons.begin()); + iter!=mButtons.end(); ++iter) + if (iter->first->isChecked()) + { + if (!first) + toolTip += ", "; + else + first = false; + + toolTip += iter->second.mName; + } + + if (first) + toolTip += "none"; + + toolTip += "

    (left click to alter selection)"; + + setToolTip (toolTip); +} + +void CSVWidget::SceneToolToggle::adjustIcon() +{ + unsigned int selection = getSelection(); + if (!selection) + setIcon (QIcon (QString::fromUtf8 (mEmptyIcon.c_str()))); + else + { + QPixmap pixmap (48, 48); + pixmap.fill (QColor (0, 0, 0, 0)); + + { + QPainter painter (&pixmap); + + for (std::map::const_iterator iter (mButtons.begin()); + iter!=mButtons.end(); ++iter) + if (iter->first->isChecked()) + { + painter.drawImage (getIconBox (iter->second.mIndex), + QImage (QString::fromUtf8 (iter->second.mSmallIcon.c_str()))); + } + } + + setIcon (pixmap); + } +} + +QRect CSVWidget::SceneToolToggle::getIconBox (int index) const +{ + // layout for a 3x3 grid + int xMax = 3; + int yMax = 3; + + // icon size + int xBorder = 1; + int yBorder = 1; + + int iconXSize = (mIconSize-xBorder*(xMax+1))/xMax; + int iconYSize = (mIconSize-yBorder*(yMax+1))/yMax; + + int y = index / xMax; + int x = index % xMax; + + int total = mButtons.size(); + + int actualYIcons = total/xMax; + + if (total % xMax) + ++actualYIcons; + + if (actualYIcons!=yMax) + { + // space out icons vertically, if there aren't enough to populate all rows + int diff = yMax - actualYIcons; + yBorder += (diff*(yBorder+iconXSize)) / (actualYIcons+1); + } + + if (y==actualYIcons-1) + { + // generating the last row of icons + int actualXIcons = total % xMax; + + if (actualXIcons) + { + // space out icons horizontally, if there aren't enough to fill the last row + int diff = xMax - actualXIcons; + + xBorder += (diff*(xBorder+iconXSize)) / (actualXIcons+1); + } + } + + return QRect ((iconXSize+xBorder)*x+xBorder, (iconYSize+yBorder)*y+yBorder, + iconXSize, iconYSize); +} + +CSVWidget::SceneToolToggle::SceneToolToggle (SceneToolbar *parent, const QString& toolTip, + const std::string& emptyIcon) +: SceneTool (parent), mEmptyIcon (emptyIcon), mButtonSize (parent->getButtonSize()), + mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (0) +{ + mPanel = new QFrame (this, Qt::Popup); + + mLayout = new QHBoxLayout (mPanel); + + mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + + mPanel->setLayout (mLayout); +} + +void CSVWidget::SceneToolToggle::showPanel (const QPoint& position) +{ + mPanel->move (position); + mPanel->show(); + + if (mFirst) + mFirst->setFocus (Qt::OtherFocusReason); +} + +void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int id, + const std::string& smallIcon, const QString& name, const QString& tooltip) +{ + if (mButtons.size()>=9) + throw std::runtime_error ("Exceeded number of buttons in toggle type tool"); + + PushButton *button = new PushButton (QIcon (QPixmap (icon.c_str())), + PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); + + button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize (QSize (mIconSize, mIconSize)); + button->setFixedSize (mButtonSize, mButtonSize); + + mLayout->addWidget (button); + + ButtonDesc desc; + desc.mId = id; + desc.mSmallIcon = smallIcon; + desc.mName = name; + desc.mIndex = mButtons.size(); + + mButtons.insert (std::make_pair (button, desc)); + + connect (button, SIGNAL (clicked()), this, SLOT (selected())); + + if (mButtons.size()==1) + mFirst = button; +} + +unsigned int CSVWidget::SceneToolToggle::getSelection() const +{ + unsigned int selection = 0; + + for (std::map::const_iterator iter (mButtons.begin()); + iter!=mButtons.end(); ++iter) + if (iter->first->isChecked()) + selection |= iter->second.mId; + + return selection; +} + +void CSVWidget::SceneToolToggle::setSelection (unsigned int selection) +{ + for (std::map::iterator iter (mButtons.begin()); + iter!=mButtons.end(); ++iter) + iter->first->setChecked (selection & iter->second.mId); + + adjustToolTip(); + adjustIcon(); +} + +void CSVWidget::SceneToolToggle::selected() +{ + std::map::const_iterator iter = + mButtons.find (dynamic_cast (sender())); + + if (iter!=mButtons.end()) + { + if (!iter->first->hasKeepOpen()) + mPanel->hide(); + + adjustToolTip(); + adjustIcon(); + + emit selectionChanged(); + } +} diff --git a/apps/opencs/view/widget/scenetooltoggle.hpp b/apps/opencs/view/widget/scenetooltoggle.hpp new file mode 100644 index 0000000000..55e6975249 --- /dev/null +++ b/apps/opencs/view/widget/scenetooltoggle.hpp @@ -0,0 +1,75 @@ +#ifndef CSV_WIDGET_SCENETOOL_TOGGLE_H +#define CSV_WIDGET_SCENETOOL_TOGGLE_H + +#include "scenetool.hpp" + +#include + +class QHBoxLayout; +class QRect; + +namespace CSVWidget +{ + class SceneToolbar; + class PushButton; + + ///< \brief Multi-Toggle tool + class SceneToolToggle : public SceneTool + { + Q_OBJECT + + struct ButtonDesc + { + unsigned int mId; + std::string mSmallIcon; + QString mName; + int mIndex; + }; + + std::string mEmptyIcon; + QWidget *mPanel; + QHBoxLayout *mLayout; + std::map mButtons; // widget, id + int mButtonSize; + int mIconSize; + QString mToolTip; + PushButton *mFirst; + + void adjustToolTip(); + + void adjustIcon(); + + QRect getIconBox (int index) const; + + public: + + SceneToolToggle (SceneToolbar *parent, const QString& toolTip, + const std::string& emptyIcon); + + virtual void showPanel (const QPoint& position); + + /// \attention After the last button has been added, setSelection must be called at + /// least once to finalise the layout. + /// + /// \note The layout algorithm can not handle more than 9 buttons. To prevent this An + /// attempt to add more will result in an exception being thrown. + /// The small icons will be sized at (x-4)/3 (where x is the main icon size). + void addButton (const std::string& icon, unsigned int id, + const std::string& smallIcon, const QString& name, const QString& tooltip = ""); + + unsigned int getSelection() const; + + /// \param or'ed button IDs. IDs that do not exist will be ignored. + void setSelection (unsigned int selection); + + signals: + + void selectionChanged(); + + private slots: + + void selected(); + }; +} + +#endif diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index d753a2b476..a24c58e544 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -1,7 +1,16 @@ #include "creator.hpp" -CSVWorld::Creator:: ~Creator() {} +#include + +CSVWorld::Creator::~Creator() {} + +void CSVWorld::Creator::setScope (unsigned int scope) +{ + if (scope!=CSMWorld::Scope_Content) + throw std::logic_error ("Invalid scope in creator"); +} + CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {} diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp index 88da70330d..8e50e87154 100644 --- a/apps/opencs/view/world/creator.hpp +++ b/apps/opencs/view/world/creator.hpp @@ -1,9 +1,14 @@ #ifndef CSV_WORLD_CREATOR_H #define CSV_WORLD_CREATOR_H +#include + #include + #include "../../model/world/universalid.hpp" +#include "../../model/world/scope.hpp" + class QUndoStack; namespace CSMWorld @@ -32,6 +37,9 @@ namespace CSVWorld virtual void toggleWidgets(bool active = true) = 0; + /// Default implementation: Throw an exception if scope!=Scope_Content. + virtual void setScope (unsigned int scope); + signals: void done(); @@ -68,7 +76,7 @@ namespace CSVWorld /// \note The function always returns 0. }; - template + template class CreatorFactory : public CreatorFactoryBase { public: @@ -81,11 +89,15 @@ namespace CSVWorld /// records should be provided. }; - template - Creator *CreatorFactory::makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + template + Creator *CreatorFactory::makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) const { - return new CreatorT (data, undoStack, id); + std::auto_ptr creator (new CreatorT (data, undoStack, id)); + + creator->setScope (scope); + + return creator.release(); } } diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 31ec18d52e..46ca17a292 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -6,11 +6,11 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const IconList &icons, - QUndoStack &undoStack, + CSMDoc::Document& document, const QString &pageName, const QString &settingName, QObject *parent) - : EnumDelegate (values, undoStack, parent), mDisplayMode (Mode_TextOnly), + : EnumDelegate (values, document, parent), mDisplayMode (Mode_TextOnly), mIcons (icons), mIconSize (QSize(16, 16)), mIconLeftOffset(3), mTextLeftOffset(8), mSettingKey (pageName + '/' + settingName) { @@ -38,7 +38,7 @@ void CSVWorld::DataDisplayDelegate::buildPixmaps () } } -void CSVWorld::DataDisplayDelegate::setIconSize(const QSize size) +void CSVWorld::DataDisplayDelegate::setIconSize(const QSize& size) { mIconSize = size; buildPixmaps(); @@ -126,8 +126,6 @@ void CSVWorld::DataDisplayDelegate::updateDisplayMode (const QString &mode) CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() { - mIcons.clear(); - mPixmaps.clear(); } void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, QString iconFilename) @@ -137,11 +135,10 @@ void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, } -CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate (QUndoStack& undoStack, - QObject *parent) const +CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate ( + CSMDoc::Document& document, QObject *parent) const { - - return new DataDisplayDelegate (mValues, mIcons, undoStack, "", "", parent); + return new DataDisplayDelegate (mValues, mIcons, document, "", "", parent); } diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index ef453c58f2..73790e3c65 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -40,7 +40,7 @@ namespace CSVWorld public: explicit DataDisplayDelegate (const ValueList & values, const IconList & icons, - QUndoStack& undoStack, + CSMDoc::Document& document, const QString &pageName, const QString &settingName, QObject *parent); @@ -50,7 +50,7 @@ namespace CSVWorld virtual void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; /// pass a QSize defining height / width of icon. Default is QSize (16,16). - void setIconSize (const QSize icon); + void setIconSize (const QSize& icon); /// offset the horizontal position of the icon from the left edge of the cell. Default is 3 pixels. void setIconLeftOffset (int offset); @@ -82,7 +82,7 @@ namespace CSVWorld public: - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + virtual CommandDelegate *makeDelegate (CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. protected: diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index bcf108934f..57069bec05 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -167,10 +167,10 @@ void CSVWorld::DialogueDelegateDispatcherProxy::tableMimeDataDropped(const std:: ==============================DialogueDelegateDispatcher========================================== */ -CSVWorld::DialogueDelegateDispatcher::DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, QUndoStack& undoStack) : +CSVWorld::DialogueDelegateDispatcher::DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, CSMDoc::Document& document) : mParent(parent), mTable(table), -mUndoStack(undoStack), +mDocument (document), mNotEditableDelegate(table, parent) { } @@ -182,7 +182,7 @@ CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CS if (delegateIt == mDelegates.end()) { delegate = CommandDelegateFactoryCollection::get().makeDelegate ( - display, mUndoStack, mParent); + display, mDocument, mParent); mDelegates.insert(std::make_pair(display, delegate)); } else { @@ -266,7 +266,6 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: editor = delegateIt->second->createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index, display); DialogueDelegateDispatcherProxy* proxy = new DialogueDelegateDispatcherProxy(editor, display); - bool skip = false; if (qobject_cast(editor)) { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); @@ -274,27 +273,22 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: proxy, SLOT(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*))); connect(proxy, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); - skip = true; } - if(!skip && qobject_cast(editor)) + else if (qobject_cast(editor)) { connect(editor, SIGNAL(stateChanged(int)), proxy, SLOT(editorDataCommited())); - skip = true; } - if(!skip && qobject_cast(editor)) + else if (qobject_cast(editor)) { connect(editor, SIGNAL(textChanged()), proxy, SLOT(editorDataCommited())); - skip = true; } - if(!skip && qobject_cast(editor)) + else if (qobject_cast(editor)) { connect(editor, SIGNAL(currentIndexChanged (int)), proxy, SLOT(editorDataCommited())); - skip = true; } - if(!skip && qobject_cast(editor)) + else if (qobject_cast(editor)) { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); - skip = true; } connect(proxy, SIGNAL(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display)), this, SLOT(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display))); @@ -315,12 +309,12 @@ CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() =============================================================EditWidget===================================================== */ -CSVWorld::EditWidget::EditWidget(QWidget *parent, int row, CSMWorld::IdTable* table, QUndoStack& undoStack, bool createAndDelete) : -mDispatcher(this, table, undoStack), +CSVWorld::EditWidget::EditWidget(QWidget *parent, int row, CSMWorld::IdTable* table, CSMDoc::Document& document, bool createAndDelete) : +mDispatcher(this, table, document), QScrollArea(parent), mWidgetMapper(NULL), mMainWidget(NULL), -mUndoStack(undoStack), +mDocument (document), mTable(table) { remake (row); @@ -478,7 +472,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM mMainLayout = new QVBoxLayout(mainWidget); - mEditWidget = new EditWidget(mainWidget, mRow, mTable, mUndoStack, false); + mEditWidget = new EditWidget(mainWidget, mRow, mTable, document, false); connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index cbca0159cf..4c260170f3 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -101,14 +101,14 @@ namespace CSVWorld CSMWorld::IdTable* mTable; - QUndoStack& mUndoStack; + CSMDoc::Document& mDocument; NotEditableSubDelegate mNotEditableDelegate; std::vector mProxys; //once we move to the C++11 we should use unique_ptr public: - DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, QUndoStack& undoStack); + DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, CSMDoc::Document& document); ~DialogueDelegateDispatcher(); @@ -145,11 +145,11 @@ namespace CSVWorld DialogueDelegateDispatcher mDispatcher; QWidget* mMainWidget; CSMWorld::IdTable* mTable; - QUndoStack& mUndoStack; + CSMDoc::Document& mDocument; public: - EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, QUndoStack& undoStack, bool createAndDelete = false); + EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, CSMDoc::Document& document, bool createAndDelete = false); void remake(int row); diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 377f479bff..168e5cb0a3 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -35,8 +35,8 @@ void CSVWorld::EnumDelegate::addCommands (QAbstractItemModel *model, CSVWorld::EnumDelegate::EnumDelegate (const std::vector >& values, - QUndoStack& undoStack, QObject *parent) -: CommandDelegate (undoStack, parent), mValues (values) + CSMDoc::Document& document, QObject *parent) +: CommandDelegate (document, parent), mValues (values) { } @@ -140,10 +140,10 @@ CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const std::vector >& values, - QUndoStack& undoStack, QObject *parent); + CSMDoc::Document& document, QObject *parent); virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option, @@ -64,7 +64,7 @@ namespace CSVWorld EnumDelegateFactory (const std::vector& names, bool allowNone = false); /// \param allowNone Use value of -1 for "none selected" (empty string) - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + virtual CommandDelegate *makeDelegate (CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. void add (int value, const QString& name); diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 31c216e2cb..7beaf9c67d 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -7,6 +7,10 @@ #include #include #include +#include +#include + +#include #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" @@ -46,32 +50,87 @@ std::string CSVWorld::GenericCreator::getId() const void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} +void CSVWorld::GenericCreator::pushCommand (std::auto_ptr command, + const std::string& id) +{ + mUndoStack.push (command.release()); +} + CSMWorld::Data& CSVWorld::GenericCreator::getData() const { return mData; } +QUndoStack& CSVWorld::GenericCreator::getUndoStack() +{ + return mUndoStack; +} + const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const { return mListId; } +std::string CSVWorld::GenericCreator::getNamespace() const +{ + CSMWorld::Scope scope = CSMWorld::Scope_Content; + + if (mScope) + { + scope = static_cast (mScope->itemData (mScope->currentIndex()).toInt()); + } + else + { + if (mScopes & CSMWorld::Scope_Project) + scope = CSMWorld::Scope_Project; + else if (mScopes & CSMWorld::Scope_Session) + scope = CSMWorld::Scope_Session; + } + + switch (scope) + { + case CSMWorld::Scope_Content: return ""; + case CSMWorld::Scope_Project: return "project::"; + case CSMWorld::Scope_Session: return "session::"; + } + + return ""; +} + +void CSVWorld::GenericCreator::updateNamespace() +{ + std::string namespace_ = getNamespace(); + + mValidator->setNamespace (namespace_); + + int index = mId->text().indexOf ("::"); + + if (index==-1) + { + // no namespace in old text + mId->setText (QString::fromUtf8 (namespace_.c_str()) + mId->text()); + } + else + { + std::string oldNamespace = + Misc::StringUtils::lowerCase (mId->text().left (index).toUtf8().constData()); + + if (oldNamespace=="project" || oldNamespace=="session") + mId->setText (QString::fromUtf8 (namespace_.c_str()) + mId->text().mid (index+2)); + } +} + CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, bool relaxedIdRules): - - mData (data), - mUndoStack (undoStack), - mListId (id), - mLocked (false), - mCloneMode(false), - mClonedType(CSMWorld::UniversalId::Type_None) - + const CSMWorld::UniversalId& id, bool relaxedIdRules) +: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false), mCloneMode (false), + mClonedType (CSMWorld::UniversalId::Type_None), mScopes (CSMWorld::Scope_Content), mScope (0), + mScopeLabel (0) { mLayout = new QHBoxLayout; mLayout->setContentsMargins (0, 0, 0, 0); mId = new QLineEdit; - mId->setValidator (new IdValidator (relaxedIdRules, this)); + mId->setValidator (mValidator = new IdValidator (relaxedIdRules, this)); mLayout->addWidget (mId, 1); mCreate = new QPushButton ("Create"); @@ -99,22 +158,17 @@ void CSVWorld::GenericCreator::reset() mCloneMode = false; mId->setText (""); update(); + updateNamespace(); } std::string CSVWorld::GenericCreator::getErrors() const { std::string errors; - std::string id = getId(); - - if (id.empty()) - { - errors = "Missing ID"; - } - else if (mData.hasId (id)) - { + if (!mId->hasAcceptableInput()) + errors = mValidator->getError(); + else if (mData.hasId (getId())) errors = "ID is already in use"; - } return errors; } @@ -128,29 +182,27 @@ void CSVWorld::GenericCreator::create() { if (!mLocked) { + std::string id = getId(); + + std::auto_ptr command; + if (mCloneMode) { - std::string id = getId(); - std::auto_ptr command (new CSMWorld::CloneCommand ( + command.reset (new CSMWorld::CloneCommand ( dynamic_cast (*mData.getTableModel(mListId)), mClonedId, id, mClonedType)); - - mUndoStack.push(command.release()); - - emit done(); - emit requestFocus(id); - } else { - std::string id = getId(); - - std::auto_ptr command (new CSMWorld::CreateCommand ( - dynamic_cast (*mData.getTableModel (mListId)), id)); - - configureCreateCommand (*command); - - mUndoStack.push (command.release()); - - emit done(); - emit requestFocus (id); } + else + { + command.reset (new CSMWorld::CreateCommand ( + dynamic_cast (*mData.getTableModel (mListId)), id)); + + } + + configureCreateCommand (*command); + pushCommand (command, id); + + emit done(); + emit requestFocus(id); } } @@ -165,3 +217,49 @@ void CSVWorld::GenericCreator::cloneMode(const std::string& originId, void CSVWorld::GenericCreator::toggleWidgets(bool active) { } + +void CSVWorld::GenericCreator::setScope (unsigned int scope) +{ + mScopes = scope; + int count = (mScopes & CSMWorld::Scope_Content) + (mScopes & CSMWorld::Scope_Project) + + (mScopes & CSMWorld::Scope_Session); + + // scope selector widget + if (count>1) + { + mScope = new QComboBox (this); + insertAtBeginning (mScope, false); + + if (mScopes & CSMWorld::Scope_Content) + mScope->addItem ("Content", static_cast (CSMWorld::Scope_Content)); + + if (mScopes & CSMWorld::Scope_Project) + mScope->addItem ("Project", static_cast (CSMWorld::Scope_Project)); + + if (mScopes & CSMWorld::Scope_Session) + mScope->addItem ("Session", static_cast (CSMWorld::Scope_Session)); + + connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (scopeChanged (int))); + + mScopeLabel = new QLabel ("Scope", this); + insertAtBeginning (mScopeLabel, false); + + mScope->setCurrentIndex (0); + } + else + { + delete mScope; + mScope = 0; + + delete mScopeLabel; + mScopeLabel = 0; + } + + updateNamespace(); +} + +void CSVWorld::GenericCreator::scopeChanged (int index) +{ + update(); + updateNamespace(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 714853f986..8d9e329663 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -1,14 +1,18 @@ #ifndef CSV_WORLD_GENERICCREATOR_H #define CSV_WORLD_GENERICCREATOR_H +#include + +#include "../../model/world/universalid.hpp" + +#include "creator.hpp" + class QString; class QPushButton; class QLineEdit; class QHBoxLayout; - -#include "creator.hpp" - -#include "../../model/world/universalid.hpp" +class QComboBox; +class QLabel; namespace CSMWorld { @@ -17,6 +21,8 @@ namespace CSMWorld namespace CSVWorld { + class IdValidator; + class GenericCreator : public Creator { Q_OBJECT @@ -31,6 +37,10 @@ namespace CSVWorld bool mLocked; std::string mClonedId; CSMWorld::UniversalId::Type mClonedType; + unsigned int mScopes; + QComboBox *mScope; + QLabel *mScopeLabel; + IdValidator *mValidator; protected: bool mCloneMode; @@ -48,12 +58,26 @@ namespace CSVWorld virtual std::string getId() const; + /// Allow subclasses to add additional data to \a command. virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + /// Allow subclasses to wrap the create command together with additional commands + /// into a macro. + virtual void pushCommand (std::auto_ptr command, + const std::string& id); + CSMWorld::Data& getData() const; + QUndoStack& getUndoStack(); + const CSMWorld::UniversalId& getCollectionId() const; + std::string getNamespace() const; + + private: + + void updateNamespace(); + public: GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, @@ -65,18 +89,22 @@ namespace CSVWorld virtual void toggleWidgets (bool active = true); - virtual void cloneMode(const std::string& originId, + virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. + virtual void setScope (unsigned int scope); + private slots: void textChanged (const QString& text); void create(); + + void scopeChanged (int index); }; } diff --git a/apps/opencs/view/world/idtypedelegate.cpp b/apps/opencs/view/world/idtypedelegate.cpp index 6b4d442f30..543c96a24c 100755 --- a/apps/opencs/view/world/idtypedelegate.cpp +++ b/apps/opencs/view/world/idtypedelegate.cpp @@ -3,8 +3,8 @@ #include "../../model/world/universalid.hpp" CSVWorld::IdTypeDelegate::IdTypeDelegate - (const ValueList &values, const IconList &icons, QUndoStack& undoStack, QObject *parent) - : DataDisplayDelegate (values, icons, undoStack, + (const ValueList &values, const IconList &icons, CSMDoc::Document& document, QObject *parent) + : DataDisplayDelegate (values, icons, document, "Display Format", "Referenceable ID Type Display", parent) {} @@ -20,8 +20,8 @@ CSVWorld::IdTypeDelegateFactory::IdTypeDelegateFactory() } } -CSVWorld::CommandDelegate *CSVWorld::IdTypeDelegateFactory::makeDelegate (QUndoStack& undoStack, - QObject *parent) const +CSVWorld::CommandDelegate *CSVWorld::IdTypeDelegateFactory::makeDelegate ( + CSMDoc::Document& document, QObject *parent) const { - return new IdTypeDelegate (mValues, mIcons, undoStack, parent); + return new IdTypeDelegate (mValues, mIcons, document, parent); } diff --git a/apps/opencs/view/world/idtypedelegate.hpp b/apps/opencs/view/world/idtypedelegate.hpp index bed81f2d56..e9a0af68c5 100755 --- a/apps/opencs/view/world/idtypedelegate.hpp +++ b/apps/opencs/view/world/idtypedelegate.hpp @@ -11,7 +11,7 @@ namespace CSVWorld class IdTypeDelegate : public DataDisplayDelegate { public: - IdTypeDelegate (const ValueList &mValues, const IconList &icons, QUndoStack& undoStack, QObject *parent); + IdTypeDelegate (const ValueList &mValues, const IconList &icons, CSMDoc::Document& document, QObject *parent); }; class IdTypeDelegateFactory : public DataDisplayDelegateFactory @@ -20,7 +20,7 @@ namespace CSVWorld IdTypeDelegateFactory(); - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + virtual CommandDelegate *makeDelegate (CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 7c210daaec..7caa20f9b5 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -1,6 +1,8 @@ #include "idvalidator.hpp" +#include + bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const { if (c.isLetter() || c=='_') @@ -18,6 +20,8 @@ CSVWorld::IdValidator::IdValidator (bool relaxed, QObject *parent) QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const { + mError.clear(); + if (mRelaxed) { if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1 || input.indexOf ("#")!=-1) @@ -25,12 +29,95 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con } else { - bool first = true; + if (input.isEmpty()) + { + mError = "Missing ID"; + return QValidator::Intermediate; + } - for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false) - if (!isValid (*iter, first)) - return QValidator::Invalid; + bool first = true; + bool scope = false; + bool prevScope = false; + + QString::const_iterator iter = input.begin(); + + if (!mNamespace.empty()) + { + std::string namespace_ = input.left (mNamespace.size()).toUtf8().constData(); + + if (Misc::StringUtils::lowerCase (namespace_)!=mNamespace) + return QValidator::Invalid; // incorrect namespace + + iter += namespace_.size(); + first = false; + prevScope = true; + } + else + { + int index = input.indexOf (":"); + + if (index!=-1) + { + QString namespace_ = input.left (index); + + if (namespace_=="project" || namespace_=="session") + return QValidator::Invalid; // reserved namespace + } + } + + for (; iter!=input.end(); ++iter, first = false) + { + if (*iter==':') + { + if (first) + return QValidator::Invalid; // scope operator at the beginning + + if (scope) + { + scope = false; + prevScope = true; + } + else + { + if (prevScope) + return QValidator::Invalid; // sequence of two scope operators + + scope = true; + } + } + else if (scope) + return QValidator::Invalid; // incomplete scope operator + else + { + prevScope = false; + + if (!isValid (*iter, first)) + return QValidator::Invalid; + } + } + + if (scope) + { + mError = "ID ending with incomplete scope operator"; + return QValidator::Intermediate; + } + + if (prevScope) + { + mError = "ID ending with scope operator"; + return QValidator::Intermediate; + } } return QValidator::Acceptable; +} + +void CSVWorld::IdValidator::setNamespace (const std::string& namespace_) +{ + mNamespace = Misc::StringUtils::lowerCase (namespace_); +} + +std::string CSVWorld::IdValidator::getError() const +{ + return mError; } \ No newline at end of file diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index 8ca162440b..a9df9580ab 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -1,6 +1,8 @@ #ifndef CSV_WORLD_IDVALIDATOR_H #define CSV_WORLD_IDVALIDATOR_H +#include + #include namespace CSVWorld @@ -8,6 +10,8 @@ namespace CSVWorld class IdValidator : public QValidator { bool mRelaxed; + std::string mNamespace; + mutable std::string mError; private: @@ -20,6 +24,14 @@ namespace CSVWorld virtual State validate (QString& input, int& pos) const; + void setNamespace (const std::string& namespace_); + + /// Return a description of the error that resulted in the last call of validate + /// returning QValidator::Intermediate. If the last call to validate returned + /// a different value (or if there was no such call yet), an empty string is + /// returned. + std::string getError() const; + }; } diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index f09222930e..1d914716ba 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -53,6 +53,22 @@ CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); } +void CSVWorld::InfoCreator::cloneMode (const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSMWorld::IdTable& infoTable = + dynamic_cast (*getData().getTableModel (getCollectionId())); + + int topicColumn = infoTable.findColumnIndex ( + getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? + CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); + + mTopic->setText ( + infoTable.data (infoTable.getModelIndex (originId, topicColumn)).toString()); + + GenericCreator::cloneMode (originId, type); +} + void CSVWorld::InfoCreator::reset() { mTopic->setText (""); diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp index e9cb7e5960..2296a82973 100644 --- a/apps/opencs/view/world/infocreator.hpp +++ b/apps/opencs/view/world/infocreator.hpp @@ -27,6 +27,9 @@ namespace CSVWorld InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + virtual void cloneMode (const std::string& originId, + const CSMWorld::UniversalId::Type type); + virtual void reset(); virtual std::string getErrors() const; diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp index 4fe7031ce2..13931b7ad9 100644 --- a/apps/opencs/view/world/recordstatusdelegate.cpp +++ b/apps/opencs/view/world/recordstatusdelegate.cpp @@ -9,16 +9,16 @@ CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, const IconList & icons, - QUndoStack &undoStack, QObject *parent) - : DataDisplayDelegate (values, icons, undoStack, + CSMDoc::Document& document, QObject *parent) + : DataDisplayDelegate (values, icons, document, "Display Format", "Record Status Display", parent) {} -CSVWorld::CommandDelegate *CSVWorld::RecordStatusDelegateFactory::makeDelegate (QUndoStack& undoStack, - QObject *parent) const +CSVWorld::CommandDelegate *CSVWorld::RecordStatusDelegateFactory::makeDelegate ( + CSMDoc::Document& document, QObject *parent) const { - return new RecordStatusDelegate (mValues, mIcons, undoStack, parent); + return new RecordStatusDelegate (mValues, mIcons, document, parent); } CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory() diff --git a/apps/opencs/view/world/recordstatusdelegate.hpp b/apps/opencs/view/world/recordstatusdelegate.hpp index 1b42223afc..fbdaed538b 100644 --- a/apps/opencs/view/world/recordstatusdelegate.hpp +++ b/apps/opencs/view/world/recordstatusdelegate.hpp @@ -19,7 +19,7 @@ namespace CSVWorld explicit RecordStatusDelegate(const ValueList& values, const IconList& icons, - QUndoStack& undoStack, QObject *parent = 0); + CSMDoc::Document& document, QObject *parent = 0); }; class RecordStatusDelegateFactory : public DataDisplayDelegateFactory @@ -28,7 +28,7 @@ namespace CSVWorld RecordStatusDelegateFactory(); - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + virtual CommandDelegate *makeDelegate (CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 7a5fca8538..e8055ed317 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -42,6 +42,13 @@ void CSVWorld::ReferenceableCreator::reset() GenericCreator::reset(); } +void CSVWorld::ReferenceableCreator::cloneMode (const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + GenericCreator::cloneMode (originId, type); + mType->setCurrentIndex (mType->findData (static_cast (type))); +} + void CSVWorld::ReferenceableCreator::toggleWidgets(bool active) { CSVWorld::GenericCreator::toggleWidgets(active); diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp index 88545575e3..14ad24b292 100644 --- a/apps/opencs/view/world/referenceablecreator.hpp +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -23,6 +23,10 @@ namespace CSVWorld const CSMWorld::UniversalId& id); virtual void reset(); + + virtual void cloneMode (const std::string& originId, + const CSMWorld::UniversalId::Type type); + virtual void toggleWidgets(bool active = true); }; diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 6b8e02da0d..1e3cc00d7d 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -16,11 +16,58 @@ std::string CSVWorld::ReferenceCreator::getId() const void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { - int index = + // Set cellID + int cellIdColumn = dynamic_cast (*getData().getTableModel (getCollectionId())). findColumnIndex (CSMWorld::Columns::ColumnId_Cell); - command.addValue (index, mCell->text()); + command.addValue (cellIdColumn, mCell->text()); + + // Set RefNum + int refNumColumn = dynamic_cast ( + *getData().getTableModel (CSMWorld::UniversalId::Type_References)). + findColumnIndex (CSMWorld::Columns::ColumnId_RefNum); + + command.addValue (refNumColumn, getRefNumCount()); +} + +void CSVWorld::ReferenceCreator::pushCommand (std::auto_ptr command, + const std::string& id) +{ + // get the old count + std::string cellId = mCell->text().toUtf8().constData(); + + CSMWorld::IdTable& cellTable = dynamic_cast ( + *getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + int countColumn = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter); + + QModelIndex countIndex = cellTable.getModelIndex (cellId, countColumn); + + int count = cellTable.data (countIndex).toInt(); + + // command for incrementing counter + std::auto_ptr increment (new CSMWorld::ModifyCommand + (cellTable, countIndex, count+1)); + + getUndoStack().beginMacro (command->text()); + GenericCreator::pushCommand (command, id); + getUndoStack().push (increment.release()); + getUndoStack().endMacro(); +} + +int CSVWorld::ReferenceCreator::getRefNumCount() const +{ + std::string cellId = mCell->text().toUtf8().constData(); + + CSMWorld::IdTable& cellTable = dynamic_cast ( + *getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + int countColumn = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter); + + QModelIndex countIndex = cellTable.getModelIndex (cellId, countColumn); + + return cellTable.data (countIndex).toInt(); } CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, @@ -47,12 +94,9 @@ void CSVWorld::ReferenceCreator::reset() std::string CSVWorld::ReferenceCreator::getErrors() const { - std::string errors = GenericCreator::getErrors(); - - if (mCloneMode) - { - return errors; - } + // We are ignoring errors coming from GenericCreator here, because the ID of the new + // record is internal and requires neither user input nor verification. + std::string errors; std::string cell = mCell->text().toUtf8().constData(); @@ -79,15 +123,17 @@ void CSVWorld::ReferenceCreator::cellChanged() update(); } -void CSVWorld::ReferenceCreator::toggleWidgets(bool active) -{ - CSVWorld::GenericCreator::toggleWidgets(active); - mCell->setEnabled(active); -} - void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { + CSMWorld::IdTable& referenceTable = dynamic_cast ( + *getData().getTableModel (CSMWorld::UniversalId::Type_References)); + + int cellIdColumn = referenceTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + + mCell->setText ( + referenceTable.data (referenceTable.getModelIndex (originId, cellIdColumn)).toString()); + CSVWorld::GenericCreator::cloneMode(originId, type); cellChanged(); //otherwise ok button will remain disabled } diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index 12fb12dd90..002a62d879 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -20,16 +20,20 @@ namespace CSVWorld virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + virtual void pushCommand (std::auto_ptr command, + const std::string& id); + + int getRefNumCount() const; + public: ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - virtual void cloneMode(const std::string& originId, + virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); virtual void reset(); - virtual void toggleWidgets(bool active = true); virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index dc1525b05e..52bd47b54a 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -19,6 +19,8 @@ #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" +#include "../widget/scenetooltoggle.hpp" +#include "../widget/scenetoolrun.hpp" #include "tablebottombox.hpp" #include "creator.hpp" @@ -107,18 +109,22 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp CSVWidget::SceneToolMode *lightingTool = widget->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); -/* Add buttons specific to the type. For now no need for it. - * - switch (type) + CSVWidget::SceneToolToggle *sceneVisibilityTool = + widget->makeSceneVisibilitySelector (toolbar); + toolbar->addTool (sceneVisibilityTool); + + if (type==widget_Paged) { - case widget_Paged: - break; - - case widget_Unpaged: - break; + CSVWidget::SceneToolToggle *controlVisibilityTool = + dynamic_cast (*widget). + makeControlVisibilitySelector (toolbar); + toolbar->addTool (controlVisibilityTool); } -*/ + + CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); + toolbar->addTool (runTool); + return toolbar; } @@ -192,10 +198,12 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = NULL; CSVWidget::SceneToolbar* toolbar = NULL; - switch (mScene->getDropRequirements(CSVRender::WorldspaceWidget::getDropType(data))) + CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (data); + + switch (mScene->getDropRequirements (type)) { case CSVRender::WorldspaceWidget::canHandle: - mScene->handleDrop(data); + mScene->handleDrop (data, type); break; case CSVRender::WorldspaceWidget::needPaged: @@ -203,7 +211,7 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI toolbar = makeToolbar(pagedNewWidget, widget_Paged); makeConnections(pagedNewWidget); replaceToolbarAndWorldspace(pagedNewWidget, toolbar); - mScene->handleDrop(data); + mScene->handleDrop (data, type); break; case CSVRender::WorldspaceWidget::needUnpaged: diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 23bc76000d..c2d94ab5d3 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -6,14 +6,35 @@ #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/universalid.hpp" #include "../../model/world/tablemimedata.hpp" -CSVWorld::ScriptEdit::ScriptEdit (QWidget* parent, const CSMDoc::Document& document) : - QTextEdit (parent), - mDocument (document), - mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) + +CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) { + ++mEdit.mChangeLocked; +} + +CSVWorld::ScriptEdit::ChangeLock::~ChangeLock() +{ + --mEdit.mChangeLocked; +} + + +CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, + QWidget* parent) + : QPlainTextEdit (parent), + mDocument (document), + mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive), + mChangeLocked (0) +{ +// setAcceptRichText (false); + setLineWrapMode (QPlainTextEdit::NoWrap); + setTabStopWidth (4); + setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead + mAllowedTypes < (event->mimeData()); if (!mime) - QTextEdit::dragEnterEvent(event); + QPlainTextEdit::dragEnterEvent(event); else { setTextCursor (cursorForPosition (event->pos())); @@ -59,7 +95,7 @@ void CSVWorld::ScriptEdit::dragMoveEvent (QDragMoveEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) - QTextEdit::dragMoveEvent(event); + QPlainTextEdit::dragMoveEvent(event); else { setTextCursor (cursorForPosition (event->pos())); @@ -72,7 +108,7 @@ void CSVWorld::ScriptEdit::dropEvent (QDropEvent* event) const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped { - QTextEdit::dropEvent(event); + QPlainTextEdit::dropEvent(event); return; } @@ -103,3 +139,21 @@ bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const //I'm not quite sure when do we need to put quotes. To be safe we will use quotes for anything other than… return !(string.contains(mWhiteListQoutes)); } + +void CSVWorld::ScriptEdit::idListChanged() +{ + mHighlighter->invalidateIds(); + + if (!mUpdateTimer.isActive()) + mUpdateTimer.start (0); +} + +void CSVWorld::ScriptEdit::updateHighlighting() +{ + if (isChangeLocked()) + return; + + ChangeLock lock (*this); + + mHighlighter->rehighlight(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index b4627c2fee..c67385816c 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -1,11 +1,14 @@ #ifndef SCRIPTEDIT_H #define SCRIPTEDIT_H -#include +#include #include +#include #include "../../model/world/universalid.hpp" +#include "scripthighlighter.hpp" + class QWidget; class QRegExp; @@ -16,11 +19,42 @@ namespace CSMDoc namespace CSVWorld { - class ScriptEdit : public QTextEdit + class ScriptEdit : public QPlainTextEdit { Q_OBJECT + public: - ScriptEdit (QWidget* parent, const CSMDoc::Document& document); + + class ChangeLock + { + ScriptEdit& mEdit; + + ChangeLock (const ChangeLock&); + ChangeLock& operator= (const ChangeLock&); + + public: + + ChangeLock (ScriptEdit& edit); + ~ChangeLock(); + }; + + friend class ChangeLock; + + private: + + int mChangeLocked; + ScriptHighlighter *mHighlighter; + QTimer mUpdateTimer; + + public: + + ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, + QWidget* parent); + + /// Should changes to the data be ignored (i.e. not cause updated)? + /// + /// \note This mechanism is used to avoid infinite update recursions + bool isChangeLocked() const; private: QVector mAllowedTypes; @@ -34,6 +68,12 @@ namespace CSVWorld void dragMoveEvent (QDragMoveEvent* event); bool stringNeedsQuote(const std::string& id) const; + + private slots: + + void idListChanged(); + + void updateHighlighting(); }; } #endif // SCRIPTEDIT_H \ No newline at end of file diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index e06dab3727..36cebcb768 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -30,6 +30,16 @@ bool CSVWorld::ScriptHighlighter::parseName (const std::string& name, const Comp bool CSVWorld::ScriptHighlighter::parseKeyword (int keyword, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { + if (((mMode==Mode_Console || mMode==Mode_Dialogue) && + (keyword==Compiler::Scanner::K_begin || keyword==Compiler::Scanner::K_end || + keyword==Compiler::Scanner::K_short || keyword==Compiler::Scanner::K_long || + keyword==Compiler::Scanner::K_float)) + || (mMode==Mode_Console && (keyword==Compiler::Scanner::K_if || + keyword==Compiler::Scanner::K_endif || keyword==Compiler::Scanner::K_else || + keyword==Compiler::Scanner::K_elseif || keyword==Compiler::Scanner::K_while || + keyword==Compiler::Scanner::K_endwhile))) + return parseName (loc.mLiteral, loc, scanner); + highlight (loc, Type_Keyword); return true; } @@ -63,8 +73,10 @@ void CSVWorld::ScriptHighlighter::highlight (const Compiler::TokenLoc& loc, Type setFormat (index, length, mScheme[type]); } -CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, QTextDocument *parent) -: QSyntaxHighlighter (parent), Compiler::Parser (mErrorHandler, mContext), mContext (data) +CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, Mode mode, + QTextDocument *parent) +: QSyntaxHighlighter (parent), Compiler::Parser (mErrorHandler, mContext), mContext (data), + mMode (mode) { /// \todo replace this with user settings { diff --git a/apps/opencs/view/world/scripthighlighter.hpp b/apps/opencs/view/world/scripthighlighter.hpp index 495c2e6a3a..953f2f953a 100644 --- a/apps/opencs/view/world/scripthighlighter.hpp +++ b/apps/opencs/view/world/scripthighlighter.hpp @@ -28,12 +28,20 @@ namespace CSVWorld Type_Id }; + enum Mode + { + Mode_General, + Mode_Console, + Mode_Dialogue + }; + private: Compiler::NullErrorHandler mErrorHandler; Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; std::map mScheme; + Mode mMode; private: @@ -74,7 +82,7 @@ namespace CSVWorld public: - ScriptHighlighter (const CSMWorld::Data& data, QTextDocument *parent); + ScriptHighlighter (const CSMWorld::Data& data, Mode mode, QTextDocument *parent); virtual void highlightBlock (const QString& text); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index fa41151ca3..df3fd87be5 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -3,8 +3,6 @@ #include -#include - #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" @@ -12,28 +10,12 @@ #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" -#include "scripthighlighter.hpp" #include "scriptedit.hpp" -CSVWorld::ScriptSubView::ChangeLock::ChangeLock (ScriptSubView& view) : mView (view) -{ - ++mView.mChangeLocked; -} - -CSVWorld::ScriptSubView::ChangeLock::~ChangeLock() -{ - --mView.mChangeLocked; -} - CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mChangeLocked (0) +: SubView (id), mDocument (document), mColumn (-1) { - setWidget (mEditor = new ScriptEdit (this, mDocument)); - - mEditor->setAcceptRichText (false); - mEditor->setLineWrapMode (QTextEdit::NoWrap); - mEditor->setTabStopWidth (4); - mEditor->setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead + setWidget (mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this)); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); @@ -58,14 +40,6 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); - - connect (&document.getData(), SIGNAL (idListChanged()), this, SLOT (idListChanged())); - - mHighlighter = new ScriptHighlighter (document.getData(), mEditor->document()); - - connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); - - mUpdateTimer.setSingleShot (true); } void CSVWorld::ScriptSubView::setEditLock (bool locked) @@ -73,20 +47,12 @@ void CSVWorld::ScriptSubView::setEditLock (bool locked) mEditor->setReadOnly (locked); } -void CSVWorld::ScriptSubView::idListChanged() -{ - mHighlighter->invalidateIds(); - - if (!mUpdateTimer.isActive()) - mUpdateTimer.start (0); -} - void CSVWorld::ScriptSubView::textChanged() { - if (mChangeLocked) + if (mEditor->isChangeLocked()) return; - ChangeLock lock (*this); + ScriptEdit::ChangeLock lock (*mEditor); mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); @@ -94,10 +60,10 @@ void CSVWorld::ScriptSubView::textChanged() void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mChangeLocked) + if (mEditor->isChangeLocked()) return; - ChangeLock lock (*this); + ScriptEdit::ChangeLock lock (*mEditor); QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); @@ -118,12 +84,3 @@ void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, i deleteLater(); } -void CSVWorld::ScriptSubView::updateHighlighting() -{ - if (mChangeLocked) - return; - - ChangeLock lock (*this); - - mHighlighter->rehighlight(); -} \ No newline at end of file diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 7ceab70bae..77127d9bee 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -3,9 +3,6 @@ #include "../doc/subview.hpp" -#include - -class QTextEdit; class QModelIndex; namespace CSMDoc @@ -20,34 +17,16 @@ namespace CSMWorld namespace CSVWorld { - class ScriptHighlighter; + class ScriptEdit; class ScriptSubView : public CSVDoc::SubView { Q_OBJECT - QTextEdit *mEditor; + ScriptEdit *mEditor; CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; - int mChangeLocked; - ScriptHighlighter *mHighlighter; - QTimer mUpdateTimer; - - class ChangeLock - { - ScriptSubView& mView; - - ChangeLock (const ChangeLock&); - ChangeLock& operator= (const ChangeLock&); - - public: - - ChangeLock (ScriptSubView& view); - ~ChangeLock(); - }; - - friend class ChangeLock; public: @@ -57,17 +36,11 @@ namespace CSVWorld public slots: - void idListChanged(); - void textChanged(); void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); - - private slots: - - void updateHighlighting(); }; } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 200a26a852..f86aa34ffa 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -3,8 +3,6 @@ #include "../doc/subviewfactoryimp.hpp" -#include "../filter/filtercreator.hpp" - #include "tablesubview.hpp" #include "dialoguesubview.hpp" #include "scriptsubview.hpp" @@ -35,7 +33,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Factions, CSMWorld::UniversalId::Type_Races, CSMWorld::UniversalId::Type_Sounds, - CSMWorld::UniversalId::Type_Scripts, CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, @@ -91,11 +88,20 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) // Other stuff (combined record tables) manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); + manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); + + // More other stuff manager.add (CSMWorld::UniversalId::Type_Filters, new CSVDoc::SubViewFactoryWithCreator >); + CreatorFactory >); - manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); + manager.add (CSMWorld::UniversalId::Type_DebugProfiles, + new CSVDoc::SubViewFactoryWithCreator >); + + manager.add (CSMWorld::UniversalId::Type_Scripts, + new CSVDoc::SubViewFactoryWithCreator >); // Dialogue subviews static const CSMWorld::UniversalId::Type sTableTypes2[] = @@ -106,7 +112,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Global, CSMWorld::UniversalId::Type_Race, CSMWorld::UniversalId::Type_Class, - CSMWorld::UniversalId::Type_Filter, CSMWorld::UniversalId::Type_Sound, CSMWorld::UniversalId::Type_Faction, CSMWorld::UniversalId::Type_Enchantment, @@ -147,6 +152,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Journal, new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add (CSMWorld::UniversalId::Type_DebugProfile, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Filter, + new CSVDoc::SubViewFactoryWithCreator > (false)); + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 43a34e41da..6adf3e0c13 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -54,6 +54,35 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) /// \todo add menu items for select all and clear selection + { + // Request UniversalId editing from table columns. + + int currRow = rowAt( event->y() ), + currCol = columnAt( event->x() ); + + currRow = mProxyModel->mapToSource(mProxyModel->index( currRow, 0 )).row(); + + CSMWorld::ColumnBase::Display colDisplay = + static_cast( + mModel->headerData( + currCol, + Qt::Horizontal, + CSMWorld::ColumnBase::Role_Display ).toInt()); + + QString cellData = mModel->data(mModel->index( currRow, currCol )).toString(); + CSMWorld::UniversalId::Type colType = CSMWorld::TableMimeData::convertEnums( colDisplay ); + + if ( !cellData.isEmpty() + && colType != CSMWorld::UniversalId::Type_None ) + { + mEditCellAction->setText(tr("Edit '").append(cellData).append("'")); + + menu.addAction( mEditCellAction ); + + mEditCellId = CSMWorld::UniversalId( colType, cellData.toUtf8().constData() ); + } + } + if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { if (selectedRows.size()==1) @@ -179,7 +208,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, - mDocument.getUndoStack(), this); + mDocument, this); mDelegates.push_back (delegate); setItemDelegateForColumn (i, delegate); @@ -219,6 +248,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); + mEditCellAction = new QAction( tr("Edit Cell"), this ); + connect( mEditCellAction, SIGNAL(triggered()), this, SLOT(editCell()) ); + addAction( mEditCellAction ); + mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); addAction (mViewAction); @@ -364,6 +397,11 @@ void CSVWorld::Table::moveDownRecord() } } +void CSVWorld::Table::editCell() +{ + emit editRequest( mEditCellId, std::string() ); +} + void CSVWorld::Table::viewRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); @@ -448,12 +486,12 @@ void CSVWorld::Table::tableSizeUpdate() size = rows; } - tableSizeChanged (size, deleted, modified); + emit tableSizeChanged (size, deleted, modified); } void CSVWorld::Table::selectionSizeUpdate() { - selectionSizeChanged (selectionModel()->selectedRows().size()); + emit selectionSizeChanged (selectionModel()->selectedRows().size()); } void CSVWorld::Table::requestFocus (const std::string& id) @@ -467,6 +505,8 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { mProxyModel->setFilter (filter); + tableSizeUpdate(); + selectionSizeUpdate(); } void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 883834b602..a80a0b3620 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -8,6 +8,7 @@ #include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" +#include "../../model/world/universalid.hpp" #include "dragrecordtable.hpp" class QUndoStack; @@ -21,7 +22,6 @@ namespace CSMDoc namespace CSMWorld { class Data; - class UniversalId; class IdTableProxyModel; class IdTableBase; class CommandDispatcher; @@ -45,6 +45,7 @@ namespace CSVWorld QAction *mMoveUpAction; QAction *mMoveDownAction; QAction *mViewAction; + QAction *mEditCellAction; QAction *mPreviewAction; QAction *mExtendedDeleteAction; QAction *mExtendedRevertAction; @@ -53,6 +54,8 @@ namespace CSVWorld int mRecordStatusDisplay; CSMWorld::CommandDispatcher *mDispatcher; + CSMWorld::UniversalId mEditCellId; + private: void contextMenuEvent (QContextMenuEvent *event); @@ -93,6 +96,8 @@ namespace CSVWorld private slots: + void editCell(); + void editRecord(); void cloneRecord(); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 327fb1c0e4..e2c8d5c1e3 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -111,14 +111,21 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers { std::vector > > filterSource; + std::vector refIdColumns = mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(CSMWorld::UniversalId::Type_Referenceable)); + bool hasRefIdDisplay = !refIdColumns.empty(); + for (std::vector::iterator it(types.begin()); it != types.end(); ++it) { - std::pair > pair( //splited long line - std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); - - if(!pair.second.empty()) + CSMWorld::UniversalId::Type type = it->getType(); + std::vector col = mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(type)); + if(!col.empty()) { - filterSource.push_back(pair); + filterSource.push_back(std::make_pair(it->getId(), col)); + } + + if(hasRefIdDisplay && CSMWorld::TableMimeData::isReferencable(type)) + { + filterSource.push_back(std::make_pair(it->getId(), refIdColumns)); } } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index ca66087fad..f8847abfcd 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -17,6 +17,8 @@ #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" +#include "scriptedit.hpp" + CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model) : mModel (model) {} @@ -78,15 +80,15 @@ void CSVWorld::CommandDelegateFactoryCollection::add (CSMWorld::ColumnBase::Disp } CSVWorld::CommandDelegate *CSVWorld::CommandDelegateFactoryCollection::makeDelegate ( - CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, QObject *parent) const + CSMWorld::ColumnBase::Display display, CSMDoc::Document& document, QObject *parent) const { std::map::const_iterator iter = mFactories.find (display); if (iter!=mFactories.end()) - return iter->second->makeDelegate (undoStack, parent); + return iter->second->makeDelegate (document, parent); - return new CommandDelegate (undoStack, parent); + return new CommandDelegate (document, parent); } const CSVWorld::CommandDelegateFactoryCollection& CSVWorld::CommandDelegateFactoryCollection::get() @@ -100,7 +102,12 @@ const CSVWorld::CommandDelegateFactoryCollection& CSVWorld::CommandDelegateFacto QUndoStack& CSVWorld::CommandDelegate::getUndoStack() const { - return mUndoStack; + return mDocument.getUndoStack(); +} + +CSMDoc::Document& CSVWorld::CommandDelegate::getDocument() const +{ + return mDocument; } void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, @@ -112,11 +119,11 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM QVariant new_ = hack.getData(); if (model->data (index)!=new_) - mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, new_)); + getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, new_)); } -CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent) -: QStyledItemDelegate (parent), mUndoStack (undoStack), mEditLock (false) +CSVWorld::CommandDelegate::CommandDelegate (CSMDoc::Document& document, QObject *parent) +: QStyledItemDelegate (parent), mDocument (document), mEditLock (false) {} void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, @@ -162,8 +169,11 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return new QDoubleSpinBox(parent); case CSMWorld::ColumnBase::Display_LongString: - - return new QTextEdit(parent); + { + QPlainTextEdit *edit = new QPlainTextEdit(parent); + edit->setUndoRedoEnabled (false); + return edit; + } case CSMWorld::ColumnBase::Display_Boolean: @@ -173,6 +183,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_Skill: case CSMWorld::ColumnBase::Display_Script: case CSMWorld::ColumnBase::Display_Race: + case CSMWorld::ColumnBase::Display_Region: case CSMWorld::ColumnBase::Display_Class: case CSMWorld::ColumnBase::Display_Faction: case CSMWorld::ColumnBase::Display_Miscellaneous: @@ -183,9 +194,14 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_SoundRes: case CSMWorld::ColumnBase::Display_Texture: case CSMWorld::ColumnBase::Display_Video: + case CSMWorld::ColumnBase::Display_GlobalVariable: return new DropLineEdit(parent); + case CSMWorld::ColumnBase::Display_ScriptLines: + + return new ScriptEdit (mDocument, ScriptHighlighter::Mode_Console, parent); + default: return QStyledItemDelegate::createEditor (parent, option, index); diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index c95a249820..b4d972bf3f 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -51,7 +51,8 @@ namespace CSVWorld virtual ~CommandDelegateFactory(); - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const = 0; + virtual CommandDelegate *makeDelegate (CSMDoc::Document& document, QObject *parent) + const = 0; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; @@ -77,7 +78,7 @@ namespace CSVWorld /// /// This function must not be called more than once per value of \a display. - CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, + CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. /// @@ -110,19 +111,21 @@ namespace CSVWorld { Q_OBJECT - QUndoStack& mUndoStack; + CSMDoc::Document& mDocument; bool mEditLock; protected: QUndoStack& getUndoStack() const; + CSMDoc::Document& getDocument() const; + virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const; public: - CommandDelegate (QUndoStack& undoStack, QObject *parent); + CommandDelegate (CSMDoc::Document& document, QObject *parent); virtual void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const; diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index fc00f44919..c3c98b800c 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -47,8 +47,8 @@ void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QM } CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector >& values, - QUndoStack& undoStack, QObject *parent) -: EnumDelegate (values, undoStack, parent) + CSMDoc::Document& document, QObject *parent) +: EnumDelegate (values, document, parent) {} @@ -68,10 +68,10 @@ CSVWorld::VarTypeDelegateFactory::VarTypeDelegateFactory (ESM::VarType type0, add (type3); } -CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate (QUndoStack& undoStack, - QObject *parent) const +CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate ( + CSMDoc::Document& document, QObject *parent) const { - return new VarTypeDelegate (mValues, undoStack, parent); + return new VarTypeDelegate (mValues, document, parent); } void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type) diff --git a/apps/opencs/view/world/vartypedelegate.hpp b/apps/opencs/view/world/vartypedelegate.hpp index c8493f0291..c86b936f63 100644 --- a/apps/opencs/view/world/vartypedelegate.hpp +++ b/apps/opencs/view/world/vartypedelegate.hpp @@ -17,7 +17,7 @@ namespace CSVWorld public: VarTypeDelegate (const std::vector >& values, - QUndoStack& undoStack, QObject *parent); + CSMDoc::Document& document, QObject *parent); }; class VarTypeDelegateFactory : public CommandDelegateFactory @@ -30,7 +30,7 @@ namespace CSVWorld ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown, ESM::VarType type3 = ESM::VT_Unknown); - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + virtual CommandDelegate *makeDelegate (CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. void add (ESM::VarType type); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 23ba78dbad..2417091f85 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -2,8 +2,15 @@ set(GAME main.cpp engine.cpp + + ${CMAKE_SOURCE_DIR}/files/windows/openmw.rc ) -if(NOT WIN32) + +if (ANDROID) + set(GAME ${GAME} android_main.c) +endif() + +if(NOT WIN32 AND NOT ANDROID) set(GAME ${GAME} crashcatcher.cpp) endif() set(GAME_HEADER @@ -15,7 +22,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation terraingrid + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput @@ -25,15 +32,15 @@ add_openmw_dir (mwinput add_openmw_dir (mwgui textinput widgets race class birth review windowmanagerimp console dialogue windowbase statswindow messagebox journalwindow charactercreation - mapwindow windowpinnablebase tooltips scrollwindow bookwindow list + mapwindow windowpinnablebase tooltips scrollwindow bookwindow formatting inventorywindow container hud countdialog tradewindow settingswindow confirmationdialog alchemywindow referenceinterface spellwindow mainmenu quickkeysmenu itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog - enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons + enchantingdialog trainingwindow travelwindow exposedwindow cursor spellicons merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview - tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge mode videowidget backgroundimage itemwidget + tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog + recharge mode videowidget backgroundimage itemwidget screenfader ) add_openmw_dir (mwdialogue @@ -48,7 +55,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound + soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper ) add_openmw_dir (mwworld @@ -57,7 +64,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader omwloader actiontrap cellreflist projectilemanager cellref + contentloader esmloader actiontrap cellreflist projectilemanager cellref ) add_openmw_dir (mwclass @@ -69,7 +76,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects 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 + disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction ) add_openmw_dir (mwstate @@ -82,24 +89,37 @@ add_openmw_dir (mwbase ) # Main executable -set(BOOST_COMPONENTS system filesystem program_options thread wave) +if (ANDROID) + set(BOOST_COMPONENTS system filesystem program_options thread wave atomic) +else () + set(BOOST_COMPONENTS system filesystem program_options thread wave) +endif () + if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) endif(WIN32) find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -add_executable(openmw - ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} - ${OPENMW_FILES} - ${GAME} ${GAME_HEADER} - ${APPLE_BUNDLE_RESOURCES} -) +if (NOT ANDROID) + add_executable(openmw + ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} + ${OPENMW_FILES} + ${GAME} ${GAME_HEADER} + ${APPLE_BUNDLE_RESOURCES} + ) +else () + add_library(openmw + SHARED + ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} + ${OPENMW_FILES} + ${GAME} ${GAME_HEADER} + ) +endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. include_directories(${SOUND_INPUT_INCLUDES} ${BULLET_INCLUDE_DIRS}) -add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} @@ -117,6 +137,23 @@ target_link_libraries(openmw components ) +if (ANDROID) + target_link_libraries(openmw + ${OGRE_STATIC_PLUGINS} + EGL + android + log + dl + MyGUI.OgrePlatform + MyGUIEngineStatic + Plugin_StrangeButtonStatic + cpufeatures + BulletCollision + BulletDynamics + LinearMath + ) +endif (ANDROID) + if (USE_SYSTEM_TINYXML) target_link_libraries(openmw ${TINYXML_LIBRARIES}) endif() diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c new file mode 100644 index 0000000000..76da91c4f6 --- /dev/null +++ b/apps/openmw/android_main.c @@ -0,0 +1,43 @@ + +#include "../../SDL_internal.h" + +#ifdef __ANDROID__ +#include "SDL_main.h" + + +/******************************************************************************* + Functions called by JNI +*******************************************************************************/ +#include + +/* Called before to initialize JNI bindings */ + + + +extern void SDL_Android_Init(JNIEnv* env, jclass cls); + + +int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) +{ + + SDL_Android_Init(env, cls); + + SDL_SetMainReady(); + + +/* Run the application code! */ + + int status; + char *argv[2]; + argv[0] = SDL_strdup("openmw"); + argv[1] = NULL; + status = main(1, argv); + + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ + + return status; +} + +#endif /* __ANDROID__ */ + diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 75d2d7953a..b9d78540e0 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -37,6 +37,10 @@ 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 +#endif + static char argv0[PATH_MAX]; static char altstack[SIGSTKSZ]; @@ -66,7 +70,7 @@ static const struct { int code; const char *name; } sigill_codes[] = { - #ifndef __FreeBSD__ + #if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) { ILL_ILLOPC, "Illegal opcode" }, { ILL_ILLOPN, "Illegal operand" }, { ILL_ILLADR, "Illegal addressing mode" }, @@ -123,7 +127,6 @@ static int (*cc_user_info)(char*, char*); static void gdb_info(pid_t pid) { char respfile[64]; - char cmd_buf[128]; FILE *f; int fd; @@ -152,6 +155,7 @@ static void gdb_info(pid_t pid) fclose(f); /* Run gdb and print process info. */ + char cmd_buf[128]; snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); printf("Executing: %s\n", cmd_buf); fflush(stdout); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f8b4c98568..00ad90a026 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -87,6 +86,11 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // update input MWBase::Environment::get().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; + // sound if (mUseSound) MWBase::Environment::get().getSoundManager()->update(frametime); @@ -105,12 +109,12 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) { if (!paused) { - // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); - // local scripts executeLocalScripts(); + // global scripts + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } @@ -180,7 +184,9 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoder(NULL) , mActivationDistanceOverride(-1) , mGrab(true) - + , mScriptBlacklistUse (true) + , mExportFonts(false) + , mNewGame (false) { std::srand ( std::time(NULL) ); MWClass::registerClasses(); @@ -263,9 +269,10 @@ void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity) mVerboseScripts = scriptsVerbosity; } -void OMW::Engine::setSkipMenu (bool skipMenu) +void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; + mNewGame = newGame; } std::string OMW::Engine::loadSettings (Settings::Manager & settings) @@ -314,8 +321,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setStateManager ( new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); - Nif::NIFFile::CacheLock cachelock; - std::string renderSystem = settings.getString("render system", "Video"); if (renderSystem == "") { @@ -336,8 +341,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. - //addResourcesDirectory(mResDir); - addResourcesDirectory(mCfgMgr.getCachePath ().string()); addResourcesDirectory(mResDir / "mygui"); @@ -364,14 +367,14 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so - std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v1.xml").string(); + std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); MWGui::WindowManager* window = new MWGui::WindowManager( mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding); + mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap); mEnvironment.setWindowManager (window); // Create sound system @@ -406,7 +409,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mScriptContext->setExtensions (&mExtensions); mEnvironment.setScriptManager (new MWScript::ScriptManager (MWBase::Environment::get().getWorld()->getStore(), - mVerboseScripts, *mScriptContext, mWarningsMode)); + mVerboseScripts, *mScriptContext, mWarningsMode, + mScriptBlacklistUse ? mScriptBlacklist : std::vector())); // Create game mechanics system MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; @@ -470,7 +474,7 @@ void OMW::Engine::go() } else { - MWBase::Environment::get().getStateManager()->newGame (true); + MWBase::Environment::get().getStateManager()->newGame (!mNewGame); } // Start the main rendering loop @@ -505,7 +509,7 @@ void OMW::Engine::screenshot() int shotCount = 0; const std::string& screenshotPath = mCfgMgr.getUserDataPath().string(); - + std::string format = Settings::Manager::getString("screenshot format", "General"); // Find the first unused filename with a do-while std::ostringstream stream; do @@ -514,11 +518,11 @@ void OMW::Engine::screenshot() stream.str(""); stream.clear(); - stream << screenshotPath << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << ".png"; + stream << screenshotPath << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << format; } while (boost::filesystem::exists(stream.str())); - mOgre->screenshot(stream.str()); + mOgre->screenshot(stream.str(), format); } void OMW::Engine::setCompileAll (bool all) @@ -565,3 +569,18 @@ void OMW::Engine::setWarningsMode (int mode) { mWarningsMode = mode; } + +void OMW::Engine::setScriptBlacklist (const std::vector& list) +{ + mScriptBlacklist = list; +} + +void OMW::Engine::setScriptBlacklistUse (bool use) +{ + mScriptBlacklistUse = use; +} + +void OMW::Engine::enableFontExport(bool exportFonts) +{ + mExportFonts = exportFonts; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index e0f51d0dcb..0ee5a09c5b 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -7,6 +7,8 @@ #include #include #include +#include + #include "mwbase/environment.hpp" @@ -83,12 +85,19 @@ namespace OMW // Grab mouse? bool mGrab; + bool mExportFonts; + Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; Files::Collections mFileCollections; bool mFSStrict; Translation::Storage mTranslationDataStorage; + std::vector mScriptBlacklist; + bool mScriptBlacklistUse; + bool mNewGame; + + Nif::Cache mNifCache; // not implemented Engine (const Engine&); @@ -149,7 +158,11 @@ namespace OMW /// Disable or enable all sounds void setSoundUsage(bool soundUsage); - void setSkipMenu (bool skipMenu); + /// Skip main menu and go directly into the game + /// + /// \param newGame Start a new game instead off dumping the player into the game + /// (ignored if !skipMenu). + void setSkipMenu (bool skipMenu, bool newGame); void setGrabMouse(bool grab) { mGrab = grab; } @@ -181,6 +194,12 @@ namespace OMW void setWarningsMode (int mode); + void setScriptBlacklist (const std::vector& list); + + void setScriptBlacklistUse (bool use); + + void enableFontExport(bool exportFonts); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index adde408b9e..744780b258 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -62,7 +62,6 @@ void validate(boost::any &v, std::vector const &tokens, FallbackMap FallbackMap *map = boost::any_cast(&v); - std::map::iterator mapIt; for(std::vector::const_iterator it=tokens.begin(); it != tokens.end(); ++it) { int sep = it->find(","); @@ -76,7 +75,7 @@ void validate(boost::any &v, std::vector const &tokens, FallbackMap std::string key(it->substr(0,sep)); std::string value(it->substr(sep+1)); - if((mapIt = map->mMap.find(key)) == map->mMap.end()) + if(map->mMap.find(key) == map->mMap.end()) { map->mMap.insert(std::make_pair (key,value)); } @@ -105,7 +104,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("help", "print help message") ("version", "print version information and quit") ("data", bpo::value()->default_value(Files::PathContainer(), "data") - ->multitoken(), "set data directories (later directories have higher priority)") + ->multitoken()->composing(), "set data directories (later directories have higher priority)") ("data-local", bpo::value()->default_value(""), "set local data directory (highest priority)") @@ -144,9 +143,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat "\t1 - show warning but consider script as correctly compiled anyway\n" "\t2 - treat warnings as errors") + ("script-blacklist", bpo::value()->default_value(StringsVector(), "") + ->multitoken(), "ignore the specified script (if the use of the blacklist is enabled)") + + ("script-blacklist-use", bpo::value()->implicit_value(true) + ->default_value(true), "enable script blacklisting") + ("skip-menu", bpo::value()->implicit_value(true) ->default_value(false), "skip main menu on game startup") + ("new-game", bpo::value()->implicit_value(true) + ->default_value(false), "run new game sequence (ignored if skip-menu=0)") + ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") @@ -162,6 +170,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("no-grab", "Don't grab mouse cursor") + ("export-fonts", bpo::value()->implicit_value(true) + ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -184,6 +195,14 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (variables.count ("version")) { std::cout << "OpenMW version " << OPENMW_VERSION << std::endl; + + 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::endl; + } run = false; } @@ -239,17 +258,24 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // startup-settings engine.setCell(variables["start"].as()); - engine.setSkipMenu (variables["skip-menu"].as()); + engine.setSkipMenu (variables["skip-menu"].as(), variables["new-game"].as()); + if (!variables["skip-menu"].as() && variables["new-game"].as()) + std::cerr << "new-game used without skip-menu -> ignoring it" << std::endl; + + // scripts + engine.setCompileAll(variables["script-all"].as()); + engine.setScriptsVerbosity(variables["script-verbose"].as()); + engine.setScriptConsoleMode (variables["script-console"].as()); + engine.setStartupScript (variables["script-run"].as()); + engine.setWarningsMode (variables["script-warn"].as()); + engine.setScriptBlacklist (variables["script-blacklist"].as()); + engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); // other settings engine.setSoundUsage(!variables["no-sound"].as()); - engine.setScriptsVerbosity(variables["script-verbose"].as()); - engine.setCompileAll(variables["script-all"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); - engine.setScriptConsoleMode (variables["script-console"].as()); - engine.setStartupScript (variables["script-run"].as()); engine.setActivationDistanceOverride (variables["activate-dist"].as()); - engine.setWarningsMode (variables["script-warn"].as()); + engine.enableFontExport(variables["export-fonts"].as()); return true; } @@ -309,6 +335,8 @@ int main(int argc, char**argv) boost::filesystem::ofstream logfile; + std::auto_ptr engine; + int ret = 0; try { @@ -350,11 +378,11 @@ int main(int argc, char**argv) boost::filesystem::current_path(bundlePath); #endif - OMW::Engine engine(cfgMgr); + engine.reset(new OMW::Engine(cfgMgr)); - if (parseOptions(argc, argv, engine, cfgMgr)) + if (parseOptions(argc, argv, *engine, cfgMgr)) { - engine.go(); + engine->go(); } } catch (std::exception &e) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f2b71bd4cc..c755814883 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -128,6 +128,8 @@ namespace MWBase OffenseType type, int arg=0) = 0; virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0) = 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; /// Utility to check if taking this item is illegal and calling commitCrime if so virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count) = 0; /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so @@ -196,6 +198,9 @@ namespace MWBase /// @param bias Can be used to add an additional aggression bias towards the target, /// making it more likely for the function to return true. virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false) = 0; + + /// Resurrects the player if necessary + virtual void keepPlayerAlive() = 0; }; } diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp index ae146e0646..7bdeba132c 100644 --- a/apps/openmw/mwbase/scriptmanager.hpp +++ b/apps/openmw/mwbase/scriptmanager.hpp @@ -46,17 +46,11 @@ namespace MWBase ///< Compile all scripts /// \return count, success - virtual Compiler::Locals& getLocals (const std::string& name) = 0; + virtual const Compiler::Locals& getLocals (const std::string& name) = 0; ///< Return locals for script \a name. virtual MWScript::GlobalScripts& getGlobalScripts() = 0; - - virtual int getLocalIndex (const std::string& scriptId, const std::string& variable, - char type) = 0; - ///< Return index of the variable of the given name and type in the given script. Will - /// throw an exception, if there is no such script or variable or the type does not match. - - }; + }; } #endif diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 15739730ba..a02a463dde 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -101,6 +101,11 @@ namespace MWBase virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0; ///< Stop an actor speaking + virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const = 0; + ///< Check the currently playing say sound for this actor + /// and get an average loudness value (scale [0,1]) at the current time position. + /// If the actor is not saying anything, returns 0. + virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; ///< Play a 2D audio track, using a custom decoder diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 8b407c9bab..e8286bdd12 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -145,8 +145,6 @@ namespace MWBase virtual MWGui::SpellWindow* getSpellWindow() = 0; virtual MWGui::Console* getConsole() = 0; - virtual MyGUI::Gui* getGui() const = 0; - virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) = 0; /// Set value for the given ID. @@ -227,7 +225,6 @@ namespace MWBase virtual void showCrosshair(bool show) = 0; virtual bool getSubtitlesEnabled() = 0; - virtual void toggleHud() = 0; virtual bool toggleGui() = 0; virtual void disallowMouse() = 0; @@ -331,6 +328,15 @@ namespace MWBase virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; virtual void pinWindow (MWGui::GuiWindow window) = 0; + + /// Fade the screen in, over \a time seconds + virtual void fadeScreenIn(const float time) = 0; + /// Fade the screen out to black, over \a time seconds + virtual void fadeScreenOut(const float time) = 0; + /// Fade the screen to a specified percentage of black, over \a time seconds + virtual void fadeScreenTo(const int percent, const float time) = 0; + /// Darken the screen by \a factor (1.0 = no darkening). Works independently from screen fading. + virtual void setScreenFactor (float factor) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 49ac5bc159..2055aeeae5 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -17,11 +17,6 @@ namespace Ogre namespace OEngine { - namespace Render - { - class Fader; - } - namespace Physic { class PhysicEngine; @@ -113,9 +108,6 @@ namespace MWBase virtual void readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap) = 0; - virtual OEngine::Render::Fader* getFader() = 0; - ///< \todo remove this function. Rendering details should not be exposed. - virtual MWWorld::CellStore *getExterior (int x, int y) = 0; virtual MWWorld::CellStore *getInterior (const std::string& name) = 0; @@ -151,13 +143,16 @@ namespace MWBase virtual bool isCellQuasiExterior() const = 0; virtual Ogre::Vector2 getNorthVector (MWWorld::CellStore* cell) = 0; - ///< get north vector (OGRE coordinates) for given interior cell + ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; ///< get a list of teleport door markers for a given cell, to be displayed on the local map - virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) = 0; - ///< see MWRender::LocalMap::getInteriorMapPosition + virtual void worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) = 0; + ///< see MWRender::LocalMap::worldToInteriorMapPosition + + virtual Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y) = 0; + ///< see MWRender::LocalMap::interiorMapToWorldPosition virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior) = 0; ///< see MWRender::LocalMap::isPositionExplored @@ -271,8 +266,9 @@ namespace MWBase /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance) = 0; - virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; + virtual void adjustPosition (const MWWorld::Ptr& ptr, bool force) = 0; ///< Adjust position after load to be on ground. Must be called after model load. + /// @param force do this even if the ptr is flying virtual void fixPosition (const MWWorld::Ptr& actor) = 0; ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. @@ -398,11 +394,22 @@ namespace MWBase /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door) = 0; - /// open or close a non-teleport door as specified - virtual void activateDoor(const MWWorld::Ptr& door, bool open) = 0; + /// update movement state of a non-teleport door as specified + /// @param state see MWClass::setDoorState + /// @note throws an exception when invoked on a teleport door + virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0; virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object) = 0; ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual float getWindSpeed() = 0; virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; @@ -530,6 +537,9 @@ namespace MWBase /// @see MWWorld::WeatherManager::getStormDirection virtual Ogre::Vector3 getStormDirection() const = 0; + + /// Resets all actors in the current active cells to their original location within that cell. + virtual void resetActors() = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 043aadd359..bf02b4d05e 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -25,6 +25,11 @@ namespace MWClass { + std::string Activator::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -97,8 +102,7 @@ namespace MWClass std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 1e772ef4f2..3e4bc3de41 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -13,6 +13,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index d61ba038a3..c466cbc33e 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -21,6 +21,11 @@ namespace MWClass { + std::string Apparatus::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -127,8 +132,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 17b8b9254f..5cdda8f26a 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -13,6 +13,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index b29bf36b2e..97f9211d9a 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -25,6 +25,11 @@ namespace MWClass { + std::string Armor::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -152,15 +157,19 @@ namespace MWClass float iWeight = gmst.find (typeGmst)->getInt(); - if (iWeight * gmst.find ("fLightMaxMod")->getFloat()>= - ref->mBase->mData.mWeight) + float epsilon = 5e-4; + + if (ref->mBase->mData.mWeight == 0) + return ESM::Skill::Unarmored; + + if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fLightMaxMod")->getFloat() + epsilon) return ESM::Skill::LightArmor; - if (iWeight * gmst.find ("fMedMaxMod")->getFloat()>= - ref->mBase->mData.mWeight) + if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fMedMaxMod")->getFloat() + epsilon) return ESM::Skill::MediumArmor; - return ESM::Skill::HeavyArmor; + else + return ESM::Skill::HeavyArmor; } int Armor::getValue (const MWWorld::Ptr& ptr) const @@ -247,8 +256,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -372,7 +380,8 @@ namespace MWClass bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const { - return npcServices & ESM::NPC::Armor; + return (npcServices & ESM::NPC::Armor) + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Armor::getWeight(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index e9164f920b..8b7804c63f 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0adee57e31..51d47e7216 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Book::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Book::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -139,8 +144,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -198,7 +202,8 @@ namespace MWClass bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const { - return npcServices & ESM::NPC::Books; + return (npcServices & ESM::NPC::Books) + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Book::getWeight(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index b60ef41d64..49d21e8bf7 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index dc98e323e1..0098783505 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Clothing::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -193,8 +198,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -288,7 +292,8 @@ namespace MWClass bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const { - return npcServices & ESM::NPC::Clothing; + return (npcServices & ESM::NPC::Clothing) + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Clothing::getWeight(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 052928238e..99ce61ece3 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 53add42746..179070aedf 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -43,6 +43,11 @@ namespace namespace MWClass { + std::string Container::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) @@ -235,8 +240,7 @@ namespace MWClass text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 9fc013e45e..e926a71fe3 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -15,6 +15,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index feb6a27147..0d88a2e103 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -9,6 +9,7 @@ #include "../mwmechanics/movement.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/difficultyscaling.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -128,6 +129,8 @@ namespace MWClass data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); + data->mCreatureStats.setNeedRecalcDynamicStats(false); + // store ptr.getRefData().setCustomData(data.release()); @@ -147,9 +150,9 @@ namespace MWClass return ref->mBase->mId; } - void Creature::adjustPosition(const MWWorld::Ptr& ptr) const + void Creature::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { - MWBase::Environment::get().getWorld()->adjustPosition(ptr); + MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -208,6 +211,9 @@ namespace MWClass const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::CreatureStats &stats = getCreatureStats(ptr); + if (stats.getDrawState() != MWMechanics::DrawState_Weapon) + return; + // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::Ptr weapon; if (ptr.getClass().hasInventoryStore(ptr)) @@ -254,6 +260,7 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); + MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -275,12 +282,10 @@ namespace MWClass break; } - // I think this should be random, since attack1-3 animations don't have an attack strength like NPCs do - float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0); + float damage = min + (max - min) * stats.getAttackStrength(); if (!weapon.isEmpty()) { - const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -290,29 +295,11 @@ namespace MWClass attack = weapon.get()->mBase->mData.mThrust; if(attack) { - float weaponDamage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - weaponDamage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); - if(weaphashealth) - { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - int weaphealth = weapon.getClass().getItemHealth(weapon); - weaponDamage *= float(weaphealth) / weapmaxhealth; - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - // Reduce weapon charge by at least one, but cap at 0 - weaphealth -= std::min(std::max(1, - (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); - - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weapon.getCellRef().getCharge() == 0) - weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); - } - - damage += weaponDamage; + damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); + damage *= gmst.find("fDamageStrengthBase")->getFloat() + + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.find("fDamageStrengthMult")->getFloat() * 0.1); + MWMechanics::adjustWeaponDamage(damage, weapon); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); } // Apply "On hit" enchanted weapons @@ -330,6 +317,8 @@ namespace MWClass } } + MWMechanics::applyElementalShields(ptr, victim); + if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) damage = 0; @@ -345,13 +334,17 @@ namespace MWClass { // NOTE: 'object' and/or 'attacker' may be empty. - getCreatureStats(ptr).setAttacked(true); + if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker) - && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures - // (they have no movement or attacks anyway) - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. + if ((canWalk(ptr) || canFly(ptr) || canSwim(ptr)) // No retaliation for totally static creatures + // (they have no movement or attacks anyway) + && !attacker.isEmpty()) + { + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + } if(!successful) { @@ -365,7 +358,7 @@ namespace MWClass if(!object.isEmpty()) getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); - if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") + if(setOnPcHitMe && !attacker.isEmpty() && attacker.getRefData().getHandle() == "player") { const std::string &script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -376,23 +369,34 @@ namespace MWClass if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); + if (damage < 0.001f) + damage = 0; + if (damage > 0.f) { - // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); - float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + if (!attacker.isEmpty()) { - getCreatureStats(ptr).setKnockedDown(true); + // Check for knockdown + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); + float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() + * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + { + getCreatureStats(ptr).setKnockedDown(true); + } + else + getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? } - else - getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? + + damage = std::max(1.f, damage); if(ishealth) { + if (!attacker.isEmpty()) + 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); @@ -466,6 +470,8 @@ namespace MWClass if(getCreatureStats(ptr).isDead()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) + return boost::shared_ptr(new MWWorld::FailedAction("")); return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); } @@ -517,9 +523,7 @@ namespace MWClass bool Creature::hasToolTip (const MWWorld::Ptr& ptr) const { - /// \todo We don't want tooltips for Creatures in combat mode. - - return true; + return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); } float Creature::getSpeed(const MWWorld::Ptr &ptr) const @@ -543,11 +547,11 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled())) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + - mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); + mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); @@ -558,7 +562,7 @@ namespace MWClass float swimSpeed = walkSpeed; if(running) swimSpeed = runSpeed; - swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; + swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->getFloat(); moveSpeed = swimSpeed; @@ -618,8 +622,8 @@ namespace MWClass float Creature::getArmorRating (const MWWorld::Ptr& ptr) const { - /// \todo add Shield magic effect magnitude here, controlled by a GMST (Vanilla vs MCP behaviour) - return 0.f; + // Note this is currently unused. Creatures do not use armor mitigation. + return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); } float Creature::getCapacity (const MWWorld::Ptr& ptr) const @@ -634,9 +638,9 @@ namespace MWClass const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).mMagnitude; + weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).getMagnitude(); - weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).mMagnitude; + weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).getMagnitude(); if (weight<0) weight = 0; diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 6920a4b1d3..1820d4ea43 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -49,7 +49,9 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void adjustPosition(const MWWorld::Ptr& ptr) 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); diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 7843048044..b1e27c3450 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -27,6 +27,11 @@ namespace namespace MWClass { + std::string CreatureLevList::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const { return ""; @@ -78,6 +83,8 @@ namespace MWClass customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); customData.mSpawn = false; } + else + customData.mSpawn = false; } void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 6c51a31891..7016524eb9 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -11,6 +11,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + 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. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 677ad462e0..fa9db9e160 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -42,6 +42,11 @@ namespace namespace MWClass { + std::string Door::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -62,7 +67,7 @@ namespace MWClass const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); if (customData.mDoorState > 0) { - MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState); } } } @@ -251,9 +256,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); } info.text = text; @@ -320,6 +324,9 @@ namespace MWClass void Door::setDoorState (const MWWorld::Ptr &ptr, int state) const { + if (ptr.getCellRef().getTeleport()) + throw std::runtime_error("load doors can't be moved"); + ensureCustomData(ptr); DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); customData.mDoorState = state; diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 12b360aa8c..23e11d3368 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -16,6 +16,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 6830929239..fa03f23ff1 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -147,8 +147,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index 6ed9ab2e53..d31080bb2c 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -5,6 +5,11 @@ namespace MWClass { + std::string ItemLevList::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + std::string ItemLevList::getName (const MWWorld::Ptr& ptr) const { return ""; diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp index 0b71b072c8..2b507135fd 100644 --- a/apps/openmw/mwclass/itemlevlist.hpp +++ b/apps/openmw/mwclass/itemlevlist.hpp @@ -9,6 +9,9 @@ namespace MWClass { public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + 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. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 8a2c20f699..eabebc72a0 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -47,12 +47,20 @@ namespace namespace MWClass { + std::string Light::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Light::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); + MWWorld::LiveCellRef *ref = + ptr.get(); + // Insert even if model is empty, so that the light is added - renderingInterface.getObjects().insertModel(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, false, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const @@ -66,7 +74,7 @@ namespace MWClass if(!model.empty()) physics.addObject(ptr,ref->mBase->mData.mFlags & ESM::Light::Carry); - if (!ref->mBase->mSound.empty()) + if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); @@ -187,8 +195,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 5568e1727e..5840393368 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,6 +14,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index bc68551291..9df5870248 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Lockpick::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -141,8 +146,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 8f5a699d9d..d4bdf3fa69 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 1044fb01d4..1b4719c6e7 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -38,6 +38,11 @@ bool isGold (const MWWorld::Ptr& ptr) namespace MWClass { + std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -175,15 +180,14 @@ namespace MWClass std::string text; - if (!gold) + if (!gold && !ref->mBase->mData.mIsKey) { text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); } if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 16e9ca10b0..53a8e050bd 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e2cb8ba7af..5d5b8a6891 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -23,6 +23,7 @@ #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/autocalcspell.hpp" +#include "../mwmechanics/difficultyscaling.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -279,6 +280,7 @@ namespace MWClass gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); gmst.fDamageStrengthBase = store.find("fDamageStrengthBase"); gmst.fDamageStrengthMult = store.find("fDamageStrengthMult"); + gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult"); inited = true; } @@ -333,6 +335,8 @@ namespace MWClass data->mNpcStats.setLevel(ref->mBase->mNpdt52.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition); data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation); + + data->mNpcStats.setNeedRecalcDynamicStats(false); } else { @@ -347,6 +351,8 @@ namespace MWClass autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); + + data->mNpcStats.setNeedRecalcDynamicStats(true); } // race powers @@ -401,9 +407,9 @@ namespace MWClass return ref->mBase->mId; } - void Npc::adjustPosition(const MWWorld::Ptr& ptr) const + void Npc::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { - MWBase::Environment::get().getWorld()->adjustPosition(ptr); + MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -527,6 +533,7 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); + MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -535,7 +542,6 @@ namespace MWClass MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { - const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -548,27 +554,9 @@ namespace MWClass damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); damage *= gmst.fDamageStrengthBase->getFloat() + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1); - if(weaphashealth) - { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - int weaphealth = weapon.getClass().getItemHealth(weapon); - - damage *= float(weaphealth) / weapmaxhealth; - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - // Reduce weapon charge by at least one, but cap at 0 - weaphealth -= std::min(std::max(1, - (int)(damage * store.find("fWeaponDamageMult")->getFloat())), weaphealth); - - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weaphealth == 0) - weapon = *inv.unequipItem(weapon, ptr); - } } + MWMechanics::adjustWeaponDamage(damage, weapon); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); healthdmg = true; } else @@ -581,14 +569,13 @@ namespace MWClass damage = stats.getSkill(weapskill).getModified(); damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength()); - healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f) - || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); + healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + || otherstats.getKnockedDown(); if(stats.isWerewolf()) { healthdmg = true; // GLOB instead of GMST because it gets updated during a quest - const MWWorld::Store &glob = world->getStore().get(); - damage *= glob.find("WerewolfClawMult")->mValue.getFloat(); + damage *= world->getGlobalFloat("werewolfclawmult"); } if(healthdmg) damage *= store.find("fHandtoHandHealthPer")->getFloat(); @@ -604,15 +591,21 @@ namespace MWClass sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); } if(ptr.getRefData().getHandle() == "player") + { skillUsageSucceeded(ptr, weapskill, 0); - bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); - if(!detected) - { - damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); + + bool unaware = !seq.isInCombat() + && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); + if(unaware) + { + damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } } + if (othercls.getCreatureStats(victim).getKnockedDown()) damage *= store.find("fCombatKODamageMult")->getFloat(); @@ -630,6 +623,8 @@ namespace MWClass } } + MWMechanics::applyElementalShields(ptr, victim); + if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) damage = 0; @@ -647,14 +642,16 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. - // Attacking peaceful NPCs is a crime - if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && - !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) - MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); - bool wasDead = getCreatureStats(ptr).isDead(); - getCreatureStats(ptr).setAttacked(true); + // Note OnPcHitMe is not set for friendly hits. + bool setOnPcHitMe = true; + if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + { + getCreatureStats(ptr).setAttacked(true); + + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + } if(!successful) { @@ -668,7 +665,7 @@ namespace MWClass if(!object.isEmpty()) getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); - if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") + if(setOnPcHitMe && !attacker.isEmpty() && attacker.getRefData().getHandle() == "player") { const std::string &script = ptr.getClass().getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -679,7 +676,10 @@ namespace MWClass if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); - if(damage > 0.0f) + if (damage < 0.001f) + damage = 0; + + if(damage > 0.0f && !attacker.isEmpty()) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. @@ -707,7 +707,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage + if(damage > 0 && ishealth) { // Hit percentages: // cuirass = 30% @@ -727,9 +727,12 @@ namespace MWClass }; int hitslot = hitslots[(int)(::rand()/(RAND_MAX+1.0)*20.0)]; - float damagediff = damage; - damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); - damagediff -= damage; + float unmitigatedDamage = damage; + float x = damage / (damage + getArmorRating(ptr)); + damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x); + int damageDiff = unmitigatedDamage - damage; + if (damage < 1) + damage = 1; MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); @@ -737,13 +740,13 @@ namespace MWClass if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { int armorhealth = armor.getClass().getItemHealth(armor); - armorhealth -= std::min(std::max(1, (int)damagediff), + armorhealth -= std::min(std::max(1, damageDiff), armorhealth); armor.getCellRef().setCharge(armorhealth); // Armor broken? unequip it if (armorhealth == 0) - inv.unequipItem(armor, ptr); + armor = *inv.unequipItem(armor, ptr); if (ptr.getRefData().getHandle() == "player") skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); @@ -768,6 +771,9 @@ namespace MWClass if(ishealth) { + if (!attacker.isEmpty()) + damage = scaleDamage(damage, attacker, ptr); + if(damage > 0.0f) sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; @@ -848,6 +854,7 @@ namespace MWClass if(ptr.getRefData().getHandle() == "player") return boost::shared_ptr(new MWWorld::ActionTalk(actor)); + // Werewolfs can't activate NPCs if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -858,15 +865,18 @@ namespace MWClass return action; } + if(getCreatureStats(ptr).isDead()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); - if(ptr.getClass().getCreatureStats(ptr).isHostile()) + if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); - if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) + if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) + || ptr.getClass().getCreatureStats(ptr).getKnockedDown()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing - + // Can't talk to werewolfs + if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf()) + return boost::shared_ptr (new MWWorld::FailedAction("")); return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); - } MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) @@ -915,17 +925,15 @@ namespace MWClass float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); - if(npcdata->mNpcStats.isWerewolf()) - runSpeed *= gmst.fWereWolfRunMult->getFloat(); float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + else if(mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled()) { float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + - mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); + mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); @@ -936,7 +944,7 @@ namespace MWClass float swimSpeed = walkSpeed; if(running) swimSpeed = runSpeed; - swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; + swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* gmst.fSwimRunAthleticsMult->getFloat(); moveSpeed = swimSpeed; @@ -948,6 +956,9 @@ namespace MWClass if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) moveSpeed *= 0.75f; + if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) + moveSpeed *= gmst.fWereWolfRunMult->getFloat(); + return moveSpeed; } @@ -971,7 +982,7 @@ namespace MWClass float x = gmst.fJumpAcrobaticsBase->getFloat() + std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->getFloat()); x += 3.0f * b * gmst.fJumpAcroMultiplier->getFloat(); - x += mageffects.get(ESM::MagicEffect::Jump).mMagnitude * 64; + x += mageffects.get(ESM::MagicEffect::Jump).getMagnitude() * 64; x *= encumbranceTerm; if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) @@ -994,7 +1005,7 @@ namespace MWClass { const float acrobaticsSkill = ptr.getClass().getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); - const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude; + const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); @@ -1057,9 +1068,7 @@ namespace MWClass bool Npc::hasToolTip (const MWWorld::Ptr& ptr) const { - /// \todo We don't want tooltips for NPCs in combat mode. - - return true; + return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); } MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const @@ -1100,8 +1109,8 @@ namespace MWClass if(!stats.isWerewolf()) { weight = getContainerStore(ptr).getWeight(); - weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).mMagnitude; - weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).mMagnitude; + weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).getMagnitude(); + weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).getMagnitude(); if(weight < 0.0f) weight = 0.0f; } @@ -1116,10 +1125,13 @@ namespace MWClass return cast.cast(id); } - void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const + void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const { MWMechanics::NpcStats& stats = getNpcStats (ptr); + if (stats.isWerewolf()) + return; + MWWorld::LiveCellRef *ref = ptr.get(); const ESM::Class *class_ = @@ -1127,7 +1139,7 @@ namespace MWClass ref->mBase->mClass ); - stats.useSkill (skill, *class_, usageType); + stats.useSkill (skill, *class_, usageType, extraFactor); } float Npc::getArmorRating (const MWWorld::Ptr& ptr) const @@ -1166,7 +1178,7 @@ namespace MWClass } } - float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).mMagnitude; + float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet] @@ -1178,13 +1190,6 @@ namespace MWClass + shield; } - - void Npc::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const - { - y = 0; - x = 0; - } - void Npc::adjustScale(const MWWorld::Ptr &ptr, float &scale) const { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d6b1f8c268..fd16e6f083 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -40,6 +40,7 @@ namespace MWClass const ESM::GameSetting *iKnockDownOddsBase; const ESM::GameSetting *fDamageStrengthBase; const ESM::GameSetting *fDamageStrengthMult; + const ESM::GameSetting *fCombatArmorMinMult; }; static const GMST& getGmst(); @@ -54,7 +55,9 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void adjustPosition(const MWWorld::Ptr& ptr) 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); @@ -133,11 +136,9 @@ namespace MWClass virtual void adjustScale (const MWWorld::Ptr &ptr, float &scale) const; - virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const; + virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const; ///< Inform actor \a ptr that a skill use has succeeded. - virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; - virtual bool isEssential (const MWWorld::Ptr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 440121d35b..2da213c8d6 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -24,6 +24,11 @@ namespace MWClass { + std::string Potion::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -140,19 +145,15 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) { - it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) - || (i == 1 && alchemySkill >= fWortChanceValue*2) - || (i == 2 && alchemySkill >= fWortChanceValue*3) - || (i == 3 && alchemySkill >= fWortChanceValue*4)); - + it->mKnown = (i <= 1 && alchemySkill >= fWortChanceValue) + || (i <= 3 && alchemySkill >= fWortChanceValue*2); ++i; } info.isPotion = true; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 0f0578ca02..4c407d161c 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index ed8625eec0..82f4879864 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Probe::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -140,8 +145,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index a959e6c42e..047cb8ed4f 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index d7a0805349..4b18aced0d 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -21,6 +21,11 @@ namespace MWClass { + std::string Repair::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -144,8 +149,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 28ca5ad4c0..17730d6ecf 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 8768bde063..c241935ab2 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -12,6 +12,11 @@ namespace MWClass { + std::string Static::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index e36b3d1426..2ac2e86825 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 66affa599e..d2f88efef3 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -351,8 +351,7 @@ namespace MWClass info.remainingEnchantCharge = ptr.getCellRef().getEnchantmentCharge(); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -379,6 +378,7 @@ namespace MWClass newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; + newItem.mData.mFlags |= ESM::Weapon::Magical; const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); return record->mId; } @@ -435,7 +435,8 @@ namespace MWClass bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const { - return npcServices & ESM::NPC::Weapon; + return (npcServices & ESM::NPC::Weapon) + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Weapon::getWeight(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 8d9dc670f2..892669678a 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -266,7 +266,7 @@ namespace MWDialogue } catch (const std::exception& error) { - std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); + std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; } } } @@ -290,6 +290,9 @@ namespace MWDialogue std::string title; if (dialogue.mType==ESM::Dialogue::Persuasion) { + // Determine GMST from dialogue topic. GMSTs are: + // sAdmireSuccess, sAdmireFail, sIntimidateSuccess, sIntimidateFail, + // sTauntSuccess, sTauntFail, sBribeSuccess, sBribeFail std::string modifiedTopic = "s" + topic; modifiedTopic.erase (std::remove (modifiedTopic.begin(), modifiedTopic.end(), ' '), modifiedTopic.end()); @@ -505,9 +508,10 @@ namespace MWDialogue void DialogueManager::askQuestion (const std::string& question, int choice) { + mIsInChoice = true; + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->addChoice(question, choice); - mIsInChoice = true; } void DialogueManager::goodbye() @@ -725,10 +729,10 @@ namespace MWDialogue { std::vector result; MyGUI::UString utext(text); - size_t pos_begin, pos_end, iteration_pos = 0; + size_t pos_end, iteration_pos = 0; for(;;) { - pos_begin = utext.find('@', iteration_pos); + size_t pos_begin = utext.find('@', iteration_pos); if (pos_begin != std::string::npos) pos_end = utext.find('#', pos_begin); @@ -754,12 +758,12 @@ namespace MWDialogue size_t RemovePseudoAsterisks(std::string& phrase) { size_t pseudoAsterisksCount = 0; - const char specialPseudoAsteriskCharacter = 127; if( !phrase.empty() ) { std::string::reverse_iterator rit = phrase.rbegin(); + const char specialPseudoAsteriskCharacter = 127; while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) { pseudoAsterisksCount++; diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 08cdb1d003..2888a6e08b 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,11 +1,14 @@ #include "filter.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -76,8 +79,18 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const } else if (info.mData.mRank != -1) { - // if there is a rank condition, but the NPC is not in a faction, always fail - return false; + if (isCreature) + return false; + + // Rank requirement, but no faction given. Use the actor's faction, if there is one. + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats (mActor); + + if (!stats.getFactionRanks().size()) + return false; + + // check rank + if (stats.getFactionRanks().begin()->second < info.mData.mRank) + return false; } // Gender @@ -187,33 +200,28 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c if (scriptName.empty()) return false; // no script - const ESM::Script *script = - MWBase::Environment::get().getWorld()->getStore().get().find (scriptName); + std::string name = Misc::StringUtils::lowerCase (select.getName()); - std::string name = select.getName(); + const Compiler::Locals& localDefs = + MWBase::Environment::get().getScriptManager()->getLocals (scriptName); - int i = 0; + char type = localDefs.getType (name); - for (; i (script->mVarNames.size()); ++i) - if (Misc::StringUtils::ciEqual(script->mVarNames[i], name)) - break; + if (type==' ') + return false; // script does not have a variable of this name. - if (i>=static_cast (script->mVarNames.size())) - return false; // script does not have a variable of this name + int index = localDefs.getIndex (name); const MWScript::Locals& locals = mActor.getRefData().getLocals(); - if (imData.mNumShorts) - return select.selectCompare (static_cast (locals.mShorts[i])); + switch (type) + { + case 's': return select.selectCompare (static_cast (locals.mShorts[index])); + case 'l': return select.selectCompare (locals.mLongs[index]); + case 'f': return select.selectCompare (locals.mFloats[index]); + } - i -= script->mData.mNumShorts; - - if (imData.mNumLongs) - return select.selectCompare (locals.mLongs[i]); - - i -= script->mData.mNumLongs; - - return select.selectCompare (locals.mFloats.at (i)); + throw std::logic_error ("unknown local variable type in dialogue filter"); } case SelectWrapper::Function_PcHealthPercent: @@ -241,7 +249,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() / mActor.getClass().getCreatureStats (mActor).getHealth().getModified(); - return select.selectCompare (ratio); + return select.selectCompare (static_cast(ratio*100)); } default: @@ -453,20 +461,10 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co // This actor has no attached script, so there is no local variable return true; - const ESM::Script *script = - MWBase::Environment::get().getWorld()->getStore().get().find (scriptName); + const Compiler::Locals& localDefs = + MWBase::Environment::get().getScriptManager()->getLocals (scriptName); - std::string name = select.getName(); - - int i = 0; - for (; i < static_cast (script->mVarNames.size()); ++i) - if (Misc::StringUtils::ciEqual(script->mVarNames[i], name)) - break; - - if (i >= static_cast (script->mVarNames.size())) - return true; // script does not have a variable of this name - - return false; + return localDefs.getIndex (Misc::StringUtils::lowerCase (select.getName()))==-1; } case SelectWrapper::Function_SameGender: @@ -494,7 +492,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_PcCorprus: return player.getClass().getCreatureStats (player). - getMagicEffects().get (ESM::MagicEffect::Corprus).mMagnitude!=0; + getMagicEffects().get (ESM::MagicEffect::Corprus).getMagnitude()!=0; case SelectWrapper::Function_PcExpelled: { @@ -510,7 +508,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_PcVampire: return player.getClass().getCreatureStats(player).getMagicEffects(). - get(ESM::MagicEffect::Vampirism).mMagnitude > 0; + get(ESM::MagicEffect::Vampirism).getMagnitude() > 0; case SelectWrapper::Function_TalkedToPc: diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 0b3e3c1684..0fa9121b7d 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -8,6 +8,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/magiceffects.hpp" + #include "../mwworld/class.hpp" #include "inventoryitemmodel.hpp" @@ -201,22 +203,26 @@ namespace MWGui ingredient->setUserString("ToolTipType", "ItemPtr"); ingredient->setUserData(item); - MyGUI::TextBox* text = ingredient->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); - text->setTextAlign(MyGUI::Align::Right); - text->setNeedMouseFocus(false); - text->setTextShadow(true); - text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(ItemView::getCountString(ingredient->getUserData()->getRefData().getCount())); + ingredient->setCount(ingredient->getUserData()->getRefData().getCount()); } mItemView->update(); - std::vector effects; - ESM::EffectList list; - list.mList = effects; - for (MWMechanics::Alchemy::TEffectsIterator it = mAlchemy.beginEffects (); it != mAlchemy.endEffects (); ++it) + std::set effectIds = mAlchemy.listEffects(); + Widgets::SpellEffectList list; + for (std::set::iterator it = effectIds.begin(); it != effectIds.end(); ++it) { - list.mList.push_back(*it); + Widgets::SpellEffectParams params; + params.mEffectID = it->mId; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(it->mId); + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + params.mSkill = it->mArg; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + params.mAttribute = it->mArg; + params.mIsConstant = true; + params.mNoTarget = true; + + list.push_back(params); } while (mEffectsBox->getChildCount()) @@ -226,8 +232,7 @@ namespace MWGui Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget ("MW_StatName", coord, MyGUI::Align::Left | MyGUI::Align::Top); - Widgets::SpellEffectList _list = Widgets::MWEffectList::effectListFromESM(&list); - effectsWidget->setEffectList(_list); + effectsWidget->setEffectList(list); std::vector effectItems; effectsWidget->createEffectWidgets(effectItems, mEffectsBox, coord, false, 0); diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 7f58309ba0..a7f90c00ba 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -182,9 +184,7 @@ namespace MWGui const ESM::BirthSign *birth = store.get().find(mCurrentBirthId); - std::string texturePath = std::string("textures\\") + birth->mTexture; - Widgets::fixTexturePath(texturePath); - mBirthImage->setImageTexture(texturePath); + mBirthImage->setImageTexture(Misc::ResourceHelpers::correctTexturePath(birth->mTexture)); std::vector abilities, powers, spells; diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index 20a64c78ce..257dc6fefe 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -35,6 +35,11 @@ namespace MWGui */ EventHandle_Void eventBack; + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + protected: void onSelectBirth(MyGUI::ListBox* _sender, size_t _index); diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 659ba714c6..57cb3e7a9c 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -386,8 +386,6 @@ struct TypesetBookImpl::Typesetter : BookTypesetter int sectionHeightLeft = sectionHeight; while (sectionHeightLeft > mPageHeight) { - spaceLeft = mPageHeight - (curPageStop - curPageStart); - // Adjust to the top of the first line that does not fit on the current page anymore int splitPos = curPageStop; for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 32a5255c9a..92e8767082 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -70,11 +70,6 @@ namespace MWGui void BookWindow::clearPages() { - for (std::vector::iterator it=mPages.begin(); - it!=mPages.end(); ++it) - { - MyGUI::Gui::getInstance().destroyWidget(*it); - } mPages.clear(); } @@ -89,25 +84,9 @@ namespace MWGui MWWorld::LiveCellRef *ref = mBook.get(); - BookTextParser parser; - std::vector results = parser.split(ref->mBase->mText, mLeftPage->getSize().width, mLeftPage->getSize().height); - - int i=0; - for (std::vector::iterator it=results.begin(); - it!=results.end(); ++it) - { - MyGUI::Widget* parent; - if (i%2 == 0) - parent = mLeftPage; - else - parent = mRightPage; - - MyGUI::Widget* pageWidget = parent->createWidgetReal("", MyGUI::FloatCoord(0.0,0.0,1.0,1.0), MyGUI::Align::Default, "BookPage" + boost::lexical_cast(i)); - pageWidget->setNeedMouseFocus(false); - parser.parsePage(*it, pageWidget, mLeftPage->getSize().width); - mPages.push_back(pageWidget); - ++i; - } + Formatting::BookFormatter formatter; + mPages = formatter.markupToWidget(mLeftPage, ref->mBase->mText); + formatter.markupToWidget(mRightPage, ref->mBase->mText); updatePages(); @@ -164,19 +143,6 @@ namespace MWGui mLeftPageNumber->setCaption( boost::lexical_cast(mCurrentPage*2 + 1) ); mRightPageNumber->setCaption( boost::lexical_cast(mCurrentPage*2 + 2) ); - unsigned int i=0; - for (std::vector::iterator it = mPages.begin(); - it != mPages.end(); ++it) - { - if (mCurrentPage*2 == i || mCurrentPage*2+1 == i) - (*it)->setVisible(true); - else - { - (*it)->setVisible(false); - } - ++i; - } - //If it is the last page, hide the button "Next Page" if ( (mCurrentPage+1)*2 == mPages.size() || (mCurrentPage+1)*2 == mPages.size() + 1) @@ -191,9 +157,30 @@ namespace MWGui } else { mPrevPageButton->setVisible(true); } + + if (mPages.empty()) + return; + + MyGUI::Widget * paper; + + paper = mLeftPage->getChildAt(0); + paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage*2].first, + paper->getWidth(), mPages[mCurrentPage*2].second); + + paper = mRightPage->getChildAt(0); + if ((mCurrentPage+1)*2 <= mPages.size()) + { + paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage*2+1].first, + paper->getWidth(), mPages[mCurrentPage*2+1].second); + paper->setVisible(true); + } + else + { + paper->setVisible(false); + } } - void BookWindow::adjustButton (MWGui::ImageButton* button) + void BookWindow::adjustButton (Gui::ImageButton* button) { MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(); button->setSize(button->getRequestedSize()); diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index a944f56e25..ea3057a6f5 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -5,7 +5,7 @@ #include "../mwworld/ptr.hpp" -#include "imagebutton.hpp" +#include namespace MWGui { @@ -31,20 +31,24 @@ namespace MWGui void updatePages(); void clearPages(); - void adjustButton(MWGui::ImageButton* button); + void adjustButton(Gui::ImageButton* button); private: - MWGui::ImageButton* mCloseButton; - MWGui::ImageButton* mTakeButton; - MWGui::ImageButton* mNextPageButton; - MWGui::ImageButton* mPrevPageButton; + typedef std::pair Page; + typedef std::vector Pages; + + Gui::ImageButton* mCloseButton; + Gui::ImageButton* mTakeButton; + Gui::ImageButton* mNextPageButton; + Gui::ImageButton* mPrevPageButton; + MyGUI::TextBox* mLeftPageNumber; MyGUI::TextBox* mRightPageNumber; MyGUI::Widget* mLeftPage; MyGUI::Widget* mRightPage; unsigned int mCurrentPage; // 0 is first page - std::vector mPages; + Pages mPages; MWWorld::Ptr mBook; diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 9f6306830b..4e45f1a7c5 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -17,9 +17,6 @@ namespace MWGui GenerateClassResultDialog::GenerateClassResultDialog() : WindowModal("openmw_chargen_generate_class_result.layout") { - // Centre dialog - center(); - setText("ReflectT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sMessageQuestionAnswer1", "")); getWidget(mClassImage, "ClassImage"); @@ -34,6 +31,8 @@ namespace MWGui getWidget(okButton, "OKButton"); okButton->setCaptionWithReplacing("#{sMessageQuestionAnswer2}"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); + + center(); } std::string GenerateClassResultDialog::getClassId() const @@ -46,6 +45,8 @@ namespace MWGui mCurrentClassId = classId; mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds"); mClassName->setCaption(MWBase::Environment::get().getWorld()->getStore().get().find(mCurrentClassId)->mName); + + center(); } // widget controls @@ -276,7 +277,6 @@ namespace MWGui InfoBoxDialog::InfoBoxDialog() : WindowModal("openmw_infobox.layout") - , mCurrentButton(-1) { getWidget(mTextBox, "TextBox"); getWidget(mText, "Text"); @@ -305,7 +305,6 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(*it); } this->mButtons.clear(); - mCurrentButton = -1; // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget MyGUI::Button* button; @@ -335,11 +334,6 @@ namespace MWGui center(); } - int InfoBoxDialog::getChosenButton() const - { - return mCurrentButton; - } - void InfoBoxDialog::onButtonClicked(MyGUI::Widget* _sender) { std::vector::const_iterator end = mButtons.end(); @@ -348,7 +342,6 @@ namespace MWGui { if (*it == _sender) { - mCurrentButton = i; eventButtonSelected(i); return; } @@ -670,8 +663,6 @@ namespace MWGui // Centre dialog center(); - setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationMenu1", "")); - getWidget(mSpecialization0, "Specialization0"); getWidget(mSpecialization1, "Specialization1"); getWidget(mSpecialization2, "Specialization2"); @@ -693,7 +684,6 @@ namespace MWGui MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); - cancelButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked); } @@ -736,8 +726,6 @@ namespace MWGui // Centre dialog center(); - setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sAttributesMenu1", "")); - for (int i = 0; i < 8; ++i) { Widgets::MWAttributePtr attribute; @@ -751,7 +739,6 @@ namespace MWGui MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); - cancelButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked); } @@ -787,11 +774,6 @@ namespace MWGui // Centre dialog center(); - setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillsMenu1", "")); - setText("CombatLabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationCombat", "")); - setText("MagicLabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationMagic", "")); - setText("StealthLabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationStealth", "")); - for(int i = 0; i < 9; i++) { char theIndex = '0'+i; @@ -848,7 +830,6 @@ namespace MWGui MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); - cancelButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked); } diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index 5c23c834d7..40056ca5e9 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -24,7 +24,6 @@ namespace MWGui void setButtons(ButtonList &buttons); virtual void open(); - int getChosenButton() const; // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; @@ -41,7 +40,6 @@ namespace MWGui void fitToText(MyGUI::TextBox* widget); void layoutVertically(MyGUI::Widget* widget, int margin); - int mCurrentButton; MyGUI::Widget* mTextBox; MyGUI::TextBox* mText; MyGUI::Widget* mButtonBar; @@ -79,6 +77,11 @@ namespace MWGui */ EventHandle_Void eventBack; + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + protected: void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); @@ -109,6 +112,11 @@ namespace MWGui */ EventHandle_Void eventBack; + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + protected: void onSelectClass(MyGUI::ListBox* _sender, size_t _index); void onAccept(MyGUI::ListBox* _sender, size_t _index); @@ -238,6 +246,11 @@ namespace MWGui std::string getTextInput() const { return mTextEdit->getCaption(); } void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + protected: void onOkClicked(MyGUI::Widget* _sender); @@ -268,6 +281,11 @@ namespace MWGui */ EventHandle_Void eventBack; + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + protected: void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 8d199e7275..2dd130b0da 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -50,6 +50,13 @@ void CompanionWindow::onItemSelected(int index) const ItemStack& item = mSortModel->getItem(index); + // We can't take conjured items from a companion NPC + if (item.mFlags & ItemStack::Flag_Bound) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } + MWWorld::Ptr object = item.mBase; int count = item.mCount; bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); @@ -141,10 +148,11 @@ void CompanionWindow::onMessageBoxButtonClicked(int button) if (button == 0) { mPtr.getRefData().getLocals().setVarByInt(mPtr.getClass().getScript(mPtr), - "minimumProfit", mPtr.getClass().getNpcStats(mPtr).getProfit()); + "minimumprofit", mPtr.getClass().getNpcStats(mPtr).getProfit()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); - MWBase::Environment::get().getDialogueManager()->startDialogue (mPtr); + // Important for Calvus' contract script to work properly + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); } } @@ -157,6 +165,8 @@ void CompanionWindow::resetReference() { ReferenceInterface::resetReference(); mItemView->setModel(NULL); + mModel = NULL; + mSortModel = NULL; } diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 57c88bfa2e..a4e10749a2 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -13,12 +13,15 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked); } - void ConfirmationDialog::open(const std::string& message) + void ConfirmationDialog::open(const std::string& message, const std::string& confirmMessage, const std::string& cancelMessage) { setVisible(true); mMessage->setCaptionWithReplacing(message); + mCancelButton->setCaptionWithReplacing(cancelMessage); + mOkButton->setCaptionWithReplacing(confirmMessage); + int height = mMessage->getTextSize().height + 72; mMainWidget->setSize(mMainWidget->getWidth(), height); diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index c50873c74c..7ff16b10ad 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -9,7 +9,7 @@ namespace MWGui { public: ConfirmationDialog(); - void open(const std::string& message); + void open(const std::string& message, const std::string& confirmMessage="#{sOk}", const std::string& cancelMessage="#{sCancel}"); virtual void exit(); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 9f67524ae2..5a7193d650 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -140,6 +140,7 @@ namespace MWGui void Console::close() { // Apparently, hidden widgets can retain key focus + // Remove for MyGUI 3.2.2 MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); } @@ -154,11 +155,6 @@ namespace MWGui mCommandLine->setFontName(fntName); } - void Console::clearHistory() - { - mHistory->setCaption(""); - } - void Console::print(const std::string &msg) { mHistory->addText(msg); diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 90da740c20..09a05be487 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -48,8 +48,6 @@ namespace MWGui void onResChange(int width, int height); - void clearHistory(); - // Print a message to the console. Messages may contain color // code, eg. "#FFFFFF this is white". void print(const std::string &msg); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 8da3def5fa..7aac65bd3f 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -28,6 +28,16 @@ namespace MWGui { + DragAndDrop::DragAndDrop() + : mDraggedWidget(NULL) + , mDraggedCount(0) + , mSourceModel(NULL) + , mSourceView(NULL) + , mSourceSortModel(NULL) + , mIsOnDragAndDrop(NULL) + { + } + void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) { mItem = sourceModel->getItem(index); @@ -36,7 +46,6 @@ namespace MWGui mSourceView = sourceView; mSourceSortModel = sortModel; mIsOnDragAndDrop = true; - mDragAndDropWidget->setVisible(true); // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, @@ -73,21 +82,17 @@ namespace MWGui mSourceSortModel->addDragItem(mItem.mBase, count); } - ItemWidget* baseWidget = mDragAndDropWidget->createWidget - ("MW_ItemIcon", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); + ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget("MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); + + Controllers::ControllerFollowMouse* controller = + MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerFollowMouse::getClassTypeName()) + ->castType(); + MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); + mDraggedWidget = baseWidget; baseWidget->setItem(mItem.mBase); baseWidget->setNeedMouseFocus(false); - - // text widget that shows item count - // TODO: move to ItemWidget - MyGUI::TextBox* text = baseWidget->createWidget("SandBrightText", - MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); - text->setTextAlign(MyGUI::Align::Right); - text->setNeedMouseFocus(false); - text->setTextShadow(true); - text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(ItemView::getCountString(count)); + baseWidget->setCount(count); sourceView->update(); @@ -99,7 +104,12 @@ namespace MWGui std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - mDragAndDropWidget->setVisible(false); + // We can't drop a conjured item to the ground; the target container should always be the source container + if (mItem.mFlags & ItemStack::Flag_Bound && targetModel != mSourceModel) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } // If item is dropped where it was taken from, we don't need to do anything - // otherwise, do the transfer @@ -164,6 +174,13 @@ namespace MWGui const ItemStack& item = mSortModel->getItem(index); + // We can't take a conjured item from a container (some NPC we're pickpocketing, a box, etc) + if (item.mFlags & ItemStack::Flag_Bound) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage1}"); + return; + } + MWWorld::Ptr object = item.mBase; int count = item.mCount; bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); @@ -195,15 +212,6 @@ namespace MWGui { if (mPtr.getTypeName() == typeid(ESM::Container).name()) { - // check that we don't exceed container capacity - MWWorld::Ptr item = mDragAndDrop->mItem.mBase; - float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; - if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); - return; - } - // check container organic flag MWWorld::LiveCellRef* ref = mPtr.get(); if (ref->mBase->mFlags & ESM::Container::Organic) @@ -212,6 +220,15 @@ namespace MWGui messageBox("#{sContentsMessage2}"); return; } + + // check that we don't exceed container capacity + MWWorld::Ptr item = mDragAndDrop->mItem.mBase; + float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; + if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); + return; + } } mDragAndDrop->drop(mModel, mItemView); @@ -232,7 +249,8 @@ namespace MWGui { // we are stealing stuff MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mModel = new PickpocketItemModel(player, new InventoryItemModel(container)); + mModel = new PickpocketItemModel(player, new InventoryItemModel(container), + !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); } else mModel = new InventoryItemModel(container); @@ -262,6 +280,8 @@ namespace MWGui { ReferenceInterface::resetReference(); mItemView->setModel(NULL); + mModel = NULL; + mSortModel = NULL; } void ContainerWindow::close() @@ -354,7 +374,9 @@ namespace MWGui bool ContainerWindow::onTakeItem(const ItemStack &item, int count) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (dynamic_cast(mModel)) + // TODO: move to ItemModels + if (dynamic_cast(mModel) + && !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()) { MWMechanics::Pickpocket pickpocket(player, mPtr); if (pickpocket.pick(item.mBase, count)) diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 79951f70e9..e32c6efaf0 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -33,13 +33,14 @@ namespace MWGui public: bool mIsOnDragAndDrop; MyGUI::Widget* mDraggedWidget; - MyGUI::Widget* mDragAndDropWidget; ItemModel* mSourceModel; ItemView* mSourceView; SortFilterItemModel* mSourceSortModel; ItemStack mItem; int mDraggedCount; + DragAndDrop(); + void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); void drop (ItemModel* targetModel, ItemView* targetView); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index b2befc3ba8..520a32ef8c 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -76,7 +76,7 @@ MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) throw std::runtime_error("Item to copy needs to be from a different container!"); - return *source.getClass().getContainerStore(source).add(item.mBase, count, source); + return *source.getClass().getContainerStore(source).add(item.mBase, count, source, setNewOwner); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/controllers.cpp b/apps/openmw/mwgui/controllers.cpp index e62fb3fcec..5ebd2b1e74 100644 --- a/apps/openmw/mwgui/controllers.cpp +++ b/apps/openmw/mwgui/controllers.cpp @@ -1,11 +1,13 @@ #include "controllers.hpp" +#include + namespace MWGui { namespace Controllers { - ControllerRepeatClick::ControllerRepeatClick() : + ControllerRepeatEvent::ControllerRepeatEvent() : mInit(0.5), mStep(0.1), mEnabled(true), @@ -13,11 +15,11 @@ namespace MWGui { } - ControllerRepeatClick::~ControllerRepeatClick() + ControllerRepeatEvent::~ControllerRepeatEvent() { } - bool ControllerRepeatClick::addTime(MyGUI::Widget* _widget, float _time) + bool ControllerRepeatEvent::addTime(MyGUI::Widget* _widget, float _time) { if(mTimeLeft == 0) mTimeLeft = mInit; @@ -31,24 +33,36 @@ namespace MWGui return true; } - void ControllerRepeatClick::setRepeat(float init, float step) + void ControllerRepeatEvent::setRepeat(float init, float step) { mInit = init; mStep = step; } - void ControllerRepeatClick::setEnabled(bool enable) + void ControllerRepeatEvent::setEnabled(bool enable) { mEnabled = enable; } - void ControllerRepeatClick::setProperty(const std::string& _key, const std::string& _value) + void ControllerRepeatEvent::setProperty(const std::string& _key, const std::string& _value) { } - void ControllerRepeatClick::prepareItem(MyGUI::Widget* _widget) + void ControllerRepeatEvent::prepareItem(MyGUI::Widget* _widget) { } + // ------------------------------------------------------------- + + void ControllerFollowMouse::prepareItem(MyGUI::Widget *_widget) + { + } + + bool ControllerFollowMouse::addTime(MyGUI::Widget *_widget, float _time) + { + _widget->setPosition(MyGUI::InputManager::getInstance().getMousePosition()); + return true; + } + } } diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp index 798acde622..003027a46a 100644 --- a/apps/openmw/mwgui/controllers.hpp +++ b/apps/openmw/mwgui/controllers.hpp @@ -9,14 +9,15 @@ namespace MWGui { namespace Controllers { - class ControllerRepeatClick : + // Should be removed when upgrading to MyGUI 3.2.2 (current git), it has ControllerRepeatClick + class ControllerRepeatEvent : public MyGUI::ControllerItem { - MYGUI_RTTI_DERIVED( ControllerRepeatClick ) + MYGUI_RTTI_DERIVED( ControllerRepeatEvent ) public: - ControllerRepeatClick(); - virtual ~ControllerRepeatClick(); + ControllerRepeatEvent(); + virtual ~ControllerRepeatEvent(); void setRepeat(float init, float step); void setEnabled(bool enable); @@ -40,6 +41,17 @@ namespace MWGui bool mEnabled; float mTimeLeft; }; + + /// Automatically positions a widget below the mouse cursor. + class ControllerFollowMouse : + public MyGUI::ControllerItem + { + MYGUI_RTTI_DERIVED( ControllerFollowMouse ) + + private: + bool addTime(MyGUI::Widget* _widget, float _time); + void prepareItem(MyGUI::Widget* _widget); + }; } } diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index 53c33b3c48..24d0af0d7d 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -19,7 +21,7 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onCancelButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onOkButtonClicked); - mItemEdit->eventEditTextChange += MyGUI::newDelegate(this, &CountDialog::onEditTextChange); + mItemEdit->eventValueChanged += MyGUI::newDelegate(this, &CountDialog::onEditValueChanged); mSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &CountDialog::onSliderMoved); // make sure we read the enter key being pressed to accept multiple items mItemEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &CountDialog::onEnterKeyPressed); @@ -46,7 +48,10 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mItemEdit); mSlider->setScrollPosition(maxCount-1); - mItemEdit->setCaption(boost::lexical_cast(maxCount)); + + mItemEdit->setMinValue(1); + mItemEdit->setMaxValue(maxCount); + mItemEdit->setValue(maxCount); } void CountDialog::cancel() //Keeping this here as I don't know if anything else relies on it. @@ -80,30 +85,13 @@ namespace MWGui setVisible(false); } - void CountDialog::onEditTextChange(MyGUI::EditBox* _sender) + void CountDialog::onEditValueChanged(int value) { - if (_sender->getCaption() == "") - return; - - unsigned int count; - try - { - count = boost::lexical_cast(_sender->getCaption()); - } - catch (std::bad_cast&) - { - count = 1; - } - if (count > mSlider->getScrollRange()) - { - count = mSlider->getScrollRange(); - } - mSlider->setScrollPosition(count-1); - onSliderMoved(mSlider, count-1); + mSlider->setScrollPosition(value-1); } void CountDialog::onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position) { - mItemEdit->setCaption(boost::lexical_cast(_position+1)); + mItemEdit->setValue(_position+1); } } diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp index a00b0b223b..a54e99cf4b 100644 --- a/apps/openmw/mwgui/countdialog.hpp +++ b/apps/openmw/mwgui/countdialog.hpp @@ -3,6 +3,11 @@ #include "windowbase.hpp" +namespace Gui +{ + class NumericEditBox; +} + namespace MWGui { class CountDialog : public WindowModal @@ -22,7 +27,7 @@ namespace MWGui private: MyGUI::ScrollBar* mSlider; - MyGUI::EditBox* mItemEdit; + Gui::NumericEditBox* mItemEdit; MyGUI::TextBox* mItemText; MyGUI::TextBox* mLabelText; MyGUI::Button* mOkButton; @@ -30,7 +35,7 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* _sender); void onOkButtonClicked(MyGUI::Widget* _sender); - void onEditTextChange(MyGUI::EditBox* _sender); + void onEditValueChanged(int value); void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position); void onEnterKeyPressed(MyGUI::EditBox* _sender); }; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index a6ab1f122b..a3f82e65d7 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -17,7 +19,6 @@ #include "../mwdialogue/dialoguemanagerimp.hpp" #include "widgets.hpp" -#include "list.hpp" #include "tradewindow.hpp" #include "spellbuyingwindow.hpp" #include "travelwindow.hpp" @@ -25,6 +26,16 @@ #include "journalbooks.hpp" // to_utf8_span +namespace +{ + + MyGUI::Colour getTextColour (const std::string& type) + { + return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); + } + +} + namespace MWGui { @@ -102,7 +113,7 @@ namespace MWGui void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { - BookTypesetter::Style* title = typesetter->createStyle("", MyGUI::Colour(223/255.f, 201/255.f, 159/255.f)); + BookTypesetter::Style* title = typesetter->createStyle("", getTextColour("header")); typesetter->sectionBreak(9); if (mTitle != "") typesetter->write(title, to_utf8_span(mTitle.c_str())); @@ -114,10 +125,10 @@ namespace MWGui // We need this copy for when @# hyperlinks are replaced std::string text = mText; - size_t pos_begin, pos_end; + size_t pos_end; for(;;) { - pos_begin = text.find('@'); + size_t pos_begin = text.find('@'); if (pos_begin != std::string::npos) pos_end = text.find('#', pos_begin); @@ -146,14 +157,14 @@ namespace MWGui if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) { - BookTypesetter::Style* style = typesetter->createStyle("", MyGUI::Colour(202/255.f, 165/255.f, 96/255.f)); + BookTypesetter::Style* style = typesetter->createStyle("", getTextColour("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 (143/255.f, 155/255.f, 218/255.f); - const MyGUI::Colour linkNormal (112/255.f, 126/255.f, 207/255.f); - const MyGUI::Colour linkActive (175/255.f, 184/255.f, 228/255.f); + const MyGUI::Colour linkHot (getTextColour("link_over")); + const MyGUI::Colour linkNormal (getTextColour("link")); + const MyGUI::Colour linkActive (getTextColour("link_pressed")); BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); if (formatted < it->first.first) typesetter->write(style, formatted, it->first.first); @@ -185,11 +196,11 @@ namespace MWGui void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const { - BookTypesetter::Style* style = typesetter->createStyle("", MyGUI::Colour(202/255.f, 165/255.f, 96/255.f)); + BookTypesetter::Style* style = typesetter->createStyle("", getTextColour("normal")); - const MyGUI::Colour linkHot (143/255.f, 155/255.f, 218/255.f); - const MyGUI::Colour linkNormal (112/255.f, 126/255.f, 207/255.f); - const MyGUI::Colour linkActive (175/255.f, 184/255.f, 228/255.f); + const MyGUI::Colour linkHot (getTextColour("link_over")); + const MyGUI::Colour linkNormal (getTextColour("link")); + const MyGUI::Colour linkActive (getTextColour("link_pressed")); if (topicId) style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); @@ -203,7 +214,7 @@ namespace MWGui void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { - BookTypesetter::Style* title = typesetter->createStyle("", MyGUI::Colour(223/255.f, 201/255.f, 159/255.f)); + BookTypesetter::Style* title = typesetter->createStyle("", getTextColour("notify")); typesetter->sectionBreak(9); typesetter->write(title, to_utf8_span(mText.c_str())); } @@ -266,15 +277,19 @@ namespace MWGui BookPage::ClickCallback callback = boost::bind (&DialogueWindow::notifyLinkClicked, this, _1); mHistory->adviseLinkClicked(callback); - static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } void DialogueWindow::exit() { if ((!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) && !mGoodbye) - return; - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + { + // in choice, not allowed to escape, but give access to main menu to allow loading other saves + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + else + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); } void DialogueWindow::onWindowResize(MyGUI::Window* _sender) @@ -391,6 +406,31 @@ namespace MWGui mLinks.clear(); updateOptions(); + + restock(); + } + + void DialogueWindow::restock() + { + MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); + float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); + + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) + { + sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); + + mPtr.getClass().restock(mPtr); + + // Also restock any containers owned by this merchant, which are also available to buy in the trade window + std::vector itemSources; + MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); + for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) + { + it->getClass().restock(*it); + } + + sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); + } } void DialogueWindow::setKeywords(std::list keyWords) @@ -477,10 +517,10 @@ namespace MWGui typesetter->sectionBreak(9); // choices - const MyGUI::Colour linkHot (223/255.f, 201/255.f, 159/255.f); - const MyGUI::Colour linkNormal (150/255.f, 50/255.f, 30/255.f); - const MyGUI::Colour linkActive (243/255.f, 237/255.f, 221/255.f); - for (std::map::reverse_iterator it = mChoices.rbegin(); it != mChoices.rend(); ++it) + const MyGUI::Colour linkHot (getTextColour("answer_over")); + const MyGUI::Colour linkNormal (getTextColour("answer")); + const MyGUI::Colour linkActive (getTextColour("answer_pressed")); + for (std::vector >::iterator it = mChoices.begin(); it != mChoices.end(); ++it) { Choice* link = new Choice(it->second); mLinks.push_back(link); @@ -571,7 +611,7 @@ namespace MWGui void DialogueWindow::addChoice(const std::string& choice, int id) { - mChoices[choice] = id; + mChoices.push_back(std::make_pair(choice, id)); updateHistory(); } @@ -592,8 +632,7 @@ namespace MWGui dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); - mDispositionText->eraseText(0, mDispositionText->getTextLength()); - mDispositionText->addText("#B29154"+boost::lexical_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")+"#B29154"); + mDispositionText->setCaption(boost::lexical_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")); } bool dispositionWasVisible = mDispositionBar->getVisible(); @@ -637,8 +676,7 @@ namespace MWGui + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange())); mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(disp); - mDispositionText->eraseText(0, mDispositionText->getTextLength()); - mDispositionText->addText("#B29154"+boost::lexical_cast(disp)+std::string("/100")+"#B29154"); + mDispositionText->setCaption(boost::lexical_cast(disp)+std::string("/100")); } } } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 516c04942c..18f1f15551 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -8,14 +8,14 @@ #include "keywordsearch.hpp" +namespace Gui +{ + class MWList; +} + namespace MWGui { class WindowManager; - - namespace Widgets - { - class MWList; - } } /* @@ -152,6 +152,7 @@ namespace MWGui private: void updateOptions(); + void restock(); int mServices; @@ -160,7 +161,7 @@ namespace MWGui bool mGoodbye; std::vector mHistoryContents; - std::map mChoices; + std::vector > mChoices; std::vector mLinks; std::map mTopicLinks; @@ -168,7 +169,7 @@ namespace MWGui KeywordSearchT mKeywordSearch; BookPage* mHistory; - Widgets::MWList* mTopicsList; + Gui::MWList* mTopicsList; MyGUI::ScrollBar* mScrollBar; MyGUI::Progress* mDispositionBar; MyGUI::EditBox* mDispositionText; diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 92221977b1..30d67f5540 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -24,7 +24,7 @@ namespace MWGui EnchantingDialog::EnchantingDialog() : WindowBase("openmw_enchanting_dialog.layout") - , EffectEditorBase() + , EffectEditorBase(EffectEditorBase::Enchanting) , mItemSelectionDialog(NULL) { getWidget(mName, "NameEdit"); @@ -58,9 +58,6 @@ namespace MWGui void EnchantingDialog::open() { center(); - - setSoulGem(MWWorld::Ptr()); - setItem(MWWorld::Ptr()); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) @@ -78,7 +75,6 @@ namespace MWGui mSoulBox->setUserData(gem); mEnchanting.setSoulGem(gem); } - updateLabels(); } void EnchantingDialog::setItem(const MWWorld::Ptr &item) @@ -91,12 +87,12 @@ namespace MWGui } else { + mName->setCaption(item.getClass().getName(item)); mItemBox->setItem(item); mItemBox->setUserString ("ToolTipType", "ItemPtr"); mItemBox->setUserData(item); mEnchanting.setOldItem(item); } - updateLabels(); } void EnchantingDialog::exit() @@ -144,9 +140,17 @@ namespace MWGui mEnchanting.setSelfEnchanting(false); mEnchanting.setEnchanter(actor); + mBuyButton->setCaptionWithReplacing("#{sBuy}"); + mPtr = actor; + setSoulGem(MWWorld::Ptr()); + setItem(MWWorld::Ptr()); + startEditing (); + mPrice->setVisible(true); + mPriceText->setVisible(true); + updateLabels(); } void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) @@ -156,11 +160,13 @@ namespace MWGui mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(player); + mBuyButton->setCaptionWithReplacing("#{sCreate}"); + mPtr = player; startEditing(); - mEnchanting.setSoulGem(soulgem); setSoulGem(soulgem); + setItem(MWWorld::Ptr()); mPrice->setVisible(false); mPriceText->setVisible(false); @@ -171,6 +177,16 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + resetReference(); + } + + void EnchantingDialog::resetReference() + { + ReferenceInterface::resetReference(); + setItem(MWWorld::Ptr()); + setSoulGem(MWWorld::Ptr()); + mPtr = MWWorld::Ptr(); + mEnchanting.setEnchanter(MWWorld::Ptr()); } void EnchantingDialog::onCancelButtonClicked(MyGUI::Widget* sender) @@ -193,6 +209,7 @@ namespace MWGui else { setItem(MWWorld::Ptr()); + updateLabels(); } } @@ -201,6 +218,7 @@ namespace MWGui mItemSelectionDialog->setVisible(false); setItem(item); + MWBase::Environment::get().getSoundManager()->playSound(item.getClass().getDownSoundId(item), 1, 1); mEnchanting.nextCastStyle(); updateLabels(); } @@ -213,8 +231,8 @@ namespace MWGui void EnchantingDialog::onSoulSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); - mEnchanting.setSoulGem(item); + mEnchanting.setSoulGem(item); if(mEnchanting.getGemCharge()==0) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage32}"); @@ -222,6 +240,8 @@ namespace MWGui } setSoulGem(item); + MWBase::Environment::get().getSoundManager()->playSound(item.getClass().getDownSoundId(item), 1, 1); + updateLabels(); } void EnchantingDialog::onSoulCancel() @@ -246,6 +266,7 @@ namespace MWGui else { setSoulGem(MWWorld::Ptr()); + updateLabels(); } } @@ -299,7 +320,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (mEnchanting.getEnchantPrice() > playerGold) + if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; @@ -321,7 +342,7 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, item.getClass().getValue(item)); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index b75ae82803..5b67d199b0 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -29,6 +29,8 @@ namespace MWGui void startEnchanting(MWWorld::Ptr actor); void startSelfEnchanting(MWWorld::Ptr soulgem); + virtual void resetReference(); + protected: virtual void onReferenceUnavailable(); virtual void notifyEffectsChanged (); diff --git a/apps/openmw/mwgui/fontloader.hpp b/apps/openmw/mwgui/fontloader.hpp deleted file mode 100644 index 7954b0875e..0000000000 --- a/apps/openmw/mwgui/fontloader.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MWGUI_FONTLOADER_H -#define MWGUI_FONTLOADER_H - -#include - -namespace MWGui -{ - - - /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and Ogre - class FontLoader - { - public: - FontLoader (ToUTF8::FromType encoding); - void loadAllFonts (); - - private: - ToUTF8::FromType mEncoding; - - void loadFont (const std::string& fileName); - }; - -} - -#endif diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 4d3d04ced7..096c7db0b3 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,419 +1,405 @@ #include "formatting.hpp" #include +#include +#include #include "../mwscript/interpretercontext.hpp" #include #include -#include #include -#include -#include -#include +#include #include -namespace -{ - int convertFromHex(std::string hex) - { - int value = 0; - - int a = 0; - int b = hex.length() - 1; - for (; b >= 0; a++, b--) - { - if (hex[b] >= '0' && hex[b] <= '9') - { - value += (hex[b] - '0') * (1 << (a * 4)); - } - else - { - switch (hex[b]) - { - case 'A': - case 'a': - value += 10 * (1 << (a * 4)); - break; - - case 'B': - case 'b': - value += 11 * (1 << (a * 4)); - break; - - case 'C': - case 'c': - value += 12 * (1 << (a * 4)); - break; - - case 'D': - case 'd': - value += 13 * (1 << (a * 4)); - break; - - case 'E': - case 'e': - value += 14 * (1 << (a * 4)); - break; - - case 'F': - case 'f': - value += 15 * (1 << (a * 4)); - break; - - default: - throw std::runtime_error("invalid character in hex number"); - break; - } - } - } - - return value; - } - - Ogre::UTFString::unicode_char unicodeCharFromChar(char ch) - { - std::string s; - s += ch; - Ogre::UTFString string(s); - return string.getChar(0); - } - - bool is_not_empty(const std::string& s) { - std::string temp = s; - boost::algorithm::trim(temp); - return !temp.empty(); - } -} +#include namespace MWGui { - - std::vector BookTextParser::split(std::string utf8Text, const int width, const int height) + namespace Formatting { - using Ogre::UTFString; - std::vector result; - - MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor - utf8Text = Interpreter::fixDefinesBook(utf8Text, interpreterContext); - - boost::algorithm::replace_all(utf8Text, "\n", ""); - boost::algorithm::replace_all(utf8Text, "\r", ""); - boost::algorithm::replace_all(utf8Text, "
    ", "\n"); - boost::algorithm::replace_all(utf8Text, "

    ", "\n\n"); - - UTFString text(utf8Text); - const int spacing = 48; - - const UTFString::unicode_char LEFT_ANGLE = unicodeCharFromChar('<'); - const UTFString::unicode_char NEWLINE = unicodeCharFromChar('\n'); - const UTFString::unicode_char SPACE = unicodeCharFromChar(' '); - - while (!text.empty()) + /* BookTextParser */ + BookTextParser::BookTextParser(const std::string & text) + : mIndex(0), mText(text), mIgnoreNewlineTags(true), mIgnoreLineEndings(true) { - // read in characters until we have exceeded the size, or run out of text - int currentWidth = 0; - int currentHeight = 0; + MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + mText = Interpreter::fixDefinesBook(mText, interpreterContext); - size_t currentWordStart = 0; - size_t index = 0; - + boost::algorithm::replace_all(mText, "\r", ""); + + registerTag("br", Event_BrTag); + registerTag("p", Event_PTag); + registerTag("img", Event_ImgTag); + registerTag("div", Event_DivTag); + registerTag("font", Event_FontTag); + } + + void BookTextParser::registerTag(const std::string & tag, BookTextParser::Events type) + { + mTagTypes[tag] = type; + } + + std::string BookTextParser::getReadyText() + { + return mReadyText; + } + + BookTextParser::Events BookTextParser::next() + { + while (mIndex < mText.size()) { - std::string texToTrim = text.asUTF8(); - boost::algorithm::trim( texToTrim ); - text = UTFString(texToTrim); - } - - - while (currentHeight <= height - spacing && index < text.size()) - { - const UTFString::unicode_char ch = text.getChar(index); - if (ch == LEFT_ANGLE) + char ch = mText[mIndex]; + if (ch == '<') { - const size_t tagStart = index + 1; - const size_t tagEnd = text.find('>', tagStart); - if (tagEnd == UTFString::npos) + const size_t tagStart = mIndex + 1; + const size_t tagEnd = mText.find('>', tagStart); + if (tagEnd == std::string::npos) throw std::runtime_error("BookTextParser Error: Tag is not terminated"); - const std::string tag = text.substr(tagStart, tagEnd - tagStart).asUTF8(); + parseTag(mText.substr(tagStart, tagEnd - tagStart)); + mIndex = tagEnd; - if (boost::algorithm::starts_with(tag, "IMG")) + if (mTagTypes.find(mTag) != mTagTypes.end()) { - const int h = mHeight; - parseImage(tag, false); - currentHeight += (mHeight - h); - currentWidth = 0; - } - else if (boost::algorithm::starts_with(tag, "FONT")) - { - parseFont(tag); - if (currentWidth != 0) { - currentHeight += currentFontHeight(); - currentWidth = 0; + Events type = mTagTypes.at(mTag); + + if (type == Event_BrTag || type == Event_PTag) + { + if (!mIgnoreNewlineTags) + { + if (type == Event_BrTag) + mBuffer.push_back('\n'); + else + { + mBuffer.append("\n\n"); + } + } + mIgnoreLineEndings = true; } - currentWidth = 0; - } - else if (boost::algorithm::starts_with(tag, "DIV")) - { - parseDiv(tag); - if (currentWidth != 0) { - currentHeight += currentFontHeight(); - currentWidth = 0; + else + flushBuffer(); + + if (type == Event_ImgTag) + { + mIgnoreLineEndings = false; + mIgnoreNewlineTags = false; } + + ++mIndex; + return type; } - index = tagEnd; - } - else if (ch == NEWLINE) - { - currentHeight += currentFontHeight(); - currentWidth = 0; - currentWordStart = index; - } - else if (ch == SPACE) - { - currentWidth += 3; // keep this in sync with the font's SpaceWidth property - currentWordStart = index; } else { - currentWidth += widthForCharGlyph(ch); - } - - if (currentWidth > width) - { - currentHeight += currentFontHeight(); - currentWidth = 0; - // add size of the current word - UTFString word = text.substr(currentWordStart, index - currentWordStart); - for (UTFString::const_iterator it = word.begin(), end = word.end(); it != end; ++it) - currentWidth += widthForCharGlyph(it.getCharacter()); - } - index += UTFString::_utf16_char_length(ch); - } - const size_t pageEnd = (currentHeight > height - spacing && currentWordStart != 0) - ? currentWordStart : index; - - result.push_back(text.substr(0, pageEnd).asUTF8()); - text.erase(0, pageEnd); - } - - std::vector nonEmptyPages; - boost::copy(result | boost::adaptors::filtered(is_not_empty), std::back_inserter(nonEmptyPages)); - return nonEmptyPages; - } - - float BookTextParser::widthForCharGlyph(unsigned unicodeChar) const - { - std::string fontName(mTextStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont); - return MyGUI::FontManager::getInstance().getByName(fontName) - ->getGlyphInfo(unicodeChar)->width; - } - - float BookTextParser::currentFontHeight() const - { - std::string fontName(mTextStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont); - return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight(); - } - - MyGUI::IntSize BookTextParser::parsePage(std::string text, MyGUI::Widget* parent, const int width) - { - MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor - text = Interpreter::fixDefinesBook(text, interpreterContext); - - mParent = parent; - mWidth = width; - mHeight = 0; - - assert(mParent); - while (mParent->getChildCount()) - { - MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0)); - } - - // remove trailing " - if (text[text.size()-1] == '\"') - text.erase(text.size()-1); - - parseSubText(text); - return MyGUI::IntSize(mWidth, mHeight); - } - - MyGUI::IntSize BookTextParser::parseScroll(std::string text, MyGUI::Widget* parent, const int width) - { - MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor - text = Interpreter::fixDefinesBook(text, interpreterContext); - - mParent = parent; - mWidth = width; - mHeight = 0; - - assert(mParent); - while (mParent->getChildCount()) - { - MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0)); - } - - boost::algorithm::replace_all(text, "
    ", "\n"); - boost::algorithm::replace_all(text, "

    ", "\n\n"); - boost::algorithm::trim_left(text); - - // remove trailing " - if (text[text.size()-1] == '\"') - text.erase(text.size()-1); - - parseSubText(text); - return MyGUI::IntSize(mWidth, mHeight); - } - - - void BookTextParser::parseImage(std::string tag, bool createWidget) - { - int src_start = tag.find("SRC=")+5; - std::string image = tag.substr(src_start, tag.find('"', src_start)-src_start); - - // fix texture extension to .dds - if (image.size() > 4) - { - image[image.size()-3] = 'd'; - image[image.size()-2] = 'd'; - image[image.size()-1] = 's'; - } - - int width_start = tag.find("WIDTH=")+7; - int width = boost::lexical_cast(tag.substr(width_start, tag.find('"', width_start)-width_start)); - - int height_start = tag.find("HEIGHT=")+8; - int height = boost::lexical_cast(tag.substr(height_start, tag.find('"', height_start)-height_start)); - - if (createWidget) - { - MyGUI::ImageBox* box = mParent->createWidget ("ImageBox", - MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top, - mParent->getName() + boost::lexical_cast(mParent->getChildCount())); - - // Apparently a bug with some morrowind versions, they reference the image without the size suffix. - // So if the image isn't found, try appending the size. - if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("bookart\\"+image)) - { - std::stringstream str; - str << image.substr(0, image.rfind(".")) << "_" << width << "_" << height << image.substr(image.rfind(".")); - image = str.str(); - } - - box->setImageTexture("bookart\\" + image); - box->setProperty("NeedMouse", "false"); - } - - mWidth = std::max(mWidth, width); - mHeight += height; - } - - void BookTextParser::parseDiv(std::string tag) - { - if (tag.find("ALIGN=") == std::string::npos) - return; - - int align_start = tag.find("ALIGN=")+7; - std::string align = tag.substr(align_start, tag.find('"', align_start)-align_start); - if (align == "CENTER") - mTextStyle.mTextAlign = MyGUI::Align::HCenter; - else if (align == "LEFT") - mTextStyle.mTextAlign = MyGUI::Align::Left; - } - - void BookTextParser::parseFont(std::string tag) - { - if (tag.find("COLOR=") != std::string::npos) - { - int color_start = tag.find("COLOR=")+7; - std::string color = tag.substr(color_start, tag.find('"', color_start)-color_start); - - mTextStyle.mColour = MyGUI::Colour( - convertFromHex(color.substr(0, 2))/255.0, - convertFromHex(color.substr(2, 2))/255.0, - convertFromHex(color.substr(4, 2))/255.0); - } - if (tag.find("FACE=") != std::string::npos) - { - int face_start = tag.find("FACE=")+6; - std::string face = tag.substr(face_start, tag.find('"', face_start)-face_start); - - if (face != "Magic Cards") - mTextStyle.mFont = face; - } - if (tag.find("SIZE=") != std::string::npos) - { - /// \todo - } - } - - void BookTextParser::parseSubText(std::string text) - { - if (text[0] == '<') - { - const size_t tagStart = 1; - const size_t tagEnd = text.find('>', tagStart); - if (tagEnd == std::string::npos) - throw std::runtime_error("BookTextParser Error: Tag is not terminated"); - const std::string tag = text.substr(tagStart, tagEnd - tagStart); - - if (boost::algorithm::starts_with(tag, "IMG")) - parseImage(tag); - if (boost::algorithm::starts_with(tag, "FONT")) - parseFont(tag); - if (boost::algorithm::starts_with(tag, "DIV")) - parseDiv(tag); - - text.erase(0, tagEnd + 1); - } - - size_t tagStart = std::string::npos; - std::string realText; // real text, without tags - for (size_t i = 0; i= text.size()) - throw std::runtime_error("BookTextParser Error: Tag is not terminated"); - ++i; - c = text[i]; + mBuffer.push_back(ch); + mIgnoreLineEndings = false; + mIgnoreNewlineTags = false; } + } + + ++mIndex; + } + + flushBuffer(); + return Event_EOF; + } + + void BookTextParser::flushBuffer() + { + mReadyText = mBuffer; + mBuffer.clear(); + } + + const BookTextParser::Attributes & BookTextParser::getAttributes() const + { + return mAttributes; + } + + void BookTextParser::parseTag(std::string tag) + { + size_t tagNameEndPos = tag.find(' '); + mTag = tag.substr(0, tagNameEndPos); + Misc::StringUtils::toLower(mTag); + mAttributes.clear(); + if (tagNameEndPos == std::string::npos) + return; + tag.erase(0, tagNameEndPos+1); + + while (!tag.empty()) + { + size_t sepPos = tag.find('='); + if (sepPos == std::string::npos) + return; + + std::string key = tag.substr(0, sepPos); + Misc::StringUtils::toLower(key); + tag.erase(0, sepPos+1); + + std::string value; + + if (tag.empty()) + return; + + if (tag[0] == '"') + { + size_t quoteEndPos = tag.find('"', 1); + if (quoteEndPos == std::string::npos) + throw std::runtime_error("BookTextParser Error: Missing end quote in tag"); + value = tag.substr(1, quoteEndPos-1); + tag.erase(0, quoteEndPos+2); + } + else + { + size_t valEndPos = tag.find(' '); + if (valEndPos == std::string::npos) + { + value = tag; + tag.erase(); + } + else + { + value = tag.substr(0, valEndPos); + tag.erase(0, valEndPos+1); + } + } + + mAttributes[key] = value; + } + } + + /* BookFormatter */ + Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight) + { + Paginator pag(pageWidth, pageHeight); + + while (parent->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0)); + } + + MyGUI::Widget * paper = parent->createWidget("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); + paper->setNeedMouseFocus(false); + + BookTextParser parser(markup); + for (;;) + { + BookTextParser::Events event = parser.next(); + if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag) continue; - } - else + + std::string plainText = parser.getReadyText(); + if (!plainText.empty()) { - tagStart = i; + // if there's a newline at the end of the box caption, remove it + if (plainText[plainText.size()-1] == '\n') + plainText.erase(plainText.end()-1); + +#if (MYGUI_VERSION < MYGUI_DEFINE_VERSION(3, 2, 2)) + // splitting won't be fully functional until 3.2.2 (see TextElement::pageSplit()) + // hack: prevent newlines at the end of the book possibly creating unnecessary pages + if (event == BookTextParser::Event_EOF) + { + while (plainText.size() && plainText[plainText.size()-1] == '\n') + plainText.erase(plainText.end()-1); + } +#endif + + TextElement elem(paper, pag, mTextStyle, plainText); + elem.paginate(); + } + + if (event == BookTextParser::Event_EOF) break; + + switch (event) + { + case BookTextParser::Event_ImgTag: + { + const BookTextParser::Attributes & attr = parser.getAttributes(); + + if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) + continue; + + std::string src = attr.at("src"); + int width = boost::lexical_cast(attr.at("width")); + int height = boost::lexical_cast(attr.at("height")); + + ImageElement elem(paper, pag, src, width, height); + elem.paginate(); + break; + } + case BookTextParser::Event_FontTag: + handleFont(parser.getAttributes()); + break; + case BookTextParser::Event_DivTag: + handleDiv(parser.getAttributes()); + break; + default: + break; } } - else - realText += c; + + // insert last page + if (pag.getStartTop() != pag.getCurrentTop()) + pag << Paginator::Page(pag.getStartTop(), pag.getStartTop() + pag.getPageHeight()); + + paper->setSize(paper->getWidth(), pag.getCurrentTop()); + + return pag.getPages(); } - MyGUI::EditBox* box = mParent->createWidget("NormalText", - MyGUI::IntCoord(0, mHeight, mWidth, 24), MyGUI::Align::Left | MyGUI::Align::Top, - mParent->getName() + boost::lexical_cast(mParent->getChildCount())); - box->setProperty("Static", "true"); - box->setProperty("MultiLine", "true"); - box->setProperty("WordWrap", "true"); - box->setProperty("NeedMouse", "false"); - box->setMaxTextLength(realText.size()); - box->setTextAlign(mTextStyle.mTextAlign); - box->setTextColour(mTextStyle.mColour); - box->setFontName(mTextStyle.mFont); - box->setCaption(realText); - box->setSize(box->getSize().width, box->getTextSize().height); - mHeight += box->getTextSize().height; - - if (tagStart != std::string::npos) + Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup) { - parseSubText(text.substr(tagStart, text.size())); + return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight()); + } + + void BookFormatter::handleDiv(const BookTextParser::Attributes & attr) + { + if (attr.find("align") == attr.end()) + return; + + std::string align = attr.at("align"); + + if (Misc::StringUtils::ciEqual(align, "center")) + mTextStyle.mTextAlign = MyGUI::Align::HCenter; + else if (Misc::StringUtils::ciEqual(align, "left")) + mTextStyle.mTextAlign = MyGUI::Align::Left; + } + + void BookFormatter::handleFont(const BookTextParser::Attributes & attr) + { + if (attr.find("color") != attr.end()) + { + int color; + std::stringstream ss; + ss << attr.at("color"); + ss >> std::hex >> color; + + mTextStyle.mColour = MyGUI::Colour( + (color>>16 & 0xFF) / 255.f, + (color>>8 & 0xFF) / 255.f, + (color & 0xFF) / 255.f); + } + if (attr.find("face") != attr.end()) + { + std::string face = attr.at("face"); + + if (face != "Magic Cards") + mTextStyle.mFont = face; + } + if (attr.find("size") != attr.end()) + { + /// \todo + } + } + + /* GraphicElement */ + GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag) + : mParent(parent), mPaginator(pag) + { + } + + void GraphicElement::paginate() + { + int newTop = mPaginator.getCurrentTop() + getHeight(); + while (newTop-mPaginator.getStartTop() > mPaginator.getPageHeight()) + { + int newStartTop = pageSplit(); + mPaginator << Paginator::Page(mPaginator.getStartTop(), newStartTop); + mPaginator.setStartTop(newStartTop); + } + + mPaginator.setCurrentTop(newTop); + } + + int GraphicElement::pageSplit() + { + return mPaginator.getStartTop() + mPaginator.getPageHeight(); + } + + /* TextElement */ + TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag, + const TextStyle & style, const std::string & text) + : GraphicElement(parent, pag), + mStyle(style) + { + MyGUI::EditBox* box = parent->createWidget("NormalText", + MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, + parent->getName() + boost::lexical_cast(parent->getChildCount())); + box->setProperty("Static", "true"); + box->setProperty("MultiLine", "true"); + box->setProperty("WordWrap", "true"); + box->setProperty("NeedMouse", "false"); + box->setMaxTextLength(text.size()); + box->setTextAlign(mStyle.mTextAlign); + box->setTextColour(mStyle.mColour); + box->setFontName(mStyle.mFont); + box->setCaption(MyGUI::TextIterator::toTagsString(text)); + box->setSize(box->getSize().width, box->getTextSize().height); + mEditBox = box; + } + + int TextElement::currentFontHeight() const + { + std::string fontName(mStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mStyle.mFont); + return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight(); + } + + int TextElement::getHeight() + { + return mEditBox->getTextSize().height; + } + + int TextElement::pageSplit() + { + // split lines + const int lineHeight = currentFontHeight(); + unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()) / lineHeight; + int ret = mPaginator.getCurrentTop() + lastLine * lineHeight; + + // first empty lines that would go to the next page should be ignored + // unfortunately, getLineInfo method won't be available until 3.2.2 +#if (MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3, 2, 2)) + const MyGUI::VectorLineInfo & lines = mEditBox->getSubWidgetText()->castType()->getLineInfo(); + for (unsigned int i = lastLine; i < lines.size(); ++i) + { + if (lines[i].width == 0) + ret += lineHeight; + else + break; + } +#endif + return ret; + } + + /* ImageElement */ + ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag, + const std::string & src, int width, int height) + : GraphicElement(parent, pag), + mImageHeight(height) + { + mImageBox = parent->createWidget ("ImageBox", + MyGUI::IntCoord(0, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, + parent->getName() + boost::lexical_cast(parent->getChildCount())); + + std::string image = Misc::ResourceHelpers::correctBookartPath(src, width, mImageHeight); + mImageBox->setImageTexture(image); + mImageBox->setProperty("NeedMouse", "false"); + } + + int ImageElement::getHeight() + { + return mImageHeight; + } + + int ImageElement::pageSplit() + { + // if the image is larger than the page, fall back to the default pageSplit implementation + if (mImageHeight > mPaginator.getPageHeight()) + return GraphicElement::pageSplit(); + return mPaginator.getCurrentTop(); } } - } diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index a32d98fe58..cf55b36fb9 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -2,66 +2,150 @@ #define MWGUI_FORMATTING_H #include +#include namespace MWGui { - struct TextStyle + namespace Formatting { - TextStyle() : - mColour(0,0,0) - , mFont("Default") - , mTextSize(16) - , mTextAlign(MyGUI::Align::Left | MyGUI::Align::Top) + struct TextStyle { - } + TextStyle() : + mColour(0,0,0) + , mFont("Default") + , mTextSize(16) + , mTextAlign(MyGUI::Align::Left | MyGUI::Align::Top) + { + } - MyGUI::Colour mColour; - std::string mFont; - int mTextSize; - MyGUI::Align mTextAlign; - }; + MyGUI::Colour mColour; + std::string mFont; + int mTextSize; + MyGUI::Align mTextAlign; + }; - /// \brief utilities for parsing book/scroll text as mygui widgets - class BookTextParser - { - public: - /** - * Parse markup as MyGUI widgets - * @param markup to parse - * @param parent for the created widgets - * @param maximum width - * @return size of the created widgets - */ - MyGUI::IntSize parsePage(std::string text, MyGUI::Widget* parent, const int width); - - /** - * Parse markup as MyGUI widgets - * @param markup to parse - * @param parent for the created widgets - * @param maximum width - * @return size of the created widgets - */ - MyGUI::IntSize parseScroll(std::string text, MyGUI::Widget* parent, const int width); + class BookTextParser + { + public: + typedef std::map Attributes; + enum Events + { + Event_None = -2, + Event_EOF = -1, + Event_BrTag, + Event_PTag, + Event_ImgTag, + Event_DivTag, + Event_FontTag + }; - /** - * Split the specified text into pieces that fit in the area specified by width and height parameters - */ - std::vector split(std::string text, const int width, const int height); + BookTextParser(const std::string & text); + void registerTag(const std::string & tag, Events type); + std::string getReadyText(); - protected: - float widthForCharGlyph(unsigned unicodeChar) const; - float currentFontHeight() const; - void parseSubText(std::string text); + Events next(); + void flushBuffer(); + const Attributes & getAttributes() const; + void parseTag(std::string tag); - void parseImage(std::string tag, bool createWidget=true); - void parseDiv(std::string tag); - void parseFont(std::string tag); - private: - MyGUI::Widget* mParent; - int mWidth; // maximum width - int mHeight; // current height - TextStyle mTextStyle; - }; + private: + size_t mIndex; + std::string mText; + std::string mReadyText; + + bool mIgnoreNewlineTags; + bool mIgnoreLineEndings; + Attributes mAttributes; + std::string mTag; + std::map mTagTypes; + std::string mBuffer; + }; + + class Paginator + { + public: + typedef std::pair Page; + typedef std::vector Pages; + + Paginator(int pageWidth, int pageHeight) + : mStartTop(0), mCurrentTop(0), + mPageWidth(pageWidth), mPageHeight(pageHeight) + { + } + + int getStartTop() const { return mStartTop; } + int getCurrentTop() const { return mCurrentTop; } + int getPageWidth() const { return mPageWidth; } + int getPageHeight() const { return mPageHeight; } + Pages getPages() const { return mPages; } + + void setStartTop(int top) { mStartTop = top; } + void setCurrentTop(int top) { mCurrentTop = top; } + + Paginator & operator<<(const Page & page) + { + mPages.push_back(page); + return *this; + } + + private: + int mStartTop, mCurrentTop; + int mPageWidth, mPageHeight; + Pages mPages; + }; + + /// \brief utilities for parsing book/scroll text as mygui widgets + class BookFormatter + { + public: + Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight); + Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup); + + protected: + void handleImg(const BookTextParser::Attributes & attr); + void handleDiv(const BookTextParser::Attributes & attr); + void handleFont(const BookTextParser::Attributes & attr); + private: + TextStyle mTextStyle; + }; + + class GraphicElement + { + public: + GraphicElement(MyGUI::Widget * parent, Paginator & pag); + virtual int getHeight() = 0; + virtual void paginate(); + virtual int pageSplit(); + + protected: + MyGUI::Widget * mParent; + Paginator & mPaginator; + }; + + class TextElement : public GraphicElement + { + public: + TextElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style, const std::string & text); + virtual int getHeight(); + virtual int pageSplit(); + private: + int currentFontHeight() const; + TextStyle mStyle; + MyGUI::EditBox * mEditBox; + }; + + class ImageElement : public GraphicElement + { + public: + ImageElement(MyGUI::Widget * parent, Paginator & pag, const std::string & src, int width, int height); + virtual int getHeight(); + virtual int pageSplit(); + + private: + int mImageHeight; + MyGUI::ImageBox * mImageBox; + }; + } } #endif diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index b39fd0db73..2593e6e77c 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -59,8 +61,9 @@ namespace MWGui }; - HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) + HUD::HUD(CustomMarkerCollection &customMarkers, int fpsLevel, DragAndDrop* dragAndDrop) : Layout("openmw_hud.layout") + , LocalMapBase(customMarkers) , mHealth(NULL) , mMagicka(NULL) , mStamina(NULL) @@ -92,11 +95,12 @@ namespace MWGui , mSpellVisible(true) , mWorldMouseOver(false) , mEnemyHealthTimer(-1) + , mEnemyActorId(-1) , mIsDrowning(false) , mWeaponSpellTimer(0.f) , mDrowningFlashTheta(0.f) { - setCoord(0,0, width, height); + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); // Energy bars getWidget(mHealthFrame, "HealthFrame"); @@ -158,7 +162,7 @@ namespace MWGui getWidget(mTriangleCounter, "TriangleCounter"); getWidget(mBatchCounter, "BatchCounter"); - LocalMapBase::init(mMinimap, mCompass, this); + LocalMapBase::init(mMinimap, mCompass); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); @@ -404,11 +408,6 @@ namespace MWGui mDrowningFlashTheta += dt * Ogre::Math::TWO_PI; } - void HUD::onResChange(int width, int height) - { - setCoord(0, 0, width, height); - } - void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) { const ESM::Spell* spell = @@ -434,10 +433,9 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get().find(spell->mEffects.mList.front().mEffectID); std::string icon = effect->mIcon; - int slashPos = icon.find("\\"); + int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); - icon = std::string("icons\\") + icon; - Widgets::fixTexturePath(icon); + icon = Misc::ResourceHelpers::correctIconPath(icon); mSpellImage->setItem(MWWorld::Ptr()); mSpellImage->setIcon(icon); @@ -474,6 +472,7 @@ namespace MWGui mWeaponSpellBox->setVisible(true); } + mWeapBox->clearUserStrings(); mWeapBox->setUserString("ToolTipType", "ItemPtr"); mWeapBox->setUserData(item); @@ -518,12 +517,14 @@ namespace MWGui MWWorld::Ptr player = world->getPlayerPtr(); mWeapImage->setItem(MWWorld::Ptr()); - if (player.getClass().getNpcStats(player).isWerewolf()) - mWeapImage->setIcon("icons\\k\\tx_werewolf_hand.dds"); - else - mWeapImage->setIcon("icons\\k\\stealth_handtohand.dds"); + std::string icon = (player.getClass().getNpcStats(player).isWerewolf()) ? "icons\\k\\tx_werewolf_hand.dds" : "icons\\k\\stealth_handtohand.dds"; + mWeapImage->setIcon(icon); mWeapBox->clearUserStrings(); + mWeapBox->setUserString("ToolTipType", "Layout"); + mWeapBox->setUserString("ToolTipLayout", "HandToHandToolTip"); + mWeapBox->setUserString("Caption_HandToHandText", itemName); + mWeapBox->setUserString("ImageTexture_HandToHandImage", icon); } void HUD::setCrosshairVisible(bool visible) @@ -609,7 +610,10 @@ namespace MWGui void HUD::updateEnemyHealthBar() { - MWMechanics::CreatureStats& stats = mEnemy.getClass().getCreatureStats(mEnemy); + MWWorld::Ptr enemy = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mEnemyActorId); + if (enemy.isEmpty()) + return; + MWMechanics::CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) @@ -620,7 +624,7 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, true); - if (!mEnemy.isEmpty() && mEnemyHealth->getVisible()) + if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) { updateEnemyHealthBar(); } @@ -634,7 +638,7 @@ namespace MWGui void HUD::setEnemy(const MWWorld::Ptr &enemy) { - mEnemy = enemy; + mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); mEnemyHealthTimer = 5; if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); @@ -644,7 +648,7 @@ namespace MWGui void HUD::resetEnemy() { - mEnemy = MWWorld::Ptr(); + mEnemyActorId = -1; mEnemyHealthTimer = -1; } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index f451ea4d2d..b41a374b6e 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -15,7 +15,7 @@ namespace MWGui class HUD : public OEngine::GUI::Layout, public LocalMapBase { public: - HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop); + HUD(CustomMarkerCollection& customMarkers, int fpsLevel, DragAndDrop* dragAndDrop); virtual ~HUD(); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setFPS(float fps); @@ -47,7 +47,6 @@ namespace MWGui void setCrosshairVisible(bool visible); void onFrame(float dt); - void onResChange(int width, int height); void setCellName(const std::string& cellName); @@ -104,7 +103,7 @@ namespace MWGui SpellIcons* mSpellIcons; - MWWorld::Ptr mEnemy; + int mEnemyActorId; float mEnemyHealthTimer; bool mIsDrowning; diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index ad1a4e9537..f45881770a 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -61,6 +61,10 @@ void InventoryItemModel::removeItem (const ItemStack& item, size_t count) MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) { + // Can't move conjured items: This is a general fix that also takes care of issues with taking conjured items via the 'Take All' button. + if (item.mFlags & ItemStack::Flag_Bound) + return MWWorld::Ptr(); + bool setNewOwner = false; // Are you dead? Then you wont need that anymore diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index abea68185d..2786f6bd3d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -37,11 +37,12 @@ namespace MWGui , mLastYSize(0) , mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())) , mPreviewDirty(true) + , mPreviewResize(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) , mGuiMode(GM_Inventory) { - static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); getWidget(mAvatar, "Avatar"); getWidget(mAvatarImage, "AvatarImage"); @@ -91,8 +92,18 @@ namespace MWGui mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel(mSortModel); + + mPreview.reset(NULL); + mAvatarImage->setImageTexture(""); + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("CharacterPreview"); + if (tex) + MyGUI::RenderManager::getInstance().destroyTexture(tex); + mPreview.reset(new MWRender::InventoryPreview(mPtr)); mPreview->setup(); + + mPreviewDirty = true; + mPreviewResize = true; } void InventoryWindow::setGuiMode(GuiMode mode) @@ -125,7 +136,7 @@ namespace MWGui Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) - mPreviewDirty = true; + mPreviewResize = true; mMainWidget->setPosition(pos); mMainWidget->setSize(size); @@ -172,21 +183,20 @@ namespace MWGui MWWorld::Ptr object = item.mBase; int count = item.mCount; - // Bound items may not be moved - if (item.mBase.getCellRef().getRefId().size() > 6 - && item.mBase.getCellRef().getRefId().substr(0,6) == "bound_") - { - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); - return; - } - bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; if (mTrading) { + // Can't give conjured items to a merchant + if (item.mFlags & ItemStack::Flag_Bound) + { + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog9}"); + return; + } + // check if merchant accepts item int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices(); if (!object.getClass().canSell(object, services)) @@ -218,9 +228,6 @@ namespace MWGui else dragItem (NULL, count); } - - // item might have been unequipped - notifyContentChanged(); } void InventoryWindow::ensureSelectedItemUnequipped() @@ -258,6 +265,7 @@ namespace MWGui { ensureSelectedItemUnequipped(); mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count); + notifyContentChanged(); } void InventoryWindow::sellItem(MyGUI::Widget* sender, int count) @@ -270,17 +278,18 @@ namespace MWGui if (item.mType == ItemStack::Type_Barter) { // this was an item borrowed to us by the merchant - MWBase::Environment::get().getWindowManager()->getTradeWindow()->returnItem(mSelectedItem, count); mTradeModel->returnItemBorrowedToUs(mSelectedItem, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->returnItem(mSelectedItem, count); } else { // borrow item to the merchant - MWBase::Environment::get().getWindowManager()->getTradeWindow()->borrowItem(mSelectedItem, count); mTradeModel->borrowItemFromUs(mSelectedItem, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->borrowItem(mSelectedItem, count); } mItemView->update(); + notifyContentChanged(); } void InventoryWindow::updateItemView() @@ -333,7 +342,7 @@ namespace MWGui { mLastXSize = mMainWidget->getSize().width; mLastYSize = mMainWidget->getSize().height; - mPreviewDirty = true; + mPreviewResize = true; } } @@ -358,7 +367,7 @@ namespace MWGui mItemView->update(); - static_cast(_sender)->setStateSelected(true); + _sender->castType()->setStateSelected(true); } void InventoryWindow::onPinToggled() @@ -366,6 +375,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned); } + void InventoryWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); + } + void InventoryWindow::useItem(const MWWorld::Ptr &ptr) { const std::string& script = ptr.getClass().getScript(ptr); @@ -403,9 +418,13 @@ namespace MWGui else mSkippedToEquip = ptr; - mItemView->update(); + if (isVisible()) + { + mItemView->update(); - notifyContentChanged(); + notifyContentChanged(); + } + // else: will be updated in open() } void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender) @@ -473,6 +492,7 @@ namespace MWGui float capacity = player.getClass().getCapacity(player); float encumbrance = player.getClass().getEncumbrance(player); + mTradeModel->adjustEncumbrance(encumbrance); mEncumbranceBar->setValue(encumbrance, capacity); } @@ -491,16 +511,23 @@ namespace MWGui void InventoryWindow::doRenderUpdate () { - if (mPreviewDirty) + mPreview->onFrame(); + if (mPreviewResize) { - mPreviewDirty = false; + mPreviewResize = false; MyGUI::IntSize size = mAvatarImage->getSize(); - - mPreview->update (size.width, size.height); + mPreview->resize(size.width, size.height); mAvatarImage->setImageTexture("CharacterPreview"); mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); + } + if (mPreviewDirty) + { + mPreviewDirty = false; + mPreview->update (); + + mAvatarImage->setImageTexture("CharacterPreview"); mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); @@ -515,6 +542,9 @@ namespace MWGui if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects( + MWBase::Environment::get().getWorld()->getPlayerPtr()); + mPreviewDirty = true; } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index ae7af5719b..6fd6ece4a9 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -53,6 +53,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; bool mPreviewDirty; + bool mPreviewResize; int mSelectedItem; MWWorld::Ptr mPtr; @@ -98,6 +99,7 @@ namespace MWGui void onFilterChanged(MyGUI::Widget* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); + void onTitleDoubleClicked(); void updateEncumbranceBar(); void notifyContentChanged(); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 59b54e171d..f5135ce80e 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -2,6 +2,11 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/store.hpp" +#include "../mwworld/esmstore.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" namespace MWGui { @@ -15,6 +20,40 @@ namespace MWGui { if (base.getClass().getEnchantment(base) != "") mFlags |= Flag_Enchanted; + + static std::set boundItemIDCache; + + // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's for some reason + if (boundItemIDCache.empty()) + { + // Build a list of known bound item ID's + const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); + + for (MWWorld::Store::iterator currentIteration = gameSettings.begin(); currentIteration != gameSettings.end(); ++currentIteration) + { + const ESM::GameSetting ¤tSetting = *currentIteration; + std::string currentGMSTID = currentSetting.mId; + Misc::StringUtils::toLower(currentGMSTID); + + // Don't bother checking this GMST if it's not a sMagicBound* one. + const std::string& toFind = "smagicbound"; + if (currentGMSTID.compare(0, toFind.length(), toFind) != 0) + continue; + + // All sMagicBound* GMST's should be of type string + std::string currentGMSTValue = currentSetting.getString(); + Misc::StringUtils::toLower(currentGMSTValue); + + boundItemIDCache.insert(currentGMSTValue); + } + } + + // Perform bound item check and assign the Flag_Bound bit if it passes + std::string tempItemID = base.getCellRef().getRefId(); + Misc::StringUtils::toLower(tempItemID); + + if (boundItemIDCache.count(tempItemID) != 0) + mFlags |= Flag_Bound; } ItemStack::ItemStack() diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 21c5477d0b..c1b243a761 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -26,7 +26,8 @@ namespace MWGui enum Flags { - Flag_Enchanted = (1<<0) + Flag_Enchanted = (1<<0), + Flag_Bound = (1<<1) }; int mFlags; diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index b86f610341..a51ada2754 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -17,16 +17,6 @@ namespace MWGui { -std::string ItemView::getCountString(int count) -{ - if (count == 1) - return ""; - if (count > 9999) - return boost::lexical_cast(int(count/1000.f)) + "k"; - else - return boost::lexical_cast(count); -} - ItemView::ItemView() : mModel(NULL) , mScrollView(NULL) @@ -56,6 +46,43 @@ void ItemView::initialiseOverride() mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); } +void ItemView::layoutWidgets() +{ + if (!mScrollView->getChildCount()) + return; + + int x = 0; + int y = 0; + int maxHeight = mScrollView->getSize().height - 58; + + MyGUI::Widget* dragArea = mScrollView->getChildAt(0); + + for (unsigned int i=0; igetChildCount(); ++i) + { + MyGUI::Widget* w = dragArea->getChildAt(i); + + w->setPosition(x, y); + + y += 42; + if (y > maxHeight) + { + x += 42; + y = 0; + } + } + x += 42; + + MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setVisibleHScroll(false); + mScrollView->setCanvasSize(size); + mScrollView->setVisibleVScroll(true); + mScrollView->setVisibleHScroll(true); + dragArea->setSize(size); +} + void ItemView::update() { while (mScrollView->getChildCount()) @@ -64,10 +91,6 @@ void ItemView::update() if (!mModel) return; - int x = 0; - int y = 0; - int maxHeight = mScrollView->getSize().height - 58; - mModel->update(); MyGUI::Widget* dragArea = mScrollView->createWidget("",0,0,mScrollView->getWidth(),mScrollView->getHeight(), @@ -80,9 +103,8 @@ void ItemView::update() { const ItemStack& item = mModel->getItem(i); - /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them ItemWidget* itemWidget = dragArea->createWidget("MW_ItemIcon", - MyGUI::IntCoord(x, y, 42, 42), MyGUI::Align::Default); + MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); itemWidget->setUserString("ToolTipType", "ItemModelIndex"); itemWidget->setUserData(std::make_pair(i, mModel)); ItemWidget::ItemState state = ItemWidget::None; @@ -91,32 +113,13 @@ void ItemView::update() if (item.mType == ItemStack::Type_Equipped) state = ItemWidget::Equip; itemWidget->setItem(item.mBase, state); + itemWidget->setCount(item.mCount); itemWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); - - // text widget that shows item count - // TODO: move to ItemWidget - MyGUI::TextBox* text = itemWidget->createWidget("SandBrightText", - MyGUI::IntCoord(5, 19, 32, 18), MyGUI::Align::Default, std::string("Label")); - text->setTextAlign(MyGUI::Align::Right); - text->setNeedMouseFocus(false); - text->setTextShadow(true); - text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(getCountString(item.mCount)); - - y += 42; - if (y > maxHeight) - { - x += 42; - y = 0; - } - } - x += 42; - MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); - mScrollView->setCanvasSize(size); - dragArea->setSize(size); + + layoutWidgets(); } void ItemView::onSelectedItem(MyGUI::Widget *sender) @@ -143,7 +146,7 @@ void ItemView::setSize(const MyGUI::IntSize &_value) bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setSize(_value); if (changed) - update(); + layoutWidgets(); } void ItemView::setSize(int _width, int _height) @@ -156,7 +159,7 @@ void ItemView::setCoord(const MyGUI::IntCoord &_value) bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setCoord(_value); if (changed) - update(); + layoutWidgets(); } void ItemView::setCoord(int _left, int _top, int _width, int _height) diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index 74bc66ea0d..1a5bd79a3d 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -30,11 +30,11 @@ namespace MWGui void update(); - static std::string getCountString(int count); - private: virtual void initialiseOverride(); + void layoutWidgets(); + virtual void setSize(const MyGUI::IntSize& _value); virtual void setCoord(const MyGUI::IntCoord& _value); void setSize(int _width, int _height); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 7c79f8027c..430b57ef39 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -2,14 +2,34 @@ #include #include +#include + +#include + +#include #include "../mwworld/class.hpp" +namespace +{ + std::string getCountString(int count) + { + if (count == 1) + return ""; + if (count > 9999) + return boost::lexical_cast(int(count/1000.f)) + "k"; + else + return boost::lexical_cast(count); + } +} + namespace MWGui { ItemWidget::ItemWidget() : mItem(NULL) + , mFrame(NULL) + , mText(NULL) { } @@ -27,32 +47,28 @@ namespace MWGui assignWidget(mFrame, "Frame"); if (mFrame) mFrame->setNeedMouseFocus(false); + assignWidget(mText, "Text"); + if (mText) + mText->setNeedMouseFocus(false); Base::initialiseOverride(); } + void ItemWidget::setCount(int count) + { + if (!mText) + return; + mText->setCaption(getCountString(count)); + } + void ItemWidget::setIcon(const std::string &icon) { - // HACK HACK HACK: Don't setImageTexture if it hasn't changed. - // There is a leak in MyGUI for each setImageTexture on the same widget. - // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 - if (mCurrentItemTexture == icon) - return; - - mCurrentItemTexture = icon; if (mItem) mItem->setImageTexture(icon); } void ItemWidget::setFrame(const std::string &frame, const MyGUI::IntCoord &coord) { - // HACK HACK HACK: Don't setImageTexture if it hasn't changed. - // There is a leak in MyGUI for each setImageTexture on the same widget. - // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 - if (mCurrentFrameTexture == frame) - return; - - mCurrentFrameTexture = frame; if (mFrame) { mFrame->setImageTexture(frame); @@ -63,15 +79,7 @@ namespace MWGui void ItemWidget::setIcon(const MWWorld::Ptr &ptr) { - // image - std::string path = std::string("icons\\"); - path += ptr.getClass().getInventoryIcon(ptr); - - std::string::size_type pos = path.rfind("."); - if(pos != std::string::npos) - path.erase(pos); - path.append(".dds"); - setIcon(path); + setIcon(Misc::ResourceHelpers::correctIconPath(ptr.getClass().getInventoryIcon(ptr))); } @@ -83,21 +91,8 @@ namespace MWGui if (ptr.isEmpty()) { if (mFrame) - { - // HACK HACK HACK: Don't setImageTexture if it hasn't changed. - // There is a leak in MyGUI for each setImageTexture on the same widget. - // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 - if (!mCurrentFrameTexture.empty()) - { - mFrame->setImageTexture(""); - mCurrentFrameTexture = ""; - } - } - if (!mCurrentItemTexture.empty()) - { - mCurrentItemTexture = ""; - mItem->setImageTexture(""); - } + mFrame->setImageTexture(""); + mItem->setImageTexture(""); return; } diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 5cdf712126..e7a9022393 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -29,6 +29,9 @@ namespace MWGui Magic }; + /// Set count to be displayed in a textbox over the item + void setCount(int count); + /// \a ptr may be empty void setItem (const MWWorld::Ptr& ptr, ItemState state = None); @@ -42,9 +45,7 @@ namespace MWGui MyGUI::ImageBox* mItem; MyGUI::ImageBox* mFrame; - - std::string mCurrentItemTexture; - std::string mCurrentFrameTexture; + MyGUI::TextBox* mText; }; } diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 7ffe9e6a42..34a8525628 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -1,10 +1,13 @@ #include "journalbooks.hpp" +#include + namespace { - const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f); - const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f); - const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f); + MyGUI::Colour getTextColour (const std::string& type) + { + return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); + } struct AddContent { @@ -28,6 +31,10 @@ namespace { MWGui::BookTypesetter::Style* style = mBodyStyle; + static const MyGUI::Colour linkHot (getTextColour("journal_link_over")); + static const MyGUI::Colour linkNormal (getTextColour("journal_link")); + static const MyGUI::Colour linkActive (getTextColour("journal_link_pressed")); + if (topicId) style = mTypesetter->createHotStyle (mBodyStyle, linkNormal, linkHot, linkActive, topicId); @@ -132,22 +139,6 @@ namespace mTypesetter->sectionBreak (10); } }; - - struct AddTopicLink : AddContent - { - AddTopicLink (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : - AddContent (typesetter, style) - { - } - - void operator () (MWGui::JournalViewModel::TopicId topicId, MWGui::JournalViewModel::Utf8Span name) - { - MWGui::BookTypesetter::Style* link = mTypesetter->createHotStyle (mBodyStyle, MyGUI::Colour::Black, linkHot, linkActive, topicId); - - mTypesetter->write (link, name); - mTypesetter->lineBreak (); - } - }; } namespace MWGui @@ -242,7 +233,11 @@ book JournalBooks::createTopicIndexBook () sprintf (buffer, "( %c )", ch); - BookTypesetter::Style* style = typesetter->createHotStyle (body, MyGUI::Colour::Black, linkHot, linkActive, ch); + MyGUI::Colour linkHot (getTextColour("journal_topic_over")); + MyGUI::Colour linkActive (getTextColour("journal_topic_pressed")); + MyGUI::Colour linkNormal (getTextColour("journal_topic")); + + BookTypesetter::Style* style = typesetter->createHotStyle (body, linkNormal, linkHot, linkActive, ch); if (i == 13) typesetter->sectionBreak (); diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index b9ddb7daa9..45a1858d20 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -113,10 +113,10 @@ struct JournalViewModelImpl : JournalViewModel utf8text = getText (); - size_t pos_begin, pos_end; + size_t pos_end = 0; for(;;) { - pos_begin = utf8text.find('@'); + size_t pos_begin = utf8text.find('@'); if (pos_begin != std::string::npos) pos_end = utf8text.find('#', pos_begin); @@ -223,15 +223,6 @@ struct JournalViewModelImpl : JournalViewModel } } - void visitQuestName (QuestId questId, boost::function visitor) const - { - MWDialogue::Quest const * quest = reinterpret_cast (questId); - - std::string name = quest->getName (); - - visitor (toUtf8Span (name)); - } - template struct JournalEntryImpl : BaseEntry { @@ -259,7 +250,7 @@ struct JournalViewModelImpl : JournalViewModel os << itr->mDayOfMonth << ' ' << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth) - << " (" << dayStr << " " << (itr->mDay + 1) << ')'; + << " (" << dayStr << " " << (itr->mDay) << ')'; timestamp_buffer = os.str (); } @@ -301,11 +292,6 @@ struct JournalViewModelImpl : JournalViewModel } } - void visitTopics (boost::function visitor) const - { - throw std::runtime_error ("not implemented"); - } - void visitTopicName (TopicId topicId, boost::function visitor) const { MWDialogue::Topic const & topic = * reinterpret_cast (topicId); diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 4b827d1aba..5f0189b590 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -67,9 +67,6 @@ namespace MWGui /// returns true if their are no journal entries to display virtual bool isEmpty () const = 0; - /// provides access to the name of the quest with the specified identifier - virtual void visitQuestName (TopicId topicId, boost::function visitor) const = 0; - /// walks the active and optionally completed, quests providing the name virtual void visitQuestNames (bool active_only, boost::function visitor) const = 0; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index fa27b4ef06..4139a7d24f 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -4,7 +4,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/journal.hpp" -#include "list.hpp" #include #include @@ -15,12 +14,13 @@ #include #include "boost/lexical_cast.hpp" +#include +#include + #include "bookpage.hpp" #include "windowbase.hpp" -#include "imagebutton.hpp" #include "journalviewmodel.hpp" #include "journalbooks.hpp" -#include "list.hpp" namespace { @@ -82,7 +82,7 @@ namespace void adviseButtonClick (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender)) { - getWidget (name) -> + getWidget (name) -> eventMouseButtonClick += newDelegate(this, Handler); } @@ -110,10 +110,10 @@ namespace adviseButtonClick (ShowAllBTN, &JournalWindowImpl::notifyShowAll ); adviseButtonClick (ShowActiveBTN, &JournalWindowImpl::notifyShowActive); - MWGui::Widgets::MWList* list = getWidget(QuestsList); + Gui::MWList* list = getWidget(QuestsList); list->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyQuestClicked); - MWGui::Widgets::MWList* topicsList = getWidget(TopicsList); + Gui::MWList* topicsList = getWidget(TopicsList); topicsList->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyTopicSelected); { @@ -123,6 +123,9 @@ namespace getPage (LeftBookPage)->adviseLinkClicked (callback); getPage (RightBookPage)->adviseLinkClicked (callback); + + getPage (LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); + getPage (RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); } { @@ -143,12 +146,12 @@ namespace adjustButton(ShowActiveBTN, true); adjustButton(JournalBTN); - MWGui::ImageButton* optionsButton = getWidget(OptionsBTN); + Gui::ImageButton* optionsButton = getWidget(OptionsBTN); if (optionsButton->getWidth() == 0) { // If tribunal is not installed (-> no options button), we still want the Topics button available, // so place it where the options button would have been - MWGui::ImageButton* topicsButton = getWidget(TopicsBTN); + Gui::ImageButton* topicsButton = getWidget(TopicsBTN); topicsButton->detachFromWidget(); topicsButton->attachToWidget(optionsButton->getParent()); topicsButton->setPosition(optionsButton->getPosition()); @@ -156,7 +159,7 @@ namespace topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); } - MWGui::ImageButton* nextButton = getWidget(NextPageBTN); + Gui::ImageButton* nextButton = getWidget(NextPageBTN); if (nextButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge @@ -179,7 +182,7 @@ namespace void adjustButton (char const * name, bool optional = false) { - MWGui::ImageButton* button = getWidget(name); + Gui::ImageButton* button = getWidget(name); MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(!optional); button->setSize(button->getRequestedSize(!optional)); @@ -198,10 +201,6 @@ namespace setBookMode (); - /// \todo Wiping the whole book layout each time the journal is opened is probably too costly for a large journal (eg 300+ pages). - /// There should be a way to keep the existing layout and append new entries to the end of it. - /// However, that still leaves the problem of having to add links to previously unknown, but now known topics, so - /// we maybe need to find another way to speed things up. Book journalBook; if (mModel->isEmpty ()) journalBook = createEmptyJournalBook (); @@ -394,22 +393,13 @@ namespace popBook (); } - void showList (char const * listId, char const * pageId, Book book) - { - std::pair size = book->getSize (); - - getPage (pageId)->showPage (book, 0); - - getWidget (listId)->setCanvasSize (size.first, size.second); - } - void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character) { setVisible (LeftTopicIndex, false); setVisible (RightTopicIndex, false); setVisible (TopicsList, true); - MWGui::Widgets::MWList* list = getWidget(TopicsList); + Gui::MWList* list = getWidget(TopicsList); list->clear(); AddNamesToList add(list); @@ -432,9 +422,9 @@ namespace struct AddNamesToList { - AddNamesToList(MWGui::Widgets::MWList* list) : mList(list) {} + AddNamesToList(Gui::MWList* list) : mList(list) {} - MWGui::Widgets::MWList* mList; + Gui::MWList* mList; void operator () (const std::string& name) { mList->addItem(name); @@ -452,7 +442,7 @@ namespace setVisible (ShowAllBTN, !mAllQuests); setVisible (ShowActiveBTN, mAllQuests); - MWGui::Widgets::MWList* list = getWidget(QuestsList); + Gui::MWList* list = getWidget(QuestsList); list->clear(); AddNamesToList add(list); @@ -485,6 +475,14 @@ namespace MWBase::Environment::get().getWindowManager ()->popGuiMode (); } + void notifyMouseWheel(MyGUI::Widget* sender, int rel) + { + if (rel < 0) + notifyNextPage(sender); + else + notifyPrevPage(sender); + } + void notifyNextPage(MyGUI::Widget* _sender) { if (!mStates.empty ()) @@ -506,7 +504,7 @@ namespace { unsigned int & page = mStates.top ().mPage; - if(page > 0) + if(page >= 2) { page -= 2; updateShowingPages (); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index bf6181d76c..56a2319470 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -5,6 +5,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" @@ -26,6 +27,7 @@ namespace MWGui getWidget(mLevelText, "LevelText"); getWidget(mLevelDescription, "LevelDescription"); getWidget(mCoinBox, "Coins"); + getWidget(mAssignWidget, "AssignWidget"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onOkButtonClicked); @@ -83,13 +85,19 @@ namespace MWGui { const int coinSpacing = 10; int curX = mCoinBox->getWidth()/2 - (coinSpacing*(mCoinCount - 1) + 16*mCoinCount)/2; - for (unsigned int i=0; idetachFromWidget(); image->attachToWidget(mCoinBox); - image->setCoord(MyGUI::IntCoord(curX,0,16,16)); - curX += 16+coinSpacing; + if (i < mCoinCount) + { + mCoins[i]->setVisible(true); + image->setCoord(MyGUI::IntCoord(curX,0,16,16)); + curX += 16+coinSpacing; + } + else + mCoins[i]->setVisible(false); } } @@ -100,13 +108,13 @@ namespace MWGui { MyGUI::ImageBox* image = mCoins[i]; image->detachFromWidget(); - image->attachToWidget(mMainWidget); + image->attachToWidget(mAssignWidget); int attribute = mSpentAttributes[i]; int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 20; - MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mMainWidget->getAbsolutePosition() - MyGUI::IntPoint(22+xdiff,0); + MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mAssignWidget->getAbsolutePosition() - MyGUI::IntPoint(22+xdiff,0); pos.top += (mAttributes[attribute]->getHeight() - image->getHeight())/2; image->setPosition(pos); } @@ -127,22 +135,23 @@ namespace MWGui const ESM::Class *cls = world->getStore().get().find(playerData->mClass); - // Vanilla uses thief.dds for custom classes. A player with a custom class - // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as - // "$dynamic0". This check should resolve bug #1260. - // Choosing Stealth specialization and Speed/Agility as attributes. if(world->getStore().get().isDynamic(cls->mId)) { + // Vanilla uses thief.dds for custom classes. + // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. MWWorld::SharedIterator it = world->getStore().get().begin(); - for(; it != world->getStore().get().end(); it++) + for(; it != world->getStore().get().end(); ++it) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) break; } - mClassImage->setImageTexture("textures\\levelup\\" + it->mId + ".dds"); + if (it == world->getStore().get().end()) + it = world->getStore().get().begin(); + if (it != world->getStore().get().end()) + cls = &*it; } - else - mClassImage->setImageTexture("textures\\levelup\\" + cls->mId + ".dds"); + + mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); @@ -177,20 +186,15 @@ namespace MWGui mCoinCount = std::min(sMaxCoins, availableAttributes); - for (unsigned int i = 0; i < sMaxCoins; i++) - { - if (i < mCoinCount) - mCoins[i]->attachToWidget(mCoinBox); - else - mCoins[i]->detachFromWidget(); - } - mSpentAttributes.clear(); resetCoins(); setAttributeValues(); center(); + + // Play LevelUp Music + MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Triumph.mp3"); } void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp index 5ca9c8c758..e5edbd53c8 100644 --- a/apps/openmw/mwgui/levelupdialog.hpp +++ b/apps/openmw/mwgui/levelupdialog.hpp @@ -20,6 +20,7 @@ namespace MWGui MyGUI::EditBox* mLevelDescription; MyGUI::Widget* mCoinBox; + MyGUI::Widget* mAssignWidget; std::vector mAttributes; std::vector mAttributeValues; diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp deleted file mode 100644 index b0c514b9d3..0000000000 --- a/apps/openmw/mwgui/list.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include "list.hpp" - -#include -#include -#include -#include - -namespace MWGui -{ - - namespace Widgets - { - - MWList::MWList() : - mClient(0) - , mScrollView(0) - , mItemHeight(0) - { - } - - void MWList::initialiseOverride() - { - Base::initialiseOverride(); - - assignWidget(mClient, "Client"); - if (mClient == 0) - mClient = this; - - mScrollView = mClient->createWidgetReal( - "MW_ScrollView", MyGUI::FloatCoord(0.0, 0.0, 1.0, 1.0), - MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView"); - } - - void MWList::addItem(const std::string& name) - { - mItems.push_back(name); - } - - void MWList::addSeparator() - { - mItems.push_back(""); - } - - void MWList::adjustSize() - { - redraw(); - } - - void MWList::redraw(bool scrollbarShown) - { - const int _scrollBarWidth = 20; // fetch this from skin? - const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; - const int spacing = 3; - size_t viewPosition = -mScrollView->getViewOffset().top; - - while (mScrollView->getChildCount()) - { - MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); - } - - mItemHeight = 0; - int i=0; - for (std::vector::const_iterator it=mItems.begin(); - it!=mItems.end(); ++it) - { - if (*it != "") - { - if (mListItemSkin.empty()) - throw std::runtime_error("MWList needs a ListItemSkin property"); - MyGUI::Button* button = mScrollView->createWidget( - mListItemSkin, MyGUI::IntCoord(0, mItemHeight, mScrollView->getSize().width - scrollBarWidth - 2, 24), - MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + (*it)); - button->setCaption((*it)); - button->getSubWidgetText()->setWordWrap(true); - button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); - button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheel); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); - - int height = button->getTextSize().height; - button->setSize(MyGUI::IntSize(button->getSize().width, height)); - button->setUserData(i); - - mItemHeight += height + spacing; - } - else - { - MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", - MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth() - scrollBarWidth - 4, 18), - MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - separator->setNeedMouseFocus(false); - - mItemHeight += 18 + spacing; - } - ++i; - } - mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height)); - - if (!scrollbarShown && mItemHeight > mClient->getSize().height) - redraw(true); - - size_t viewRange = mScrollView->getCanvasSize().height; - if(viewPosition > viewRange) - viewPosition = viewRange; - mScrollView->setViewOffset(MyGUI::IntPoint(0, viewPosition * -1)); - } - - void MWList::setPropertyOverride(const std::string &_key, const std::string &_value) - { - if (_key == "ListItemSkin") - mListItemSkin = _value; - else - Base::setPropertyOverride(_key, _value); - } - - bool MWList::hasItem(const std::string& name) - { - return (std::find(mItems.begin(), mItems.end(), name) != mItems.end()); - } - - unsigned int MWList::getItemCount() - { - return mItems.size(); - } - - std::string MWList::getItemNameAt(unsigned int at) - { - assert(at < mItems.size() && "List item out of bounds"); - return mItems[at]; - } - - void MWList::removeItem(const std::string& name) - { - assert( std::find(mItems.begin(), mItems.end(), name) != mItems.end() ); - mItems.erase( std::find(mItems.begin(), mItems.end(), name) ); - } - - void MWList::clear() - { - mItems.clear(); - } - - void MWList::onMouseWheel(MyGUI::Widget* _sender, int _rel) - { - //NB view offset is negative - if (mScrollView->getViewOffset().top + _rel*0.3 > 0) - mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); - else - mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3)); - } - - void MWList::onItemSelected(MyGUI::Widget* _sender) - { - std::string name = static_cast(_sender)->getCaption(); - int id = *_sender->getUserData(); - eventItemSelected(name, id); - eventWidgetSelected(_sender); - } - - MyGUI::Widget* MWList::getItemWidget(const std::string& name) - { - return mScrollView->findWidget (getName() + "_item_" + name); - } - - } -} diff --git a/apps/openmw/mwgui/list.hpp b/apps/openmw/mwgui/list.hpp deleted file mode 100644 index acf078a2c2..0000000000 --- a/apps/openmw/mwgui/list.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef MWGUI_LIST_HPP -#define MWGUI_LIST_HPP - -#include - -namespace MWGui -{ - namespace Widgets - { - /** - * \brief a very simple list widget that supports word-wrapping entries - * \note if the width or height of the list changes, you must call adjustSize() method - */ - class MWList : public MyGUI::Widget - { - MYGUI_RTTI_DERIVED(MWList) - public: - MWList(); - - typedef MyGUI::delegates::CMultiDelegate2 EventHandle_StringInt; - typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Widget; - - /** - * Event: Item selected with the mouse. - * signature: void method(std::string itemName, int index) - */ - EventHandle_StringInt eventItemSelected; - - /** - * Event: Item selected with the mouse. - * signature: void method(MyGUI::Widget* sender) - */ - EventHandle_Widget eventWidgetSelected; - - - /** - * Call after the size of the list changed, or items were inserted/removed - */ - void adjustSize(); - - void addItem(const std::string& name); - void addSeparator(); ///< add a seperator between the current and the next item. - void removeItem(const std::string& name); - bool hasItem(const std::string& name); - unsigned int getItemCount(); - std::string getItemNameAt(unsigned int at); ///< \attention if there are separators, this method will return "" at the place where the separator is - void clear(); - - MyGUI::Widget* getItemWidget(const std::string& name); - ///< get widget for an item name, useful to set up tooltip - - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - - protected: - void initialiseOverride(); - - void redraw(bool scrollbarShown = false); - - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void onItemSelected(MyGUI::Widget* _sender); - - private: - MyGUI::ScrollView* mScrollView; - MyGUI::Widget* mClient; - std::string mListItemSkin; - - std::vector mItems; - - int mItemHeight; // height of all items - }; - } -} - -#endif diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index f6d6b81355..a446266d9b 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -30,6 +30,8 @@ namespace MWGui , mProgress(0) , mVSyncWasEnabled(false) { + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); + getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); @@ -56,13 +58,6 @@ namespace MWGui mBackgroundImage->setVisible(visible); } - void LoadingScreen::onResChange(int w, int h) - { - setCoord(0,0,w,h); - - mBackgroundImage->setCoord(MyGUI::IntCoord(0,0,w,h)); - } - void LoadingScreen::loadingOn() { // Early-out if already on @@ -71,11 +66,8 @@ namespace MWGui // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. // Threaded loading would be even better, of course - especially because some drivers force VSync to on and we can't change it. - // In Ogre 1.8, the swapBuffers argument is useless and setVSyncEnabled is bugged with GLX, nothing we can do :/ mVSyncWasEnabled = mWindow->isVSyncEnabled(); - #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) mWindow->setVSyncEnabled(false); - #endif bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); @@ -118,10 +110,7 @@ namespace MWGui void LoadingScreen::loadingOff() { // Re-enable vsync now. - // In Ogre 1.8, the swapBuffers argument is useless and setVSyncEnabled is bugged with GLX, nothing we can do :/ - #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) mWindow->setVSyncEnabled(mVSyncWasEnabled); - #endif setVisible(false); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index f198d625d7..310a6df3c2 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -35,8 +35,6 @@ namespace MWGui void setLoadingProgress (const std::string& stage, int depth, int current, int total); void loadingDone(); - void onResChange(int w, int h); - void updateWindow(Ogre::RenderWindow* rw) { mWindow = rw; } private: diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index ca7e877cce..81b0825684 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -4,6 +4,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -16,7 +18,6 @@ #include "savegamedialog.hpp" #include "confirmationdialog.hpp" -#include "imagebutton.hpp" #include "backgroundimage.hpp" #include "videowidget.hpp" @@ -92,7 +93,6 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); if (name == "return") { - MWBase::Environment::get().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu); } else if (name == "options") @@ -250,7 +250,7 @@ namespace MWGui { if (mButtons.find(*it) == mButtons.end()) { - MWGui::ImageButton* button = mButtonBox->createWidget + Gui::ImageButton* button = mButtonBox->createWidget ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); button->setProperty("ImageHighlighted", "textures\\menu_" + *it + "_over.dds"); button->setProperty("ImageNormal", "textures\\menu_" + *it + ".dds"); @@ -263,7 +263,7 @@ namespace MWGui // Start by hiding all buttons int maxwidth = 0; - for (std::map::iterator it = mButtons.begin(); it != mButtons.end(); ++it) + for (std::map::iterator it = mButtons.begin(); it != mButtons.end(); ++it) { it->second->setVisible(false); MyGUI::IntSize requested = it->second->getRequestedSize(); @@ -275,7 +275,7 @@ namespace MWGui for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) { assert(mButtons.find(*it) != mButtons.end()); - MWGui::ImageButton* button = mButtons[*it]; + Gui::ImageButton* button = mButtons[*it]; button->setVisible(true); MyGUI::IntSize requested = button->getRequestedSize(); diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index ccd8df4b0b..cd2050d0ff 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -3,10 +3,14 @@ #include +namespace Gui +{ + class ImageButton; +} + namespace MWGui { - class ImageButton; class BackgroundImage; class SaveGameDialog; class VideoWidget; @@ -39,7 +43,7 @@ namespace MWGui MyGUI::ImageBox* mVideoBackground; VideoWidget* mVideo; // For animated main menus - std::map mButtons; + std::map mButtons; void onButtonClicked (MyGUI::Widget* sender); void onNewGameConfirmed(); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index af26456f27..37bc4d1e39 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -17,49 +17,173 @@ #include "../components/esm/globalmap.hpp" #include "widgets.hpp" +#include "confirmationdialog.hpp" + +namespace +{ + + const int widgetSize = 512; + + const int cellSize = 8192; + + enum LocalMapWidgetDepth + { + Local_CompassLayer = 0, + Local_MarkerAboveFogLayer = 1, + Local_FogLayer = 2, + Local_MarkerLayer = 3, + Local_MapLayer = 4 + }; + + enum GlobalMapWidgetDepth + { + Global_CompassLayer = 0, + Global_MarkerLayer = 1, + Global_ExploreOverlayLayer = 2, + Global_MapLayer = 3 + }; + + + /// @brief A widget that changes its color when hovered. + class MarkerWidget: public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(MarkerWidget) + + public: + void setNormalColour(const MyGUI::Colour& colour) + { + mNormalColour = colour; + setColour(colour); + } + + void setHoverColour(const MyGUI::Colour& colour) + { + mHoverColour = colour; + } + + private: + MyGUI::Colour mNormalColour; + MyGUI::Colour mHoverColour; + + void onMouseLostFocus(MyGUI::Widget* _new) + { + setColour(mNormalColour); + } + + void onMouseSetFocus(MyGUI::Widget* _old) + { + setColour(mHoverColour); + } + }; +} namespace MWGui { - LocalMapBase::LocalMapBase() + void CustomMarker::save(ESM::ESMWriter &esm) const + { + esm.writeHNT("POSX", mWorldX); + esm.writeHNT("POSY", mWorldY); + mCell.save(esm); + if (!mNote.empty()) + esm.writeHNString("NOTE", mNote); + } + + void CustomMarker::load(ESM::ESMReader &esm) + { + esm.getHNT(mWorldX, "POSX"); + esm.getHNT(mWorldY, "POSY"); + mCell.load(esm); + mNote = esm.getHNOString("NOTE"); + } + + // ------------------------------------------------------ + + void CustomMarkerCollection::addMarker(const CustomMarker &marker, bool triggerEvent) + { + mMarkers.push_back(marker); + if (triggerEvent) + eventMarkersChanged(); + } + + void CustomMarkerCollection::deleteMarker(const 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"); + + eventMarkersChanged(); + } + + void CustomMarkerCollection::updateMarker(const 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"); + + eventMarkersChanged(); + } + + void CustomMarkerCollection::clear() + { + mMarkers.clear(); + eventMarkersChanged(); + } + + std::vector::const_iterator CustomMarkerCollection::begin() const + { + return mMarkers.begin(); + } + + std::vector::const_iterator CustomMarkerCollection::end() const + { + return mMarkers.end(); + } + + size_t CustomMarkerCollection::size() const + { + return mMarkers.size(); + } + + // ------------------------------------------------------ + + LocalMapBase::LocalMapBase(CustomMarkerCollection &markers) : mCurX(0) , mCurY(0) , mInterior(false) , mFogOfWar(true) , mLocalMap(NULL) - , mMapDragAndDrop(false) , mPrefix() , mChanged(true) - , mLayout(NULL) , mLastPositionX(0.0f) , mLastPositionY(0.0f) , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) , mCompass(NULL) , mMarkerUpdateTimer(0.0f) + , mCustomMarkers(markers) { + mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } LocalMapBase::~LocalMapBase() { - // Clear our "lost focus" delegate for marker widgets first, otherwise it will - // fire when the widget is about to be destroyed and the mouse cursor is over it. - // At that point, other widgets may already be destroyed, so applyFogOfWar (which is called by the delegate) would crash. - for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) - (*it)->eventMouseLostFocus.clear(); - for (std::vector::iterator it = mMarkerWidgets.begin(); it != mMarkerWidgets.end(); ++it) - (*it)->eventMouseLostFocus.clear(); + mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass) { mLocalMap = widget; - mLayout = layout; - mMapDragAndDrop = mapDragAndDrop; mCompass = compass; + mCompass->setDepth(Local_CompassLayer); + mCompass->setNeedMouseFocus(false); + // create 3x3 map widgets, 512x512 each, holding a 1024x1024 texture each - const int widgetSize = 512; for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) @@ -67,16 +191,15 @@ namespace MWGui MyGUI::ImageBox* map = mLocalMap->createWidget("ImageBox", MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), MyGUI::Align::Top | MyGUI::Align::Left); + map->setDepth(Local_MapLayer); - MyGUI::ImageBox* fog = map->createWidget("ImageBox", - MyGUI::IntCoord(0, 0, widgetSize, widgetSize), + MyGUI::ImageBox* fog = mLocalMap->createWidget("ImageBox", + MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), MyGUI::Align::Top | MyGUI::Align::Left); + fog->setDepth(Local_FogLayer); - if (!mMapDragAndDrop) - { - map->setNeedMouseFocus(false); - fog->setNeedMouseFocus(false); - } + map->setNeedMouseFocus(false); + fog->setNeedMouseFocus(false); mMapWidgets.push_back(map); mFogWidgets.push_back(fog); @@ -112,19 +235,7 @@ namespace MWGui : ""); } } - notifyMapChanged (); - } - - void LocalMapBase::onMarkerFocused (MyGUI::Widget* w1, MyGUI::Widget* w2) - { - // Workaround to not make the marker visible if it's under fog of war - applyFogOfWar (); - } - - void LocalMapBase::onMarkerUnfocused (MyGUI::Widget* w1, MyGUI::Widget* w2) - { - // Workaround to not make the marker visible if it's under fog of war - applyFogOfWar (); + redraw(); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerPosition& markerPos) @@ -139,7 +250,6 @@ namespace MWGui { int cellX, cellY; MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); - const int cellSize = 8192; nX = (worldX - cellSize * cellX) / cellSize; // Image space is -Y up, cells are Y up nY = 1 - (worldY - cellSize * cellY) / cellSize; @@ -150,21 +260,21 @@ namespace MWGui markerPos.cellX = cellX; markerPos.cellY = cellY; - widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellDx) * 512, - nY * 512 - (cellDy-1) * 512); + widgetPos = MyGUI::IntPoint(nX * widgetSize + (1+cellDx) * widgetSize, + nY * widgetSize - (cellDy-1) * widgetSize); } else { int cellX, cellY; Ogre::Vector2 worldPos (worldX, worldY); - MWBase::Environment::get().getWorld ()->getInteriorMapPosition (worldPos, nX, nY, cellX, cellY); + MWBase::Environment::get().getWorld ()->worldToInteriorMapPosition (worldPos, nX, nY, cellX, cellY); markerPos.cellX = cellX; markerPos.cellY = cellY; // Image space is -Y up, cells are Y up - widgetPos = MyGUI::IntPoint(nX * 512 + (1+(cellX-mCurX)) * 512, - nY * 512 + (1-(cellY-mCurY)) * 512); + widgetPos = MyGUI::IntPoint(nX * widgetSize + (1+(cellX-mCurX)) * widgetSize, + nY * widgetSize + (1-(cellY-mCurY)) * widgetSize); } markerPos.nX = nX; @@ -172,6 +282,53 @@ namespace MWGui return widgetPos; } + void LocalMapBase::updateCustomMarkers() + { + for (std::vector::iterator it = mCustomMarkerWidgets.begin(); it != mCustomMarkerWidgets.end(); ++it) + MyGUI::Gui::getInstance().destroyWidget(*it); + mCustomMarkerWidgets.clear(); + + for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + { + const CustomMarker& marker = *it; + + if (marker.mCell.mPaged != !mInterior) + continue; + if (mInterior) + { + 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; + } + + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); + + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", + 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(1.0,0.3,0.3)); + markerWidget->setHoverColour(MyGUI::Colour(1.0,0.5,0.5)); + markerWidget->setUserData(marker); + markerWidget->setNeedMouseFocus(true); + customMarkerCreated(markerWidget); + mCustomMarkerWidgets.push_back(markerWidget); + } + redraw(); + } + void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) @@ -182,6 +339,9 @@ namespace MWGui mInterior = interior; mChanged = false; + applyFogOfWar(); + + // clear all previous door markers for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) MyGUI::Gui::getInstance().destroyWidget(*it); @@ -238,36 +398,36 @@ namespace MWGui widgetPos.top - 4, 8, 8); ++counter; - MyGUI::Button* markerWidget = mLocalMap->createWidget("ButtonImage", + MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", widgetCoord, MyGUI::Align::Default); - markerWidget->setImageResource("DoorMarker"); + markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); + markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); + markerWidget->setDepth(Local_MarkerLayer); + markerWidget->setNeedMouseFocus(true); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setUserString("Caption_TextOneLine", marker.name); - markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused); - markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused); // Used by tooltips to not show the tooltip if marker is hidden by fog of war markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); + doorMarkerCreated(markerWidget); mDoorMarkerWidgets.push_back(markerWidget); } - updateMarkers(); - - applyFogOfWar(); - - // set the compass texture again, because MyGUI determines sorting of ImageBox widgets - // based on the last setImageTexture call - std::string tex = "textures\\compass.dds"; - mCompass->setImageTexture(""); - mCompass->setImageTexture(tex); + updateMagicMarkers(); + updateCustomMarkers(); } + void LocalMapBase::redraw() + { + // Redraw children in proper order + mLocalMap->getParent()->_updateChilds(); + } void LocalMapBase::setPlayerPos(const float x, const float y) { - updateMarkers(); + updateMagicMarkers(); if (x == mLastPositionX && y == mLastPositionY) return; @@ -280,7 +440,7 @@ namespace MWGui MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); mLocalMap->setViewOffset(pos); - mCompass->setPosition(MyGUI::IntPoint(512+x*512-16, 512+y*512-16)); + mCompass->setPosition(MyGUI::IntPoint(widgetSize+x*widgetSize-16, widgetSize+y*widgetSize-16)); mLastPositionX = x; mLastPositionY = y; } @@ -342,11 +502,12 @@ namespace MWGui ++counter; MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", widgetCoord, MyGUI::Align::Default); + markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); markerWidget->setColour(markerColour); - mMarkerWidgets.push_back(markerWidget); + mMagicMarkerWidgets.push_back(markerWidget); } } @@ -357,16 +518,16 @@ namespace MWGui if (mMarkerUpdateTimer >= 0.25) { mMarkerUpdateTimer = 0; - updateMarkers(); + updateMagicMarkers(); } } - void LocalMapBase::updateMarkers() + void LocalMapBase::updateMagicMarkers() { // clear all previous markers - for (std::vector::iterator it = mMarkerWidgets.begin(); it != mMarkerWidgets.end(); ++it) + for (std::vector::iterator it = mMagicMarkerWidgets.begin(); it != mMagicMarkerWidgets.end(); ++it) MyGUI::Gui::getInstance().destroyWidget(*it); - mMarkerWidgets.clear(); + mMagicMarkerWidgets.clear(); addDetectionMarkers(MWBase::World::Detect_Creature); addDetectionMarkers(MWBase::World::Detect_Key); @@ -386,22 +547,42 @@ namespace MWGui 8, 8); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", widgetCoord, MyGUI::Align::Default); + markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); - mMarkerWidgets.push_back(markerWidget); + mMagicMarkerWidgets.push_back(markerWidget); } + + redraw(); } // ------------------------------------------------------------------------------------------ - MapWindow::MapWindow(DragAndDrop* drag, const std::string& cacheDir) + MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, const std::string& cacheDir) : WindowPinnableBase("openmw_map_window.layout") , NoDrop(drag, mMainWidget) + , LocalMapBase(customMarkers) , mGlobal(false) , mGlobalMap(0) , mGlobalMapRender(0) + , mEditNoteDialog() + , mEventBoxGlobal(NULL) + , mEventBoxLocal(NULL) + , mGlobalMapImage(NULL) + , mGlobalMapOverlay(NULL) { + static bool registered = false; + if (!registered) + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + registered = true; + } + + mEditNoteDialog.setVisible(false); + mEditNoteDialog.eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditOk); + mEditNoteDialog.eventDeleteClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDelete); + setCoord(500,0,320,300); getWidget(mLocalMap, "LocalMap"); @@ -411,6 +592,14 @@ namespace MWGui getWidget(mPlayerArrowLocal, "CompassLocal"); getWidget(mPlayerArrowGlobal, "CompassGlobal"); + mPlayerArrowGlobal->setDepth(Global_CompassLayer); + mPlayerArrowGlobal->setNeedMouseFocus(false); + mGlobalMapImage->setDepth(Global_MapLayer); + mGlobalMapOverlay->setDepth(Global_ExploreOverlayLayer); + + mLastScrollWindowCoordinates = mLocalMap->getCoord(); + mLocalMap->eventChangeCoord += MyGUI::newDelegate(this, &MapWindow::onChangeScrollWindowCoord); + mGlobalMap->setVisible (false); getWidget(mButton, "WorldButton"); @@ -420,11 +609,106 @@ namespace MWGui getWidget(mEventBoxGlobal, "EventBoxGlobal"); mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer); + getWidget(mEventBoxLocal, "EventBoxLocal"); mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal, this); + LocalMapBase::init(mLocalMap, mPlayerArrowLocal); + } + + void MapWindow::onNoteEditOk() + { + if (mEditNoteDialog.getDeleteButtonShown()) + mCustomMarkers.updateMarker(mEditingMarker, mEditNoteDialog.getText()); + else + { + mEditingMarker.mNote = mEditNoteDialog.getText(); + mCustomMarkers.addMarker(mEditingMarker); + } + + mEditNoteDialog.setVisible(false); + } + + void MapWindow::onNoteEditDelete() + { + ConfirmationDialog* confirmation = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + confirmation->open("#{sDeleteNote}", "#{sYes}", "#{sNo}"); + confirmation->eventCancelClicked.clear(); + confirmation->eventOkClicked.clear(); + confirmation->eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDeleteConfirm); + } + + void MapWindow::onNoteEditDeleteConfirm() + { + mCustomMarkers.deleteMarker(mEditingMarker); + + mEditNoteDialog.setVisible(false); + } + + void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget *sender) + { + mEditingMarker = *sender->getUserData(); + mEditNoteDialog.setText(mEditingMarker.mNote); + mEditNoteDialog.showDeleteButton(true); + mEditNoteDialog.setVisible(true); + } + + void MapWindow::onMapDoubleClicked(MyGUI::Widget *sender) + { + MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); + + MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); + int x = int(widgetPos.left/float(widgetSize))-1; + int y = (int(widgetPos.top/float(widgetSize))-1)*-1; + float nX = widgetPos.left/float(widgetSize) - int(widgetPos.left/float(widgetSize)); + float nY = widgetPos.top/float(widgetSize) - int(widgetPos.top/float(widgetSize)); + x += mCurX; + y += mCurY; + + Ogre::Vector2 worldPos; + if (mInterior) + { + worldPos = MWBase::Environment::get().getWorld()->interiorMapToWorldPosition(nX, nY, x, y); + } + else + { + worldPos.x = (x + nX) * cellSize; + worldPos.y = (y + (1.0-nY)) * cellSize; + } + + mEditingMarker.mWorldX = worldPos.x; + mEditingMarker.mWorldY = worldPos.y; + + mEditingMarker.mCell.mPaged = !mInterior; + if (mInterior) + mEditingMarker.mCell.mWorldspace = LocalMapBase::mPrefix; + else + { + mEditingMarker.mCell.mWorldspace = "sys::default"; + mEditingMarker.mCell.mIndex.mX = x; + mEditingMarker.mCell.mIndex.mY = y; + } + + mEditNoteDialog.setVisible(true); + mEditNoteDialog.showDeleteButton(false); + mEditNoteDialog.setText(""); + } + + void MapWindow::onChangeScrollWindowCoord(MyGUI::Widget* sender) + { + MyGUI::IntCoord currentCoordinates = sender->getCoord(); + + MyGUI::IntPoint currentViewPortCenter = MyGUI::IntPoint(currentCoordinates.width / 2, currentCoordinates.height / 2); + MyGUI::IntPoint lastViewPortCenter = MyGUI::IntPoint(mLastScrollWindowCoordinates.width / 2, mLastScrollWindowCoordinates.height / 2); + MyGUI::IntPoint viewPortCenterDiff = currentViewPortCenter - lastViewPortCenter; + + mLocalMap->setViewOffset(mLocalMap->getViewOffset() + viewPortCenterDiff); + mGlobalMap->setViewOffset(mGlobalMap->getViewOffset() + viewPortCenterDiff); + + mLastScrollWindowCoordinates = currentCoordinates; } void MapWindow::renderGlobalMap(Loading::Listener* loadingListener) @@ -450,34 +734,32 @@ namespace MWGui void MapWindow::addVisitedLocation(const std::string& name, int x, int y) { - float worldX, worldY; - mGlobalMapRender->cellTopLeftCornerToImageSpace (x, y, worldX, worldY); - - MyGUI::IntCoord widgetCoord( - worldX * mGlobalMapRender->getWidth()+6, - worldY * mGlobalMapRender->getHeight()+6, - 12, 12); - - static int _counter=0; - MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default); - markerWidget->setImageResource("DoorMarker"); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", name); - ++_counter; - - markerWidget = mEventBoxGlobal->createWidget("", - widgetCoord, MyGUI::Align::Default); - markerWidget->setNeedMouseFocus (true); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", name); - CellId cell; cell.first = x; cell.second = y; - mMarkers.push_back(cell); + if (mMarkers.insert(cell).second) + { + float worldX, worldY; + mGlobalMapRender->cellTopLeftCornerToImageSpace (x, y, worldX, worldY); + + int markerSize = 12; + int offset = mGlobalMapRender->getCellSize()/2 - markerSize/2; + MyGUI::IntCoord widgetCoord( + worldX * mGlobalMapRender->getWidth()+offset, + worldY * mGlobalMapRender->getHeight()+offset, + markerSize, markerSize); + + MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", + widgetCoord, MyGUI::Align::Default); + 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); + } } void MapWindow::cellExplored(int x, int y) @@ -536,18 +818,15 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned); } + void MapWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); + } + void MapWindow::open() { - // force markers to foreground - for (unsigned int i=0; igetChildCount (); ++i) - { - if (mGlobalMapOverlay->getChildAt (i)->getName().substr(0,4) == "Door") - mGlobalMapOverlay->getChildAt (i)->castType()->setImageResource("DoorMarker"); - } - globalMapUpdatePlayer(); - - mPlayerArrowGlobal->setImageTexture ("textures\\compass.dds"); } void MapWindow::globalMapUpdatePlayer () @@ -556,8 +835,6 @@ namespace MWGui if (MWBase::Environment::get().getWorld ()->isCellExterior ()) { Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedPosition (); - Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedOrientation (); - Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y); float worldX, worldY; mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY); @@ -566,12 +843,6 @@ namespace MWGui mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(worldX - 16, worldY - 16)); - MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); - MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); - float angle = std::atan2(dir.x, dir.y); - rotatingSubskin->setAngle(angle); - // set the view offset so that player is in the center MyGUI::IntSize viewsize = mGlobalMap->getSize(); MyGUI::IntPoint viewoffs(0.5*viewsize.width - worldX, 0.5*viewsize.height - worldY); @@ -584,20 +855,6 @@ namespace MWGui globalMapUpdatePlayer (); } - void MapWindow::notifyMapChanged () - { - // workaround to prevent the map from drawing on top of the button - MyGUI::IntCoord oldCoord = mButton->getCoord (); - MyGUI::Gui::getInstance().destroyWidget (mButton); - mButton = mMainWidget->createWidget("MW_Button", - oldCoord, MyGUI::Align::Bottom | MyGUI::Align::Right); - mButton->setProperty ("ExpandDirection", "Left"); - - mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); - mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : - "#{sWorld}"); - } - void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) { float x, y; @@ -613,15 +870,23 @@ namespace MWGui mGlobalMap->setViewOffset(viewoffs); } + void MapWindow::setGlobalMapPlayerDir(const float x, const float y) + { + MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); + MyGUI::RotatingSkin* rotatingSubskin = main->castType(); + rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); + float angle = std::atan2(x,y); + rotatingSubskin->setAngle(angle); + } + void MapWindow::clear() { mMarkers.clear(); mGlobalMapRender->clear(); + mChanged = true; while (mEventBoxGlobal->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mEventBoxGlobal->getChildAt(0)); - while (mGlobalMapOverlay->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0)); } void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) @@ -646,7 +911,7 @@ namespace MWGui mGlobalMapRender->read(map); - for (std::vector::iterator it = map.mMarkers.begin(); it != map.mMarkers.end(); ++it) + for (std::set::iterator it = map.mMarkers.begin(); it != map.mMarkers.end(); ++it) { const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); if (cell && !cell->mName.empty()) @@ -654,4 +919,89 @@ namespace MWGui } } } + + void MapWindow::setAlpha(float alpha) + { + NoDrop::setAlpha(alpha); + // can't allow showing map with partial transparency, as the fog of war will also go transparent + // and reveal parts of the map you shouldn't be able to see + for (std::vector::iterator it = mMapWidgets.begin(); it != mMapWidgets.end(); ++it) + (*it)->setVisible(alpha == 1); + } + + void MapWindow::customMarkerCreated(MyGUI::Widget *marker) + { + marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + marker->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onCustomMarkerDoubleClicked); + } + + void MapWindow::doorMarkerCreated(MyGUI::Widget *marker) + { + marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + } + + // ------------------------------------------------------------------- + + EditNoteDialog::EditNoteDialog() + : WindowModal("openmw_edit_note.layout") + { + getWidget(mOkButton, "OkButton"); + getWidget(mCancelButton, "CancelButton"); + getWidget(mDeleteButton, "DeleteButton"); + getWidget(mTextEdit, "TextEdit"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onCancelButtonClicked); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onOkButtonClicked); + mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onDeleteButtonClicked); + } + + void EditNoteDialog::showDeleteButton(bool show) + { + mDeleteButton->setVisible(show); + } + + bool EditNoteDialog::getDeleteButtonShown() + { + return mDeleteButton->getVisible(); + } + + void EditNoteDialog::setText(const std::string &text) + { + mTextEdit->setCaption(MyGUI::TextIterator::toTagsString(text)); + } + + std::string EditNoteDialog::getText() + { + return MyGUI::TextIterator::getOnlyText(mTextEdit->getCaption()); + } + + void EditNoteDialog::open() + { + WindowModal::open(); + center(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + } + + void EditNoteDialog::exit() + { + setVisible(false); + } + + void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget *sender) + { + setVisible(false); + } + + void EditNoteDialog::onOkButtonClicked(MyGUI::Widget *sender) + { + eventOkClicked(); + } + + void EditNoteDialog::onDeleteButtonClicked(MyGUI::Widget *sender) + { + eventDeleteClicked(); + } + } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index c73e5d7a14..e9c0c1a69f 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -5,6 +5,8 @@ #include "windowpinnablebase.hpp" +#include + namespace MWRender { class GlobalMap; @@ -23,12 +25,52 @@ namespace Loading namespace MWGui { + + struct CustomMarker + { + float mWorldX; + float mWorldY; + + ESM::CellId mCell; + + std::string mNote; + + bool operator == (const CustomMarker& other) + { + return mNote == other.mNote && mCell == other.mCell && mWorldX == other.mWorldX && mWorldY == other.mWorldY; + } + + void load (ESM::ESMReader& reader); + void save (ESM::ESMWriter& writer) const; + }; + + class CustomMarkerCollection + { + public: + void addMarker(const CustomMarker& marker, bool triggerEvent=true); + void deleteMarker (const CustomMarker& marker); + void updateMarker(const CustomMarker& marker, const std::string& newNote); + + void clear(); + + size_t size() const; + + std::vector::const_iterator begin() const; + std::vector::const_iterator end() const; + + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + EventHandle_Void eventMarkersChanged; + + private: + std::vector mMarkers; + }; + class LocalMapBase { public: - LocalMapBase(); + LocalMapBase(CustomMarkerCollection& markers); virtual ~LocalMapBase(); - void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop=false); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); @@ -57,48 +99,81 @@ namespace MWGui bool mChanged; bool mFogOfWar; + // Stores markers that were placed by a player. May be shared between multiple map views. + CustomMarkerCollection& mCustomMarkers; + std::vector mMapWidgets; std::vector mFogWidgets; // Keep track of created marker widgets, just to easily remove them later. - std::vector mDoorMarkerWidgets; // Doors - std::vector mMarkerWidgets; // Other markers + std::vector mDoorMarkerWidgets; + std::vector mMagicMarkerWidgets; + std::vector mCustomMarkerWidgets; + + void updateCustomMarkers(); void applyFogOfWar(); - void onMarkerFocused(MyGUI::Widget* w1, MyGUI::Widget* w2); - void onMarkerUnfocused(MyGUI::Widget* w1, MyGUI::Widget* w2); - MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerPosition& markerPos); virtual void notifyPlayerUpdate() {} virtual void notifyMapChanged() {} - // Update markers (Detect X effects, Mark/Recall effects) - // Note, door markers are handled in setActiveCell - void updateMarkers(); + virtual void customMarkerCreated(MyGUI::Widget* marker) {} + virtual void doorMarkerCreated(MyGUI::Widget* marker) {} + + void updateMagicMarkers(); void addDetectionMarkers(int type); - OEngine::GUI::Layout* mLayout; + void redraw(); float mMarkerUpdateTimer; - bool mMapDragAndDrop; - float mLastPositionX; float mLastPositionY; float mLastDirectionX; float mLastDirectionY; }; + class EditNoteDialog : public MWGui::WindowModal + { + public: + EditNoteDialog(); + + virtual void open(); + virtual void exit(); + + void showDeleteButton(bool show); + bool getDeleteButtonShown(); + void setText(const std::string& text); + std::string getText(); + + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + + EventHandle_Void eventDeleteClicked; + EventHandle_Void eventOkClicked; + + private: + void onCancelButtonClicked(MyGUI::Widget* sender); + void onOkButtonClicked(MyGUI::Widget* sender); + void onDeleteButtonClicked(MyGUI::Widget* sender); + + MyGUI::TextBox* mTextEdit; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; + MyGUI::Button* mDeleteButton; + }; + class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: - MapWindow(DragAndDrop* drag, const std::string& cacheDir); + MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, const std::string& cacheDir); virtual ~MapWindow(); void setCellName(const std::string& cellName); + virtual void setAlpha(float alpha); + void renderGlobalMap(Loading::Listener* loadingListener); // adds the marker to the global map @@ -108,6 +183,7 @@ namespace MWGui void cellExplored(int x, int y); void setGlobalMapPlayerPosition (float worldX, float worldY); + void setGlobalMapPlayerDir(const float x, const float y); virtual void open(); @@ -123,7 +199,13 @@ namespace MWGui void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onWorldButtonClicked(MyGUI::Widget* _sender); - + void onMapDoubleClicked(MyGUI::Widget* sender); + void onCustomMarkerDoubleClicked(MyGUI::Widget* sender); + void onNoteEditOk(); + void onNoteEditDelete(); + void onNoteEditDeleteConfirm(); + void onNoteDoubleClicked(MyGUI::Widget* sender); + void onChangeScrollWindowCoord(MyGUI::Widget* sender); void globalMapUpdatePlayer(); MyGUI::ScrollView* mGlobalMap; @@ -135,9 +217,11 @@ namespace MWGui MyGUI::IntPoint mLastDragPos; bool mGlobal; + MyGUI::IntCoord mLastScrollWindowCoordinates; + // Markers on global map typedef std::pair CellId; - std::vector mMarkers; + std::set mMarkers; // Cells that should be explored in the next frame (i.e. their map revealed on the global map) // We can't do this immediately, because the map update is not immediate either (see mNeedMapUpdate in scene.cpp) @@ -148,11 +232,16 @@ namespace MWGui MWRender::GlobalMap* mGlobalMapRender; - protected: + EditNoteDialog mEditNoteDialog; + CustomMarker mEditingMarker; + virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); + + virtual void doorMarkerCreated(MyGUI::Widget* marker); + virtual void customMarkerCreated(MyGUI::Widget *marker); virtual void notifyPlayerUpdate(); - virtual void notifyMapChanged(); }; } diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 49cc60d8aa..499383d5f9 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -10,6 +10,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" @@ -67,7 +69,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) std::string name = iter->getClass().getName(*iter) + " - " + boost::lexical_cast(price) + MWBase::Environment::get().getWorld()->getStore().get() - .find("sgp")->getString();; + .find("sgp")->getString(); MyGUI::Button* button = @@ -91,7 +93,10 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); } } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mList->setVisibleVScroll(false); mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); + mList->setVisibleVScroll(true); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); @@ -117,17 +122,24 @@ void MerchantRepair::exit() void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + // repair MWWorld::Ptr item = *sender->getUserData(); item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item)); + player.getClass().getContainerStore(player).restack(item); + MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); int price = boost::lexical_cast(sender->getUserString("Price")); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); + actorStats.setGoldPool(actorStats.getGoldPool() + price); + startRepair(mActor); } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index c4b204de72..80f38ba40a 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -139,11 +139,6 @@ namespace MWGui return false; } - void MessageBoxManager::setMessageBoxSpeed (int speed) - { - mMessageBoxSpeed = speed; - } - int MessageBoxManager::readPressedButton () { int pressed = mLastButtonPressed; @@ -197,9 +192,9 @@ namespace MWGui WindowModal::open(); int textPadding = 10; // padding between text-widget and main-widget - int textButtonPadding = 20; // padding between the text-widget und the button-widget + int textButtonPadding = 10; // padding between the text-widget und the button-widget int buttonLeftPadding = 10; // padding between the buttons if horizontal - int buttonTopPadding = 5; // ^-- if vertical + int buttonTopPadding = 10; // ^-- if vertical int buttonPadding = 5; // padding between button label and button itself int buttonMainPadding = 10; // padding between buttons and bottom of the main widget @@ -210,6 +205,7 @@ namespace MWGui getWidget(mButtonsWidget, "buttons"); mMessageWidget->setOverflowToTheLeft(true); + mMessageWidget->setSize(400, mMessageWidget->getHeight()); mMessageWidget->setCaptionWithReplacing(message); MyGUI::IntSize textSize = mMessageWidget->getTextSize(); @@ -217,8 +213,8 @@ namespace MWGui MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); int biggestButtonWidth = 0; - int buttonWidth = 0; int buttonsWidth = 0; + int buttonsHeight = 0; int buttonHeight = 0; MyGUI::IntCoord dummyCoord(0, 0, 0, 0); @@ -236,64 +232,120 @@ namespace MWGui mButtons.push_back(button); - buttonWidth = button->getTextSize().width + 2*buttonPadding + buttonLeftPadding; + if (buttonsWidth != 0) + buttonsWidth += buttonLeftPadding; + + int buttonWidth = button->getTextSize().width + 2*buttonPadding; buttonsWidth += buttonWidth; - buttonHeight = button->getTextSize().height + 2*buttonPadding + buttonTopPadding; + + buttonHeight = button->getTextSize().height + 2*buttonPadding; + + if (buttonsHeight != 0) + buttonsHeight += buttonTopPadding; + buttonsHeight += buttonHeight; if(buttonWidth > biggestButtonWidth) { biggestButtonWidth = buttonWidth; } } - buttonsWidth += buttonLeftPadding; MyGUI::IntSize mainWidgetSize; - // among each other - if(biggestButtonWidth > textSize.width) { - mainWidgetSize.width = biggestButtonWidth + buttonTopPadding; - } - else { - mainWidgetSize.width = textSize.width + 3*textPadding; - } - - MyGUI::IntCoord buttonCord; - MyGUI::IntSize buttonSize(0, buttonHeight); - - int top = textButtonPadding + buttonTopPadding + textSize.height; - - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) + if(buttonsWidth < textSize.width) { - buttonSize.width = (*button)->getTextSize().width + buttonPadding*2; - buttonSize.height = (*button)->getTextSize().height + buttonPadding*2; + // on one line + mainWidgetSize.width = textSize.width + 3*textPadding; + mainWidgetSize.height = textPadding + textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; - buttonCord.top = top; - buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2 - 5; // FIXME: -5 is not so nice :/ + MyGUI::IntSize realSize = mainWidgetSize + + // To account for borders + (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize()); - (*button)->setCoord(buttonCord); - (*button)->setSize(buttonSize); + MyGUI::IntPoint absPos; + absPos.left = (gameWindowSize.width - realSize.width)/2; + absPos.top = (gameWindowSize.height - realSize.height)/2; - top += buttonSize.height + 2*buttonTopPadding; + mMainWidget->setPosition(absPos); + mMainWidget->setSize(realSize); + + MyGUI::IntCoord messageWidgetCoord; + messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; + messageWidgetCoord.top = textPadding; + mMessageWidget->setCoord(messageWidgetCoord); + + mMessageWidget->setSize(textSize); + + MyGUI::IntCoord buttonCord; + MyGUI::IntSize buttonSize(0, buttonHeight); + int left = (mainWidgetSize.width - buttonsWidth)/2; + + std::vector::const_iterator button; + for(button = mButtons.begin(); button != mButtons.end(); ++button) + { + buttonCord.left = left; + buttonCord.top = messageWidgetCoord.top + textSize.height + textButtonPadding; + + buttonSize.width = (*button)->getTextSize().width + 2*buttonPadding; + buttonSize.height = (*button)->getTextSize().height + 2*buttonPadding; + + (*button)->setCoord(buttonCord); + (*button)->setSize(buttonSize); + + left += buttonSize.width + buttonLeftPadding; + } } + else + { + // among each other + if(biggestButtonWidth > textSize.width) { + mainWidgetSize.width = biggestButtonWidth + buttonTopPadding*2; + } + else { + mainWidgetSize.width = textSize.width + 3*textPadding; + } - mainWidgetSize.height = top + buttonMainPadding; - mMainWidget->setSize(mainWidgetSize); + MyGUI::IntCoord buttonCord; + MyGUI::IntSize buttonSize(0, buttonHeight); - MyGUI::IntPoint absPos; - absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; + int top = textPadding + textSize.height + textButtonPadding; - mMainWidget->setPosition(absPos); + std::vector::const_iterator button; + for(button = mButtons.begin(); button != mButtons.end(); ++button) + { + buttonSize.width = (*button)->getTextSize().width + buttonPadding*2; + buttonSize.height = (*button)->getTextSize().height + buttonPadding*2; - MyGUI::IntCoord messageWidgetCoord; - messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; - messageWidgetCoord.top = textPadding; - messageWidgetCoord.width = textSize.width; - messageWidgetCoord.height = textSize.height; - mMessageWidget->setCoord(messageWidgetCoord); + buttonCord.top = top; + buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2; + + (*button)->setCoord(buttonCord); + (*button)->setSize(buttonSize); + + top += buttonSize.height + buttonTopPadding; + } + + mainWidgetSize.height = textPadding + textSize.height + textButtonPadding + buttonsHeight + buttonMainPadding; + mMainWidget->setSize(mainWidgetSize + + // To account for borders + (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize())); + + MyGUI::IntPoint absPos; + absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; + absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; + + mMainWidget->setPosition(absPos); + + MyGUI::IntCoord messageWidgetCoord; + messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; + messageWidgetCoord.top = textPadding; + messageWidgetCoord.width = textSize.width; + messageWidgetCoord.height = textSize.height; + mMessageWidget->setCoord(messageWidgetCoord); + } // Set key focus to "Ok" button std::string ok = Misc::StringUtils::lowerCase(MyGUI::LanguageManager::getInstance().replaceTags("#{sOK}")); + std::vector::const_iterator button; for(button = mButtons.begin(); button != mButtons.end(); ++button) { if(Misc::StringUtils::ciEqual((*button)->getCaption(), ok)) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 406d98c484..5f180df204 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -34,7 +34,6 @@ namespace MWGui void clear(); bool removeMessageBox (MessageBox *msgbox); - void setMessageBoxSpeed (int speed); int readPressedButton (); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 230282f152..f6882ada61 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -6,16 +6,19 @@ namespace MWGui { - PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel) + PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel, bool hideItems) { mSourceModel = sourceModel; int chance = thief.getClass().getSkill(thief, ESM::Skill::Sneak); mSourceModel->update(); - for (size_t i = 0; igetItemCount(); ++i) + if (hideItems) { - if (std::rand() / static_cast(RAND_MAX) * 100 > chance) - mHiddenItems.push_back(mSourceModel->getItem(i)); + for (size_t i = 0; igetItemCount(); ++i) + { + if (std::rand() / static_cast(RAND_MAX) * 100 > chance) + mHiddenItems.push_back(mSourceModel->getItem(i)); + } } } @@ -42,11 +45,8 @@ namespace MWGui const ItemStack& item = mSourceModel->getItem(i); // Bound items may not be stolen - if (item.mBase.getCellRef().getRefId().size() > 6 - && item.mBase.getCellRef().getRefId().substr(0,6) == "bound_") - { + if (item.mFlags & ItemStack::Flag_Bound) continue; - } if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end() && item.mType != ItemStack::Type_Equipped) diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp index 090d48d0e7..af72119c89 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.hpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp @@ -10,7 +10,7 @@ namespace MWGui class PickpocketItemModel : public ProxyItemModel { public: - PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel); + PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel, bool hideItems=true); virtual ItemStack getItem (ModelIndex index); virtual size_t getItemCount(); virtual void update(); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 328ef21fb7..440165e749 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -3,9 +3,11 @@ #include #include +#include #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -226,12 +228,9 @@ namespace MWGui esmStore.get().find(spell->mEffects.mList.front().mEffectID); std::string path = effect->mIcon; - int slashPos = path.find("\\"); + int slashPos = path.rfind('\\'); path.insert(slashPos+1, "b_"); - path = std::string("icons\\") + path; - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); + path = Misc::ResourceHelpers::correctIconPath(path); button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); button->setIcon(path); @@ -295,12 +294,18 @@ namespace MWGui return; store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } else if (type == Type_Item) { MWWorld::Ptr item = *button->getUserData(); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->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) + { + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + } } else if (type == Type_MagicItem) { @@ -324,6 +329,7 @@ namespace MWGui } store.setSelectedEnchantItem(it); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } } @@ -594,7 +600,7 @@ namespace MWGui for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) { const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mMagicList->createWidget("SpellText", + MyGUI::Button* t = mMagicList->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(spell->mName); t->setTextAlign(MyGUI::Align::Left); @@ -611,7 +617,7 @@ namespace MWGui for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) { const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mMagicList->createWidget("SpellText", + MyGUI::Button* t = mMagicList->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(spell->mName); t->setTextAlign(MyGUI::Align::Left); @@ -642,7 +648,7 @@ namespace MWGui } } - MyGUI::Button* t = mMagicList->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::Button* t = mMagicList->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(item.getClass().getName(item)); t->setTextAlign(MyGUI::Align::Left); @@ -654,9 +660,10 @@ namespace MWGui mHeight += spellHeight; } - + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mMagicList->setVisibleVScroll(false); mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight)); - + mMagicList->setVisibleVScroll(true); } void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 499c1e191a..e3bec37899 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -32,7 +32,6 @@ namespace MWGui , mHairIndex(0) , mCurrentAngle(0) , mPreviewDirty(true) - , mPreview(NULL) { // Centre dialog center(); @@ -115,7 +114,14 @@ namespace MWGui updateSkills(); updateSpellPowers(); - mPreview = new MWRender::RaceSelectionPreview(); + mPreview.reset(NULL); + + mPreviewImage->setImageTexture(""); + + const std::string textureName = "CharacterHeadPreview"; + MyGUI::RenderManager::getInstance().destroyTexture(MyGUI::RenderManager::getInstance().getTexture(textureName)); + + mPreview.reset(new MWRender::RaceSelectionPreview()); mPreview->setup(); mPreview->update (0); @@ -129,7 +135,7 @@ namespace MWGui index = proto.mHair.substr(proto.mHair.size() - 2, 2); mHairIndex = boost::lexical_cast(index) - 1; - mPreviewImage->setImageTexture ("CharacterHeadPreview"); + mPreviewImage->setImageTexture (textureName); mPreviewDirty = true; } @@ -157,8 +163,7 @@ namespace MWGui void RaceDialog::close() { - delete mPreview; - mPreview = 0; + mPreview.reset(NULL); } // widget controls @@ -306,6 +311,10 @@ namespace MWGui void RaceDialog::doRenderUpdate() { + if (!mPreview.get()) + return; + + mPreview->onFrame(); if (mPreviewDirty) { mPreview->render(); @@ -384,7 +393,6 @@ namespace MWGui if (mCurrentRaceId.empty()) return; - Widgets::MWSpellPtr spellPowerWidget; const int lineHeight = 18; MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18); @@ -396,7 +404,7 @@ namespace MWGui for (int i = 0; it != end; ++it) { const std::string &spellpower = *it; - spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); + Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); spellPowerWidget->setUserString("Spell", spellpower); diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 340dcfa27b..454c1d0b61 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -52,6 +52,11 @@ namespace MWGui */ EventHandle_Void eventBack; + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + void doRenderUpdate(); protected: @@ -100,7 +105,7 @@ namespace MWGui float mCurrentAngle; - MWRender::RaceSelectionPreview* mPreview; + std::auto_ptr mPreview; bool mPreviewDirty; }; diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 0795642736..c45c2566ea 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -119,7 +119,11 @@ void Recharge::updateView() currentY += 32 + 4; } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mView->setVisibleVScroll(false); mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY))); + mView->setVisibleVScroll(true); } void Recharge::onCancel(MyGUI::Widget *sender) @@ -165,6 +169,8 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) item.getCellRef().setEnchantmentCharge( std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(enchantment->mData.mCharge))); + player.getClass().getContainerStore(player).restack(item); + player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); } @@ -175,6 +181,10 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->getString(); message = boost::str(boost::format(message) % gem.getClass().getName(gem)); MWBase::Environment::get().getWindowManager()->messageBox(message); + + // special case: readd Azura's Star + if (Misc::StringUtils::ciEqual(gem.get()->mBase->mId, "Misc_SoulGem_Azura")) + player.getClass().getContainerStore(player).add("Misc_SoulGem_Azura", 1, player); } updateView(); diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 986e27243a..019f341b57 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -126,7 +126,10 @@ void Repair::updateRepairView() currentY += 32 + 4; } } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mRepairView->setVisibleVScroll(false); mRepairView->setCanvasSize (MyGUI::IntSize(mRepairView->getWidth(), std::max(mRepairView->getHeight(), currentY))); + mRepairView->setVisibleVScroll(true); } void Repair::onCancel(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index e27e40ae64..d1f8a76a62 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -27,22 +27,22 @@ namespace MWGui getWidget(mNameWidget, "NameText"); getWidget(button, "NameButton"); adjustButtonSize(button); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked);; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked); getWidget(mRaceWidget, "RaceText"); getWidget(button, "RaceButton"); adjustButtonSize(button); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked);; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked); getWidget(mClassWidget, "ClassText"); getWidget(button, "ClassButton"); adjustButtonSize(button); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked);; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked); getWidget(mBirthSignWidget, "SignText"); getWidget(button, "SignButton"); adjustButtonSize(button); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked);; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked); // Setup dynamic stats getWidget(mHealth, "Health"); @@ -320,7 +320,10 @@ namespace MWGui if (!mMiscSkills.empty()) addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setVisibleVScroll(true); } // widget controls diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 5d0767d217..01b106d907 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -54,6 +54,11 @@ namespace MWGui */ EventHandle_Void eventBack; + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + EventHandle_Int eventActivateDialog; protected: diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index a35415e754..3920a5dcb6 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -37,10 +37,11 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); - mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); + mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); + mSaveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &SaveGameDialog::onKeyButtonPressed); mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); } @@ -212,6 +213,7 @@ namespace MWGui void SaveGameDialog::accept(bool reallySure) { + // Remove for MyGUI 3.2.2 MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); if (mSaving) @@ -247,6 +249,12 @@ namespace MWGui } } + void SaveGameDialog::onKeyButtonPressed(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character) + { + if (key == MyGUI::KeyCode::Delete && mCurrentSlot) + confirmDeleteSave(); + } + void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) { accept(); diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 80cfad2794..11470a20f3 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -26,6 +26,7 @@ namespace MWGui private: void confirmDeleteSave(); + void onKeyButtonPressed(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character); void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); void onDeleteButtonClicked (MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp new file mode 100644 index 0000000000..c27737285c --- /dev/null +++ b/apps/openmw/mwgui/screenfader.cpp @@ -0,0 +1,111 @@ +#include "screenfader.hpp" + +namespace MWGui +{ + + ScreenFader::ScreenFader() + : WindowBase("openmw_screen_fader.layout") + , mMode(FadingMode_In) + , mRemainingTime(0.f) + , mTargetTime(0.f) + , mTargetAlpha(0.f) + , mCurrentAlpha(0.f) + , mStartAlpha(0.f) + , mFactor(1.f) + { + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); + + setVisible(false); + } + + void ScreenFader::update(float dt) + { + if (mRemainingTime > 0) + { + if (mMode == FadingMode_In) + { + mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); + if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + else if (mMode == FadingMode_Out) + { + mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); + if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + + mRemainingTime -= dt; + } + + if (1.f-((1.f-mCurrentAlpha) * mFactor) == 0.f) + mMainWidget->setVisible(false); + else + applyAlpha(); + } + + void ScreenFader::applyAlpha() + { + setVisible(true); + mMainWidget->setAlpha(1.f-((1.f-mCurrentAlpha) * mFactor)); + } + + void ScreenFader::fadeIn(float time) + { + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 0.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 0.f; + mMode = FadingMode_In; + mTargetTime = time; + mRemainingTime = time; + } + + void ScreenFader::fadeOut(const float time) + { + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 1.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 1.f; + mMode = FadingMode_Out; + mTargetTime = time; + mRemainingTime = time; + } + + void ScreenFader::fadeTo(const int percent, const float time) + { + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = percent/100.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = percent/100.f; + + if (mTargetAlpha == mStartAlpha) return; + else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out; + else mMode = FadingMode_In; + + mTargetTime = time; + mRemainingTime = time; + } + + void ScreenFader::setFactor(float factor) + { + mFactor = factor; + } + +} diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp new file mode 100644 index 0000000000..975006a853 --- /dev/null +++ b/apps/openmw/mwgui/screenfader.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_MWGUI_SCREENFADER_H +#define OPENMW_MWGUI_SCREENFADER_H + +#include "windowbase.hpp" + +namespace MWGui +{ + + class ScreenFader : public WindowBase + { + public: + ScreenFader(); + + void update(float dt); + + void fadeIn(const float time); + void fadeOut(const float time); + void fadeTo(const int percent, const float time); + + void setFactor (float factor); + + private: + enum FadingMode + { + FadingMode_In, + FadingMode_Out + }; + + void applyAlpha(); + + FadingMode mMode; + + float mRemainingTime; + float mTargetTime; + float mTargetAlpha; + float mCurrentAlpha; + float mStartAlpha; + + float mFactor; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 3d07511143..038d91ca91 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -13,7 +13,7 @@ namespace { - void adjustButton (MWGui::ImageButton* button) + void adjustButton (Gui::ImageButton* button) { MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(); button->setSize(button->getRequestedSize()); @@ -54,13 +54,17 @@ namespace MWGui MWWorld::LiveCellRef *ref = mScroll.get(); - BookTextParser parser; - MyGUI::IntSize size = parser.parseScroll(ref->mBase->mText, mTextView, 390); + Formatting::BookFormatter formatter; + formatter.markupToWidget(mTextView, ref->mBase->mText, 390, mTextView->getHeight()); + MyGUI::IntSize size = mTextView->getChildAt(0)->getSize(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mTextView->setVisibleVScroll(false); if (size.height > mTextView->getSize().height) mTextView->setCanvasSize(MyGUI::IntSize(410, size.height)); else mTextView->setCanvasSize(410, mTextView->getSize().height); + mTextView->setVisibleVScroll(true); mTextView->setViewOffset(MyGUI::IntPoint(0,0)); diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index 17e56f3345..08ce6a9054 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -2,7 +2,8 @@ #define MWGUI_SCROLLWINDOW_H #include "windowbase.hpp" -#include "imagebutton.hpp" + +#include #include "../mwworld/ptr.hpp" @@ -23,8 +24,8 @@ namespace MWGui void onTakeButtonClicked (MyGUI::Widget* _sender); private: - MWGui::ImageButton* mCloseButton; - MWGui::ImageButton* mTakeButton; + Gui::ImageButton* mCloseButton; + Gui::ImageButton* mTakeButton; MyGUI::ScrollView* mTextView; MWWorld::Ptr mScroll; diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index f4602b064c..74def66e3e 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -136,6 +136,7 @@ namespace MWGui float min,max; getSettingMinMax(scroll, min, max); float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); + value = std::max(min, std::min(value, max)); value = (value-min)/(max-min); scroll->setScrollPosition( value * (scroll->getScrollRange()-1)); @@ -157,6 +158,8 @@ namespace MWGui { configureWidgets(mMainWidget); + setTitle("#{sOptions}"); + getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); @@ -174,6 +177,7 @@ namespace MWGui getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mRefractionButton, "RefractionButton"); + getWidget(mDifficultySlider, "DifficultySlider"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled); @@ -227,6 +231,10 @@ namespace MWGui MyGUI::TextBox* fovText; getWidget(fovText, "FovText"); fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getInt("field of view", "General"))) + ")"); + + MyGUI::TextBox* diffText; + getWidget(diffText, "DifficultyText"); + diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast(int(Settings::Manager::getInt("difficulty", "Game"))) + ")"); } void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) @@ -286,15 +294,6 @@ namespace MWGui newState = true; } - if (_sender == mVSyncButton) - { - // Ogre::Window::setVSyncEnabled is bugged in 1.8 -#if OGRE_VERSION < (1 << 16 | 9 << 8 | 0) - MWBase::Environment::get().getWindowManager()-> - messageBox("VSync will be applied after a restart", std::vector()); -#endif - } - if (_sender == mShadersButton) { if (newState == false) @@ -363,10 +362,9 @@ namespace MWGui void SettingsWindow::onShaderModeToggled(MyGUI::Widget* _sender) { - std::string val = static_cast(_sender)->getCaption(); - val = hlslGlsl(); + std::string val = hlslGlsl(); - static_cast(_sender)->setCaption(val); + _sender->castType()->setCaption(val); Settings::Manager::setString("shader mode", "General", val); @@ -406,6 +404,12 @@ namespace MWGui getWidget(fovText, "FovText"); fovText->setCaption("Field of View (" + boost::lexical_cast(int(value)) + ")"); } + if (scroller == mDifficultySlider) + { + MyGUI::TextBox* diffText; + getWidget(diffText, "DifficultyText"); + diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast(int(value)) + ")"); + } } else { @@ -460,14 +464,17 @@ namespace MWGui curH += h; } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mControlsBox->setVisibleVScroll(false); mControlsBox->setCanvasSize (mControlsBox->getWidth(), std::max(curH, mControlsBox->getHeight())); + mControlsBox->setVisibleVScroll(true); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) { int actionId = *_sender->getUserData(); - static_cast(_sender)->setCaptionWithReplacing("#{sNone}"); + _sender->castType()->setCaptionWithReplacing("#{sNone}"); MWBase::Environment::get().getWindowManager ()->staticMessageBox ("#{sControlsMenu3}"); MWBase::Environment::get().getWindowManager ()->disallowMouse(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 37f2c8af06..cbfb5cfe1d 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -30,6 +30,7 @@ namespace MWGui MyGUI::Button* mVSyncButton; MyGUI::Button* mFPSButton; MyGUI::ScrollBar* mFOVSlider; + MyGUI::ScrollBar* mDifficultySlider; MyGUI::ScrollBar* mAnisotropySlider; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::TextBox* mAnisotropyLabel; diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 8d9f35daaa..cd378aadf8 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -50,6 +50,8 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + // TODO: refactor to use MyGUI::ListBox + MyGUI::Button* toAdd = mSpellsView->createWidget( "SandTextButton", @@ -106,7 +108,10 @@ namespace MWGui updateLabels(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellsView->setVisibleVScroll(false); mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); + mSpellsView->setVisibleVScroll(true); } bool SpellBuyingWindow::playerHasSpell(const std::string &id) @@ -130,6 +135,11 @@ namespace MWGui MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + startSpellBuying(mPtr); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 030b8bf37c..1116d65438 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -37,6 +39,7 @@ namespace MWGui EditEffectDialog::EditEffectDialog() : WindowModal("openmw_edit_effect.layout") , mEditing(false) + , mMagicEffect(NULL) { getWidget(mCancelButton, "CancelButton"); getWidget(mOkButton, "OkButton"); @@ -143,13 +146,7 @@ namespace MWGui void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect) { - std::string icon = effect->mIcon; - icon[icon.size()-3] = 'd'; - icon[icon.size()-2] = 'd'; - icon[icon.size()-1] = 's'; - icon = "icons\\" + icon; - - mEffectImage->setImageTexture (icon); + mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon)); mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}"); @@ -185,7 +182,7 @@ namespace MWGui { mAreaBox->setPosition(mAreaBox->getPosition().left, curY); mAreaBox->setVisible (true); - curY += mAreaBox->getSize().height; + //curY += mAreaBox->getSize().height; } } @@ -291,7 +288,7 @@ namespace MWGui SpellCreationDialog::SpellCreationDialog() : WindowBase("openmw_spellcreation_dialog.layout") - , EffectEditorBase() + , EffectEditorBase(EffectEditorBase::Spellmaking) { getWidget(mNameEdit, "NameEdit"); getWidget(mMagickaCost, "MagickaCost"); @@ -352,7 +349,13 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, boost::lexical_cast(mPriceLabel->getCaption()), player); + int price = boost::lexical_cast(mPriceLabel->getCaption()); + + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); @@ -385,6 +388,14 @@ namespace MWGui void SpellCreationDialog::notifyEffectsChanged () { + if (mEffects.empty()) + { + mMagickaCost->setCaption("0"); + mPriceLabel->setCaption("0"); + mSuccessChance->setCaption("0"); + return; + } + float y = 0; const MWWorld::ESMStore &store = @@ -392,7 +403,7 @@ namespace MWGui for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - float x = 0.5 * it->mMagnMin + it->mMagnMax; + float x = 0.5 * (it->mMagnMin + it->mMagnMax); const ESM::MagicEffect* effect = store.get().find(it->mEffectID); @@ -407,7 +418,7 @@ namespace MWGui y += x * fEffectCostMult; y = std::max(1.f,y); - if (effect->mData.mFlags & ESM::MagicEffect::CastTarget) + if (it->mRange == ESM::RT_Target) y *= 1.5; } @@ -434,10 +445,11 @@ namespace MWGui // ------------------------------------------------------------------------------------------------ - EffectEditorBase::EffectEditorBase() + EffectEditorBase::EffectEditorBase(Type type) : mAddEffectDialog() , mSelectAttributeDialog(NULL) , mSelectSkillDialog(NULL) + , mType(type) { mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); @@ -472,6 +484,13 @@ namespace MWGui const std::vector& list = spell->mEffects.mList; for (std::vector::const_iterator it2 = list.begin(); it2 != list.end(); ++it2) { + const ESM::MagicEffect * effect = MWBase::Environment::get().getWorld()->getStore().get().find(it2->mEffectID); + + // skip effects that do not allow spellmaking/enchanting + int requiredFlags = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; + if (!(effect->mData.mFlags & requiredFlags)) + continue; + if (std::find(knownEffects.begin(), knownEffects.end(), it2->mEffectID) == knownEffects.end()) knownEffects.push_back(it2->mEffectID); } @@ -504,7 +523,7 @@ namespace MWGui updateEffectsView (); } - void EffectEditorBase::setWidgets (Widgets::MWList *availableEffectsList, MyGUI::ScrollView *usedEffectsView) + void EffectEditorBase::setWidgets (Gui::MWList *availableEffectsList, MyGUI::ScrollView *usedEffectsView) { mAvailableEffectsList = availableEffectsList; mUsedEffectsView = usedEffectsView; @@ -514,16 +533,24 @@ namespace MWGui void EffectEditorBase::onSelectAttribute () { - mAddEffectDialog.setVisible(true); + const ESM::MagicEffect* effect = + MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); + mAddEffectDialog.setVisible(true); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); mSelectAttributeDialog = 0; } void EffectEditorBase::onSelectSkill () { + const ESM::MagicEffect* effect = + MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); + mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); mAddEffectDialog.setVisible(true); - mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId ()); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); mSelectSkillDialog = 0; } @@ -548,11 +575,10 @@ namespace MWGui } int buttonId = *sender->getUserData(); - short effectId = mButtonMapping[buttonId]; - + mSelectedKnownEffectId = mButtonMapping[buttonId]; for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - if (it->mEffectID == effectId) + if (it->mEffectID == mSelectedKnownEffectId) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); return; @@ -560,9 +586,7 @@ namespace MWGui } const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(effectId); - - mAddEffectDialog.newEffect (effect); + MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) { @@ -582,6 +606,7 @@ namespace MWGui } else { + mAddEffectDialog.newEffect(effect); mAddEffectDialog.setVisible(true); } } @@ -637,7 +662,10 @@ namespace MWGui ++i; } + // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mUsedEffectsView->setVisibleHScroll(false); mUsedEffectsView->setCanvasSize(size); + mUsedEffectsView->setVisibleHScroll(true); notifyEffectsChanged(); } diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 25c615678a..ecccf3baf9 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -1,9 +1,10 @@ #ifndef MWGUI_SPELLCREATION_H #define MWGUI_SPELLCREATION_H +#include + #include "windowbase.hpp" #include "referenceinterface.hpp" -#include "list.hpp" #include "widgets.hpp" namespace MWGui @@ -85,13 +86,19 @@ namespace MWGui class EffectEditorBase { public: - EffectEditorBase(); + enum Type + { + Spellmaking, + Enchanting + }; + + EffectEditorBase(Type type); virtual ~EffectEditorBase(); protected: std::map mButtonMapping; // maps button ID to effect ID - Widgets::MWList* mAvailableEffectsList; + Gui::MWList* mAvailableEffectsList; MyGUI::ScrollView* mUsedEffectsView; EditEffectDialog mAddEffectDialog; @@ -99,6 +106,7 @@ namespace MWGui SelectSkillDialog* mSelectSkillDialog; int mSelectedEffect; + short mSelectedKnownEffectId; std::vector mEffects; @@ -117,9 +125,12 @@ namespace MWGui void updateEffectsView(); void startEditing(); - void setWidgets (Widgets::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); + void setWidgets (Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); virtual void notifyEffectsChanged () {} + + private: + Type mType; }; class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase @@ -147,8 +158,6 @@ namespace MWGui MyGUI::Button* mCancelButton; MyGUI::TextBox* mPriceLabel; - Widgets::MWEffectList* mUsedEffectsList; - ESM::Spell mSpell; }; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 1a9e418de6..dbd91ab753 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -133,13 +135,7 @@ namespace MWGui ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); mWidgetMap[it->first] = image; - std::string icon = effect->mIcon; - icon[icon.size()-3] = 'd'; - icon[icon.size()-2] = 'd'; - icon[icon.size()-1] = 's'; - icon = "icons\\" + icon; - - image->setImageTexture(icon); + image->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon)); std::string name = ESM::MagicEffect::effectIdToString (it->first); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 77da56fa4f..97ebbcde2f 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -45,6 +45,7 @@ namespace MWGui , NoDrop(drag, mMainWidget) , mHeight(0) , mWidth(0) + , mWindowSize(mMainWidget->getSize()) { mSpellIcons = new SpellIcons(); @@ -66,6 +67,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned); } + void SpellWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); + } + void SpellWindow::open() { updateSpells(); @@ -150,7 +157,7 @@ namespace MWGui for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) { const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mSpellView->createWidget("SpellText", + MyGUI::Button* t = mSpellView->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(spell->mName); t->setTextAlign(MyGUI::Align::Left); @@ -170,7 +177,7 @@ namespace MWGui for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) { const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mSpellView->createWidget("SpellText", + MyGUI::Button* t = mSpellView->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(spell->mName); t->setTextAlign(MyGUI::Align::Left); @@ -181,7 +188,7 @@ namespace MWGui t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); // cost / success chance - MyGUI::Button* costChance = mSpellView->createWidget("SpellText", + MyGUI::Button* costChance = mSpellView->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); std::string cost = boost::lexical_cast(spell->mData.mCost); std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(*it, player))); @@ -217,7 +224,7 @@ namespace MWGui } } - MyGUI::Button* t = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::Button* t = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(item.getClass().getName(item)); t->setTextAlign(MyGUI::Align::Left); @@ -231,7 +238,7 @@ namespace MWGui // cost / charge - MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); float enchantCost = enchant->mData.mCost; @@ -261,7 +268,10 @@ namespace MWGui mHeight += spellHeight; } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellView->setVisibleVScroll(false); mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); + mSpellView->setVisibleVScroll(true); } void SpellWindow::addGroup(const std::string &label, const std::string& label2) @@ -299,7 +309,11 @@ namespace MWGui void SpellWindow::onWindowResize(MyGUI::Window* _sender) { - updateSpells(); + if (mMainWidget->getSize() != mWindowSize) + { + mWindowSize = mMainWidget->getSize(); + updateSpells(); + } } void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) @@ -326,6 +340,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); store.setSelectedEnchantItem(it); updateSpells(); diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 53eed1ba14..a74847b907 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -29,6 +29,8 @@ namespace MWGui int mHeight; int mWidth; + MyGUI::IntSize mWindowSize; + std::string mSpellToDelete; void addGroup(const std::string& label, const std::string& label2); @@ -42,6 +44,7 @@ namespace MWGui void onDeleteSpellAccept(); virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); virtual void open(); SpellIcons* mSpellIcons; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index b11258f1cd..baa779c1c4 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -66,7 +66,7 @@ namespace MWGui mSkillWidgetMap.insert(std::pair(i, (MyGUI::TextBox*)NULL)); } - MyGUI::WindowPtr t = static_cast(mMainWidget); + MyGUI::Window* t = mMainWidget->castType(); t->eventWindowChangeCoord += MyGUI::newDelegate(this, &StatsWindow::onWindowResize); } @@ -82,12 +82,15 @@ namespace MWGui { mLeftPane->setCoord( MyGUI::IntCoord(0, 0, 0.44*window->getSize().width, window->getSize().height) ); mRightPane->setCoord( MyGUI::IntCoord(0.44*window->getSize().width, 0, 0.56*window->getSize().width, window->getSize().height) ); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), mSkillView->getCanvasSize().height); + mSkillView->setVisibleVScroll(true); } void StatsWindow::setBar(const std::string& name, const std::string& tname, int val, int max) { - MyGUI::ProgressPtr pt; + MyGUI::ProgressBar* pt; getWidget(pt, name); pt->setProgressRange(max); pt->setProgressPosition(val); @@ -99,7 +102,7 @@ namespace MWGui void StatsWindow::setPlayerName(const std::string& playerName) { - static_cast(mMainWidget)->setCaption(playerName); + mMainWidget->castType()->setCaption(playerName); adjustWindowCaption(); } @@ -400,9 +403,24 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->mDescription); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast(progressPercent)+"/100"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast(progressPercent)); + if (base < 100) + { + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "false"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillMaxed", "true"); + + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillProgressVBox", "true"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillProgressVBox", "false"); + + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast(progressPercent)+"/100"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast(progressPercent)); + } else { + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "true"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillMaxed", "false"); + + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillProgressVBox", "false"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillProgressVBox", "true"); + } } mSkillWidgetMap[skillId] = widget; @@ -487,30 +505,30 @@ namespace MWGui std::string text; - text += std::string("#DDC79E") + faction->mName; + text += std::string("#{fontcolourhtml=header}") + faction->mName; if (expelled.find(it->first) != expelled.end()) - text += "\n#BF9959#{sExpelled}"; + text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { int rank = it->second; rank = std::max(0, std::min(9, rank)); - text += std::string("\n#BF9959") + faction->mRanks[rank]; + text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; if (rank < 9) { // player doesn't have max rank yet - text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[rank+1]; + text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank+1]; ESM::RankData rankData = faction->mData.mRankData[rank+1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); - text += "\n#BF9959#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) + text += "\n#{fontcolourhtml=normal}#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) + ", #{" + attr2->mName + "}: " + boost::lexical_cast(rankData.mAttribute2); - text += "\n\n#DDC79E#{sFavoriteSkills}"; - text += "\n#BF9959"; + text += "\n\n#{fontcolourhtml=header}#{sFavoriteSkills}"; + text += "\n#{fontcolourhtml=normal}"; bool firstSkill = true; for (int i=0; i<7; ++i) { @@ -578,11 +596,20 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setVisibleVScroll(true); } void StatsWindow::onPinToggled() { MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned); } + + void StatsWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); + } } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index d90c16be92..f41995ac07 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -37,7 +37,7 @@ namespace MWGui void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } void updateSkillArea(); - virtual void open() { onWindowResize(static_cast(mMainWidget)); } + virtual void open() { onWindowResize(mMainWidget->castType()); } private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); @@ -74,6 +74,7 @@ namespace MWGui protected: virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); }; } #endif diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 1ed80fc1e9..c83e3ffa99 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -22,6 +22,11 @@ namespace MWGui void setTextLabel(const std::string &label); virtual void open(); + /** Event : Dialog finished, OK button clicked.\n + signature : void method()\n + */ + EventHandle_WindowBase eventDone; + protected: void onOkClicked(MyGUI::Widget* _sender); void onTextAccepted(MyGUI::Edit* _sender); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 676a9ee63d..4608010ac3 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -4,11 +4,14 @@ #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "mapwindow.hpp" #include "inventorywindow.hpp" @@ -17,6 +20,7 @@ namespace MWGui { + std::string ToolTips::sSchoolNames[] = {"#{sSchoolAlteration}", "#{sSchoolConjuration}", "#{sSchoolDestruction}", "#{sSchoolIllusion}", "#{sSchoolMysticism}", "#{sSchoolRestoration}"}; ToolTips::ToolTips() : Layout("openmw_tooltips.layout") @@ -84,8 +88,6 @@ namespace MWGui || (MWBase::Environment::get().getWindowManager()->getMode() == GM_Container) || (MWBase::Environment::get().getWindowManager()->getMode() == GM_Inventory))) { - mFocusObject = MWBase::Environment::get().getWorld()->getFacedObject(); - if (mFocusObject.isEmpty ()) return; @@ -97,7 +99,9 @@ namespace MWGui setCoord(0, 0, 300, 300); mDynamicToolTipBox->setVisible(true); ToolTipInfo info; - info.caption=mFocusObject.getCellRef().getRefId(); + info.caption = mFocusObject.getClass().getName(mFocusObject); + if (info.caption.empty()) + info.caption=mFocusObject.getCellRef().getRefId(); info.icon=""; tooltipSize = createToolTip(info); } @@ -218,6 +222,12 @@ namespace MWGui params.mNoTarget = false; effects.push_back(params); } + if (MWMechanics::spellIncreasesSkill(spell)) // display school of spells that contribute to skill progress + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int school = MWMechanics::getSpellSchool(spell, player); + info.text = "#{sSchool}: " + sSchoolNames[school]; + } info.effects = effects; tooltipSize = createToolTip(info); } @@ -236,12 +246,23 @@ namespace MWGui size_t underscorePos = it->first.find("_"); if (underscorePos == std::string::npos) continue; - std::string propertyKey = it->first.substr(0, underscorePos); + std::string key = it->first.substr(0, underscorePos); std::string widgetName = it->first.substr(underscorePos+1, it->first.size()-(underscorePos+1)); + std::string type = "Property"; + size_t caretPos = key.find("^"); + if (caretPos != std::string::npos) + { + type = key.substr(0, caretPos); + key.erase(key.begin(), key.begin() + caretPos + 1); + } + MyGUI::Widget* w; getWidget(w, widgetName); - w->setProperty(propertyKey, it->second); + if (type == "Property") + w->setProperty(key, it->second); + else if (type == "UserData") + w->setUserString(key, it->second); } tooltipSize = tooltip->getSize(); @@ -319,20 +340,6 @@ namespace MWGui return tooltipSize; } - void ToolTips::findImageExtension(std::string& image) - { - int len = image.size(); - if (len < 4) return; - - if (!Ogre::ResourceGroupManager::getSingleton().resourceExists(Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, image)) - { - // Change texture extension to .dds - image[len-3] = 'd'; - image[len-2] = 'd'; - image[len-1] = 's'; - } - } - MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) { mDynamicToolTipBox->setVisible(true); @@ -371,8 +378,7 @@ namespace MWGui const int imageCaptionHPadding = (caption != "" ? 8 : 0); const int imageCaptionVPadding = (caption != "" ? 4 : 0); - std::string realImage = "icons\\" + image; - findImageExtension(realImage); + std::string realImage = Misc::ResourceHelpers::correctIconPath(image); MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setProperty("Static", "true"); @@ -547,6 +553,17 @@ namespace MWGui return " (" + boost::lexical_cast(value) + ")"; } + std::string ToolTips::getCellRefString(const MWWorld::CellRef& cellref) + { + std::string ret; + ret += getMiscString(cellref.getOwner(), "Owner"); + ret += getMiscString(cellref.getFaction(), "Faction"); + if (cellref.getFactionRank() > 0) + ret += getValueString(cellref.getFactionRank(), "Rank"); + ret += getMiscString(cellref.getGlobalVariable(), "Global"); + return ret; + } + bool ToolTips::toggleFullHelp() { mFullHelp = !mFullHelp; @@ -633,13 +650,11 @@ namespace MWGui widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "BirthSignToolTip"); - std::string image = sign->mTexture; - image.replace(image.size()-3, 3, "dds"); - widget->setUserString("ImageTexture_BirthSignImage", "textures\\" + image); + widget->setUserString("ImageTexture_BirthSignImage", Misc::ResourceHelpers::correctTexturePath(sign->mTexture)); std::string text; text += sign->mName; - text += "\n#BF9959" + sign->mDescription; + text += "\n#{fontcolourhtml=normal}" + sign->mDescription; std::vector abilities, powers, spells; @@ -679,13 +694,13 @@ namespace MWGui { if (it == categories[category].spells.begin()) { - text += std::string("\n#DDC79E") + std::string("#{") + categories[category].label + "}"; + text += std::string("\n#{fontcolourhtml=header}") + std::string("#{") + categories[category].label + "}"; } const std::string &spellId = *it; const ESM::Spell *spell = store.get().find(spellId); - text += "\n#BF9959" + spell->mName; + text += "\n#{fontcolourhtml=normal}" + spell->mName; } } @@ -728,29 +743,15 @@ namespace MWGui const std::string &name = ESM::MagicEffect::effectIdToString (id); std::string icon = effect->mIcon; - - int slashPos = icon.find("\\"); + int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); - - icon[icon.size()-3] = 'd'; - icon[icon.size()-2] = 'd'; - icon[icon.size()-1] = 's'; - - icon = "icons\\" + icon; - - std::vector schools; - schools.push_back ("#{sSchoolAlteration}"); - schools.push_back ("#{sSchoolConjuration}"); - schools.push_back ("#{sSchoolDestruction}"); - schools.push_back ("#{sSchoolIllusion}"); - schools.push_back ("#{sSchoolMysticism}"); - schools.push_back ("#{sSchoolRestoration}"); + icon = Misc::ResourceHelpers::correctIconPath(icon); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "MagicEffectToolTip"); widget->setUserString("Caption_MagicEffectName", "#{" + name + "}"); widget->setUserString("Caption_MagicEffectDescription", effect->mDescription); - widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + schools[effect->mData.mSchool]); + widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + sSchoolNames[effect->mData.mSchool]); widget->setUserString("ImageTexture_MagicEffectImage", icon); } diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 4e73cc5551..ffbb35e047 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -66,6 +66,9 @@ namespace MWGui static std::string getCountString(const int value); ///< @return blank string if count is 1, or else " (value)" + static std::string getCellRefString(const MWWorld::CellRef& cellref); + ///< Returns a string containing debug tooltip information about the given cellref. + // these do not create an actual tooltip, but they fill in the data that is required so the tooltip // system knows what to show in case this widget is hovered static void createSkillToolTip(MyGUI::Widget* widget, int skillId); @@ -81,8 +84,6 @@ namespace MWGui MWWorld::Ptr mFocusObject; - void findImageExtension(std::string& image); - MyGUI::IntSize getToolTipViaPtr (bool image=true); ///< @return requested tooltip size @@ -95,6 +96,8 @@ namespace MWGui /// Adjust position for a tooltip so that it doesn't leave the screen and does not obscure the mouse cursor void position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize); + static std::string sSchoolNames[6]; + int mHorizontalScrollIndex; diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index 5fdf604dac..e1283c89d9 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -93,6 +93,21 @@ namespace MWGui unborrowImpl(item, count, mBorrowedFromUs); } + void TradeItemModel::adjustEncumbrance(float &encumbrance) + { + for (std::vector::iterator it = mBorrowedToUs.begin(); it != mBorrowedToUs.end(); ++it) + { + MWWorld::Ptr item = it->mBase; + encumbrance += item.getClass().getWeight(item) * it->mCount; + } + for (std::vector::iterator it = mBorrowedFromUs.begin(); it != mBorrowedFromUs.end(); ++it) + { + MWWorld::Ptr item = it->mBase; + encumbrance -= item.getClass().getWeight(item) * it->mCount; + } + encumbrance = std::max(0.f, encumbrance); + } + void TradeItemModel::abort() { mBorrowedFromUs.clear(); @@ -104,7 +119,7 @@ namespace MWGui return mBorrowedToUs; } - void TradeItemModel::transferItems() + void TradeItemModel::transferItems(const MWWorld::Ptr& transferFrom) { std::vector::iterator it = mBorrowedToUs.begin(); for (; it != mBorrowedToUs.end(); ++it) @@ -120,9 +135,11 @@ namespace MWGui if (i == sourceModel->getItemCount()) throw std::runtime_error("The borrowed item disappeared"); - // reset owner while copying, but only for items bought by the player - bool setNewOwner = (mMerchant.isEmpty()); const ItemStack& item = sourceModel->getItem(i); + + bool setNewOwner = Misc::StringUtils::ciEqual(item.mBase.getCellRef().getOwner(), transferFrom.getCellRef().getRefId()) + || item.mBase.getCellRef().getOwner().empty(); + // copy the borrowed items to our model copyItem(item, it->mCount, setNewOwner); // then remove them from the source model @@ -154,11 +171,8 @@ namespace MWGui continue; // Bound items may not be bought - if (item.mBase.getCellRef().getRefId().size() > 6 - && item.mBase.getCellRef().getRefId().substr(0,6) == "bound_") - { + if (item.mFlags & ItemStack::Flag_Bound) continue; - } // don't show equipped items if(mMerchant.getClass().hasInventoryStore(mMerchant)) diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp index 5cfaaafc76..c463bf40bd 100644 --- a/apps/openmw/mwgui/tradeitemmodel.hpp +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -30,10 +30,15 @@ namespace MWGui void returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count); /// Permanently transfers items that were borrowed to us from another model to this model - void transferItems (); + /// @param transferFrom the actor that lent us the items + void transferItems (const MWWorld::Ptr& transferFrom); /// Aborts trade void abort(); + /// Adjusts the given encumbrance by adding weight for items that have been lent to us, + /// and removing weight for items we've lent to someone else. + void adjustEncumbrance (float& encumbrance); + std::vector getItemsBorrowedToUs(); private: diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 19187cde1f..081a1e2c28 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" @@ -22,6 +24,7 @@ #include "tradeitemmodel.hpp" #include "countdialog.hpp" #include "dialogue.hpp" +#include "controllers.hpp" namespace { @@ -44,8 +47,6 @@ namespace MWGui TradeWindow::TradeWindow() : WindowBase("openmw_trade_window.layout") , mCurrentBalance(0) - , mBalanceButtonsState(BBS_None) - , mBalanceChangePause(0.0) , mItemToSell(-1) , mTradeModel(NULL) , mSortModel(NULL) @@ -87,7 +88,7 @@ namespace MWGui mDecreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onDecreaseButtonPressed); mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); - mTotalBalance->eventEditTextChange += MyGUI::newDelegate(this, &TradeWindow::onBalanceEdited); + mTotalBalance->eventValueChanged += MyGUI::newDelegate(this, &TradeWindow::onBalanceValueChanged); setCoord(400, 0, 400, 300); } @@ -99,8 +100,6 @@ namespace MWGui mCurrentBalance = 0; mCurrentMerchantOffer = 0; - restock(); - std::vector itemSources; MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); @@ -141,7 +140,7 @@ namespace MWGui mFilterMagic->setStateSelected(false); mFilterMisc->setStateSelected(false); - static_cast(_sender)->setStateSelected(true); + _sender->castType()->setStateSelected(true); mItemView->update(); } @@ -242,21 +241,6 @@ namespace MWGui } } - void TradeWindow::onFrame(float frameDuration) - { - if (!mMainWidget->getVisible() || mBalanceButtonsState == BBS_None) - return; - - mBalanceChangePause -= frameDuration; - if (mBalanceChangePause < 0.0) { - mBalanceChangePause += sBalanceChangeInterval; - if (mBalanceButtonsState == BBS_Increase) - onIncreaseButtonTriggered(); - else if (mBalanceButtonsState == BBS_Decrease) - onDecreaseButtonTriggered(); - } - } - void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender) { TradeItemModel* playerItemModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); @@ -310,7 +294,7 @@ namespace MWGui it->mBase.getClass().getValue(it->mBase) * it->mCount); onCancelButtonClicked(mCancelButton); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } @@ -342,12 +326,12 @@ namespace MWGui const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); - float a1 = std::min(player.getClass().getSkill(player, ESM::Skill::Mercantile), 100); - float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); - float c1 = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d1 = std::min(mPtr.getClass().getSkill(mPtr, ESM::Skill::Mercantile), 100); - float e1 = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); - float f1 = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); + float a1 = player.getClass().getSkill(player, ESM::Skill::Mercantile); + float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); + float d1 = mPtr.getClass().getSkill(mPtr, ESM::Skill::Mercantile); + float e1 = 0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float f1 = 0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(); float pcTerm = (clampedDisposition - 50 + a1 + b1 + c1) * playerStats.getFatigueTerm(); float npcTerm = (d1 + e1 + f1) * sellerStats.getFatigueTerm(); @@ -370,7 +354,15 @@ namespace MWGui } //skill use! - player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0); + float skillGain = 0.f; + int finalPrice = std::abs(mCurrentBalance); + int initialMerchantOffer = std::abs(mCurrentMerchantOffer); + if (!buying && (finalPrice > initialMerchantOffer) && finalPrice > 0) + skillGain = int(100 * (finalPrice - initialMerchantOffer) / float(finalPrice)); + else if (buying && (finalPrice < initialMerchantOffer) && initialMerchantOffer > 0) + skillGain = int(100 * (initialMerchantOffer - finalPrice) / float(initialMerchantOffer)); + + player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); } int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt(); @@ -378,8 +370,8 @@ namespace MWGui MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterSuccessDisposition); // make the item transfer - mTradeModel->transferItems(); - playerItemModel->transferItems(); + mTradeModel->transferItems(player); + playerItemModel->transferItems(mPtr); // transfer the gold if (mCurrentBalance != 0) @@ -409,36 +401,48 @@ namespace MWGui updateLabels(); } + void TradeWindow::addRepeatController(MyGUI::Widget *widget) + { + MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerRepeatEvent::getClassTypeName()); + Controllers::ControllerRepeatEvent* controller = item->castType(); + controller->eventRepeatClick += MyGUI::newDelegate(this, &TradeWindow::onRepeatClick); + controller->setRepeat(sBalanceChangeInitialPause, sBalanceChangeInterval); + MyGUI::ControllerManager::getInstance().addItem(widget, controller); + } + void TradeWindow::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { - mBalanceButtonsState = BBS_Increase; - mBalanceChangePause = sBalanceChangeInitialPause; + addRepeatController(_sender); onIncreaseButtonTriggered(); } void TradeWindow::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { - mBalanceButtonsState = BBS_Decrease; - mBalanceChangePause = sBalanceChangeInitialPause; + addRepeatController(_sender); onDecreaseButtonTriggered(); } + void TradeWindow::onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller) + { + if (widget == mIncreaseButton) + onIncreaseButtonTriggered(); + else if (widget == mDecreaseButton) + onDecreaseButtonTriggered(); + } + void TradeWindow::onBalanceButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) { - mBalanceButtonsState = BBS_None; + MyGUI::ControllerManager::getInstance().removeItem(_sender); } - void TradeWindow::onBalanceEdited(MyGUI::EditBox *_sender) + void TradeWindow::onBalanceValueChanged(int value) { - try - { - unsigned int count = boost::lexical_cast(_sender->getCaption()); - mCurrentBalance = count * (mCurrentBalance >= 0 ? 1 : -1); - updateLabels(); - } - catch (std::bad_cast&) - { - } + // Entering a "-" sign inverts the buying/selling state + mCurrentBalance = (mCurrentBalance >= 0 ? 1 : -1) * value; + updateLabels(); + + if (value != std::abs(value)) + mTotalBalance->setValue(std::abs(value)); } void TradeWindow::onIncreaseButtonTriggered() @@ -465,35 +469,49 @@ namespace MWGui if (mCurrentBalance > 0) { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalSold}"); - mTotalBalance->setCaption(boost::lexical_cast(mCurrentBalance)); } else { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); - mTotalBalance->setCaption(boost::lexical_cast(-mCurrentBalance)); } + mTotalBalance->setValue(std::abs(mCurrentBalance)); + mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + boost::lexical_cast(getMerchantGold())); } + void TradeWindow::updateOffer() + { + TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + + int merchantOffer = 0; + + std::vector playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); + for (std::vector::const_iterator it = playerBorrowed.begin(); it != playerBorrowed.end(); ++it) + { + merchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(it->mBase, it->mCount), true); + } + + std::vector merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); + for (std::vector::const_iterator it = merchantBorrowed.begin(); it != merchantBorrowed.end(); ++it) + { + merchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(it->mBase, it->mCount), false); + } + + int diff = merchantOffer - mCurrentMerchantOffer; + mCurrentMerchantOffer = merchantOffer; + mCurrentBalance += diff; + updateLabels(); + } + void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), boughtItem); - - mCurrentBalance += diff; - mCurrentMerchantOffer += diff; - - updateLabels(); + updateOffer(); } void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), !soldItem); - - mCurrentBalance -= diff; - mCurrentMerchantOffer -= diff; - - updateLabels(); + updateOffer(); } void TradeWindow::onReferenceUnavailable() @@ -509,32 +527,11 @@ namespace MWGui return merchantGold; } - void TradeWindow::restock() - { - MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); - - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) - { - sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); - - mPtr.getClass().restock(mPtr); - - // Also restock any containers owned by this merchant, which are also available to buy in the trade window - std::vector itemSources; - MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) - { - it->getClass().restock(*it); - } - - sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); - } - } - void TradeWindow::resetReference() { ReferenceInterface::resetReference(); mItemView->setModel(NULL); + mTradeModel = NULL; + mSortModel = NULL; } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index b487a8870e..47de9215a2 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -3,10 +3,9 @@ #include "container.hpp" -namespace MyGUI +namespace Gui { - class Gui; - class Widget; + class NumericEditBox; } namespace MWGui @@ -28,8 +27,6 @@ namespace MWGui void startTrade(const MWWorld::Ptr& actor); - void onFrame(float frameDuration); - void borrowItem (int index, size_t count); void returnItem (int index, size_t count); @@ -56,7 +53,7 @@ namespace MWGui MyGUI::Button* mIncreaseButton; MyGUI::Button* mDecreaseButton; MyGUI::TextBox* mTotalBalanceLabel; - MyGUI::EditBox* mTotalBalance; + Gui::NumericEditBox* mTotalBalance; MyGUI::Widget* mBottomPane; @@ -71,17 +68,11 @@ namespace MWGui int mCurrentBalance; int mCurrentMerchantOffer; - enum BalanceButtonsState { - BBS_None, - BBS_Increase, - BBS_Decrease - } mBalanceButtonsState; - /// pause before next balance change will trigger while user holds +/- button pressed - float mBalanceChangePause; - void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance + void updateOffer(); + void onItemSelected (int index); void sellItem (MyGUI::Widget* sender, int count); @@ -92,7 +83,10 @@ namespace MWGui void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onBalanceEdited(MyGUI::EditBox* _sender); + void onBalanceValueChanged(int value); + void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); + + void addRepeatController(MyGUI::Widget* widget); void onIncreaseButtonTriggered(); void onDecreaseButtonTriggered(); @@ -104,8 +98,6 @@ namespace MWGui virtual void onReferenceUnavailable(); int getMerchantGold(); - - void restock(); }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6463db3d79..f67376c939 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -2,12 +2,11 @@ #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -159,16 +158,19 @@ namespace MWGui // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + npcStats.setGoldPool(npcStats.getGoldPool() + price); + // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); // advance time MWBase::Environment::get().getWorld ()->advanceTime (2); MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getMechanicsManager()->rest(false); - MWBase::Environment::get().getWorld ()->getFader()->fadeOut(0.25); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.25); mFadeTimeRemaining = 0.5; } @@ -180,6 +182,6 @@ namespace MWGui mFadeTimeRemaining -= dt; if (mFadeTimeRemaining <= 0) - MWBase::Environment::get().getWorld ()->getFader()->fadeIn(0.25); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.25); } } diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 9aa75173a6..6a0c99b8a3 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -4,13 +4,14 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" + +#include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -25,7 +26,6 @@ namespace MWGui TravelWindow::TravelWindow() : WindowBase("openmw_travel_window.layout") , mCurrentY(0) - , mLastPos(0) { setCoord(0, 0, 450, 300); @@ -126,7 +126,10 @@ namespace MWGui } updateLabels(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mDestinationsView->setVisibleVScroll(false); mDestinationsView->setCanvasSize (MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY))); + mDestinationsView->setVisibleVScroll(true); } void TravelWindow::onTravelButtonClick(MyGUI::Widget* _sender) @@ -147,7 +150,11 @@ namespace MWGui player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + + MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); bool interior = _sender->getUserString("interior") == "y"; @@ -164,14 +171,15 @@ namespace MWGui MWBase::Environment::get().getWorld()->advanceTime(hours); } + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + // Teleports any followers, too. MWWorld::ActionTeleport action(interior ? cellname : "", pos); action.execute(player); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0); - MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(1); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(1); } void TravelWindow::onCancelButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 4328f7ac28..3f19492562 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -39,7 +39,7 @@ namespace MWGui void onMouseWheel(MyGUI::Widget* _sender, int _rel); void addDestination(const std::string& name, ESM::Position pos, bool interior); void clearDestinations(); - int mLastPos,mCurrentY; + int mCurrentY; static const int sLineHeight; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 9c7757af93..f95ec56751 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -128,7 +128,7 @@ namespace MWGui MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); - world->getFader ()->fadeOut(0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); setVisible(false); mProgressBar.setVisible (true); @@ -179,8 +179,7 @@ namespace MWGui { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified()) - && (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) + bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player); bool werewolf = npcstats.isWerewolf(); @@ -193,7 +192,10 @@ namespace MWGui mSleeping = canRest; - dynamic_cast(mMainWidget)->notifyChildrenSizeChanged(); + Gui::Box* box = dynamic_cast(mMainWidget); + if (box == NULL) + throw std::runtime_error("main widget must be a box"); + box->notifyChildrenSizeChanged(); center(); } @@ -243,7 +245,7 @@ namespace MWGui void WaitDialog::stopWaiting () { - MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2); mProgressBar.setVisible (false); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index de48a9d3e8..d7bc2c96e9 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -20,21 +22,6 @@ namespace MWGui { namespace Widgets { - - /* Helper functions */ - - /* - * Fixes the filename of a texture path to use the correct .dds extension. - * This is needed on some ESM entries which point to a .tga file instead. - */ - void fixTexturePath(std::string &path) - { - int offset = path.rfind("."); - if (offset < 0) - return; - path.replace(offset, path.length() - offset, ".dds"); - } - /* MWSkill */ MWSkill::MWSkill() @@ -72,18 +59,18 @@ namespace MWGui { if (mSkillId == ESM::Skill::Length) { - static_cast(mSkillNameWidget)->setCaption(""); + mSkillNameWidget->setCaption(""); } else { const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mSkillId], ""); - static_cast(mSkillNameWidget)->setCaption(name); + mSkillNameWidget->setCaption(name); } } if (mSkillValueWidget) { SkillValue::Type modified = mValue.getModified(), base = mValue.getBase(); - static_cast(mSkillValueWidget)->setCaption(boost::lexical_cast(modified)); + mSkillValueWidget->setCaption(boost::lexical_cast(modified)); if (modified > base) mSkillValueWidget->_setWidgetState("increased"); else if (modified < base) @@ -158,7 +145,7 @@ namespace MWGui { if (mId < 0 || mId >= 8) { - static_cast(mAttributeNameWidget)->setCaption(""); + mAttributeNameWidget->setCaption(""); } else { @@ -173,13 +160,13 @@ namespace MWGui "sAttributeLuck" }; const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(attributes[mId], ""); - static_cast(mAttributeNameWidget)->setCaption(name); + mAttributeNameWidget->setCaption(name); } } if (mAttributeValueWidget) { int modified = mValue.getModified(), base = mValue.getBase(); - static_cast(mAttributeValueWidget)->setCaption(boost::lexical_cast(modified)); + mAttributeValueWidget->setCaption(boost::lexical_cast(modified)); if (modified > base) mAttributeValueWidget->_setWidgetState("increased"); else if (modified < base) @@ -238,11 +225,10 @@ namespace MWGui const ESM::Spell *spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - MWSpellEffectPtr effect = NULL; std::vector::const_iterator end = spell->mEffects.mList.end(); for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != end; ++it) { - effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); + MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); SpellEffectParams params; params.mEffectID = it->mEffectID; params.mSkill = it->mSkill; @@ -269,9 +255,9 @@ namespace MWGui const ESM::Spell *spell = store.get().search(mId); if (spell) - static_cast(mSpellNameWidget)->setCaption(spell->mName); + mSpellNameWidget->setCaption(spell->mName); else - static_cast(mSpellNameWidget)->setCaption(""); + mSpellNameWidget->setCaption(""); } } @@ -323,7 +309,7 @@ namespace MWGui // ... then adjust the size for all widgets for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) { - effect = static_cast(*it); + effect = (*it)->castType(); bool needcenter = center && (maxwidth > effect->getRequestedWidth()); int diff = maxwidth - effect->getRequestedWidth(); if (needcenter) @@ -483,12 +469,10 @@ namespace MWGui } } - static_cast(mTextWidget)->setCaptionWithReplacing(spellLine); + mTextWidget->setCaptionWithReplacing(spellLine); mRequestedWidth = mTextWidget->getTextSize().width + 24; - std::string path = std::string("icons\\") + magicEffect->mIcon; - fixTexturePath(path); - mImageWidget->setImageTexture(path); + mImageWidget->setImageTexture(Misc::ResourceHelpers::correctIconPath(magicEffect->mIcon)); } MWSpellEffect::~MWSpellEffect() @@ -530,13 +514,13 @@ namespace MWGui { std::stringstream out; out << mValue << "/" << mMax; - static_cast(mBarTextWidget)->setCaption(out.str().c_str()); + mBarTextWidget->setCaption(out.str().c_str()); } } void MWDynamicStat::setTitle(const std::string& text) { if (mTextWidget) - static_cast(mTextWidget)->setCaption(text); + mTextWidget->setCaption(text); } MWDynamicStat::~MWDynamicStat() @@ -552,401 +536,6 @@ namespace MWGui assignWidget(mBarTextWidget, "BarText"); } - - - - // --------------------------------------------------------------------------------------------------------------------- - - void AutoSizedWidget::notifySizeChange (MyGUI::Widget* w) - { - if (w->getParent () != 0) - { - Box* b = dynamic_cast(w->getParent()); - if (b) - b->notifyChildrenSizeChanged (); - else - { - if (mExpandDirection == MyGUI::Align::Left) - { - int hdiff = getRequestedSize ().width - w->getSize().width; - w->setPosition(w->getPosition() - MyGUI::IntPoint(hdiff, 0)); - } - w->setSize(getRequestedSize ()); - } - } - } - - - MyGUI::IntSize AutoSizedTextBox::getRequestedSize() - { - return getTextSize(); - } - - void AutoSizedTextBox::setCaption(const MyGUI::UString& _value) - { - TextBox::setCaption(_value); - - notifySizeChange (this); - } - - void AutoSizedTextBox::setPropertyOverride(const std::string& _key, const std::string& _value) - { - if (_key == "ExpandDirection") - { - mExpandDirection = MyGUI::Align::parse (_value); - } - else - { - TextBox::setPropertyOverride (_key, _value); - } - } - - MyGUI::IntSize AutoSizedEditBox::getRequestedSize() - { - if (getAlign().isHStretch()) - throw std::runtime_error("AutoSizedEditBox can't have HStretch align (" + getName() + ")"); - return MyGUI::IntSize(getSize().width, getTextSize().height); - } - - void AutoSizedEditBox::setCaption(const MyGUI::UString& _value) - { - EditBox::setCaption(_value); - - notifySizeChange (this); - } - - void AutoSizedEditBox::setPropertyOverride(const std::string& _key, const std::string& _value) - { - if (_key == "ExpandDirection") - { - mExpandDirection = MyGUI::Align::parse (_value); - } - else - { - EditBox::setPropertyOverride (_key, _value); - } - } - - - MyGUI::IntSize AutoSizedButton::getRequestedSize() - { - MyGUI::IntSize padding(24, 8); - if (isUserString("TextPadding")) - padding = MyGUI::IntSize::parse(getUserString("TextPadding")); - - MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(padding.width,padding.height); - return size; - } - - void AutoSizedButton::setCaption(const MyGUI::UString& _value) - { - Button::setCaption(_value); - - notifySizeChange (this); - } - - void AutoSizedButton::setPropertyOverride(const std::string& _key, const std::string& _value) - { - if (_key == "ExpandDirection") - { - mExpandDirection = MyGUI::Align::parse (_value); - } - else - { - Button::setPropertyOverride (_key, _value); - } - } - - Box::Box() - : mSpacing(4) - , mPadding(0) - , mAutoResize(false) - { - - } - - void Box::notifyChildrenSizeChanged () - { - align(); - } - - void Box::_setPropertyImpl(const std::string& _key, const std::string& _value) - { - if (_key == "Spacing") - mSpacing = MyGUI::utility::parseValue(_value); - else if (_key == "Padding") - mPadding = MyGUI::utility::parseValue(_value); - else if (_key == "AutoResize") - mAutoResize = MyGUI::utility::parseValue(_value); - } - - void HBox::align () - { - unsigned int count = getChildCount (); - size_t h_stretched_count = 0; - int total_width = 0; - int total_height = 0; - std::vector< std::pair > sizes; - sizes.resize(count); - - for (unsigned int i = 0; i < count; ++i) - { - MyGUI::Widget* w = getChildAt(i); - bool hstretch = w->getUserString ("HStretch") == "true"; - bool hidden = w->getUserString("Hidden") == "true"; - if (hidden) - continue; - h_stretched_count += hstretch; - AutoSizedWidget* aw = dynamic_cast(w); - if (aw) - { - sizes[i] = std::make_pair(aw->getRequestedSize (), hstretch); - total_width += aw->getRequestedSize ().width; - total_height = std::max(total_height, aw->getRequestedSize ().height); - } - else - { - sizes[i] = std::make_pair(w->getSize(), hstretch); - total_width += w->getSize().width; - if (!(w->getUserString("VStretch") == "true")) - total_height = std::max(total_height, w->getSize().height); - } - - if (i != count-1) - total_width += mSpacing; - } - - if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height)) - { - setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2)); - return; - } - - - int curX = 0; - for (unsigned int i = 0; i < count; ++i) - { - if (i == 0) - curX += mPadding; - - MyGUI::Widget* w = getChildAt(i); - - bool hidden = w->getUserString("Hidden") == "true"; - if (hidden) - continue; - - bool vstretch = w->getUserString ("VStretch") == "true"; - int max_height = getSize().height - mPadding*2; - int height = vstretch ? max_height : sizes[i].first.height; - - MyGUI::IntCoord widgetCoord; - widgetCoord.left = curX; - widgetCoord.top = mPadding + (getSize().height-mPadding*2 - height) / 2; - int width = sizes[i].second ? sizes[i].first.width + (getSize().width-mPadding*2 - total_width)/h_stretched_count - : sizes[i].first.width; - widgetCoord.width = width; - widgetCoord.height = height; - w->setCoord(widgetCoord); - curX += width; - - if (i != count-1) - curX += mSpacing; - } - } - - void HBox::setPropertyOverride(const std::string& _key, const std::string& _value) - { - Box::_setPropertyImpl (_key, _value); - } - - void HBox::setSize (const MyGUI::IntSize& _value) - { - MyGUI::Widget::setSize (_value); - align(); - } - - void HBox::setCoord (const MyGUI::IntCoord& _value) - { - MyGUI::Widget::setCoord (_value); - align(); - } - - void HBox::onWidgetCreated(MyGUI::Widget* _widget) - { - align(); - } - - MyGUI::IntSize HBox::getRequestedSize () - { - MyGUI::IntSize size(0,0); - for (unsigned int i = 0; i < getChildCount (); ++i) - { - bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; - if (hidden) - continue; - - AutoSizedWidget* w = dynamic_cast(getChildAt(i)); - if (w) - { - MyGUI::IntSize requested = w->getRequestedSize (); - size.height = std::max(size.height, requested.height); - size.width = size.width + requested.width; - if (i != getChildCount()-1) - size.width += mSpacing; - } - else - { - MyGUI::IntSize requested = getChildAt(i)->getSize (); - size.height = std::max(size.height, requested.height); - - if (getChildAt(i)->getUserString("HStretch") != "true") - size.width = size.width + requested.width; - - if (i != getChildCount()-1) - size.width += mSpacing; - } - size.height += mPadding*2; - size.width += mPadding*2; - } - return size; - } - - - - - void VBox::align () - { - unsigned int count = getChildCount (); - size_t v_stretched_count = 0; - int total_height = 0; - int total_width = 0; - std::vector< std::pair > sizes; - sizes.resize(count); - for (unsigned int i = 0; i < count; ++i) - { - MyGUI::Widget* w = getChildAt(i); - - bool hidden = w->getUserString("Hidden") == "true"; - if (hidden) - continue; - - bool vstretch = w->getUserString ("VStretch") == "true"; - v_stretched_count += vstretch; - AutoSizedWidget* aw = dynamic_cast(w); - if (aw) - { - sizes[i] = std::make_pair(aw->getRequestedSize (), vstretch); - total_height += aw->getRequestedSize ().height; - total_width = std::max(total_width, aw->getRequestedSize ().width); - } - else - { - sizes[i] = std::make_pair(w->getSize(), vstretch); - total_height += w->getSize().height; - - if (!(w->getUserString("HStretch") == "true")) - total_width = std::max(total_width, w->getSize().width); - } - - if (i != count-1) - total_height += mSpacing; - } - - if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height)) - { - setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2)); - return; - } - - - int curY = 0; - for (unsigned int i = 0; i < count; ++i) - { - if (i==0) - curY += mPadding; - - MyGUI::Widget* w = getChildAt(i); - - bool hidden = w->getUserString("Hidden") == "true"; - if (hidden) - continue; - - bool hstretch = w->getUserString ("HStretch") == "true"; - int maxWidth = getSize().width - mPadding*2; - int width = hstretch ? maxWidth : sizes[i].first.width; - - MyGUI::IntCoord widgetCoord; - widgetCoord.top = curY; - widgetCoord.left = mPadding + (getSize().width-mPadding*2 - width) / 2; - int height = sizes[i].second ? sizes[i].first.height + (getSize().height-mPadding*2 - total_height)/v_stretched_count - : sizes[i].first.height; - widgetCoord.height = height; - widgetCoord.width = width; - w->setCoord(widgetCoord); - curY += height; - - if (i != count-1) - curY += mSpacing; - } - } - - void VBox::setPropertyOverride(const std::string& _key, const std::string& _value) - { - Box::_setPropertyImpl (_key, _value); - } - - void VBox::setSize (const MyGUI::IntSize& _value) - { - MyGUI::Widget::setSize (_value); - align(); - } - - void VBox::setCoord (const MyGUI::IntCoord& _value) - { - MyGUI::Widget::setCoord (_value); - align(); - } - - MyGUI::IntSize VBox::getRequestedSize () - { - MyGUI::IntSize size(0,0); - for (unsigned int i = 0; i < getChildCount (); ++i) - { - bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; - if (hidden) - continue; - - AutoSizedWidget* w = dynamic_cast(getChildAt(i)); - if (w) - { - MyGUI::IntSize requested = w->getRequestedSize (); - size.width = std::max(size.width, requested.width); - size.height = size.height + requested.height; - if (i != getChildCount()-1) - size.height += mSpacing; - } - else - { - MyGUI::IntSize requested = getChildAt(i)->getSize (); - size.width = std::max(size.width, requested.width); - - if (getChildAt(i)->getUserString("VStretch") != "true") - size.height = size.height + requested.height; - - if (i != getChildCount()-1) - size.height += mSpacing; - } - size.height += mPadding*2; - size.width += mPadding*2; - } - return size; - } - - void VBox::onWidgetCreated(MyGUI::Widget* _widget) - { - align(); - } - MWScrollBar::MWScrollBar() : mEnableRepeat(true) , mRepeatTriggerTime(0.5) @@ -975,22 +564,6 @@ namespace MWGui } } - void MWScrollBar::setEnableRepeat(bool enable) - { - mEnableRepeat = enable; - } - - bool MWScrollBar::getEnableRepeat() - { - return mEnableRepeat; - } - - void MWScrollBar::getRepeat(float &trigger, float &step) - { - trigger = mRepeatTriggerTime; - step = mRepeatStepTime; - } - void MWScrollBar::setRepeat(float trigger, float step) { mRepeatTriggerTime = trigger; @@ -1027,8 +600,8 @@ namespace MWGui void MWScrollBar::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { mIsIncreasing = false; - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatClick::getClassTypeName()); - MWGui::Controllers::ControllerRepeatClick* controller = item->castType(); + MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatEvent::getClassTypeName()); + MWGui::Controllers::ControllerRepeatEvent* controller = item->castType(); controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); @@ -1043,8 +616,8 @@ namespace MWGui void MWScrollBar::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { mIsIncreasing = true; - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatClick::getClassTypeName()); - MWGui::Controllers::ControllerRepeatClick* controller = item->castType(); + MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatEvent::getClassTypeName()); + MWGui::Controllers::ControllerRepeatEvent* controller = item->castType(); controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index adc56f423f..aae28a6862 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -122,8 +122,8 @@ namespace MWGui ESM::Skill::SkillEnum mSkillId; SkillValue mValue; - MyGUI::Widget* mSkillNameWidget; - MyGUI::Widget* mSkillValueWidget; + MyGUI::TextBox* mSkillNameWidget; + MyGUI::TextBox* mSkillValueWidget; }; typedef MWSkill* MWSkillPtr; @@ -162,8 +162,8 @@ namespace MWGui int mId; AttributeValue mValue; - MyGUI::Widget* mAttributeNameWidget; - MyGUI::Widget* mAttributeValueWidget; + MyGUI::TextBox* mAttributeNameWidget; + MyGUI::TextBox* mAttributeValueWidget; }; typedef MWAttribute* MWAttributePtr; @@ -293,123 +293,12 @@ namespace MWGui int mValue, mMax; MyGUI::TextBox* mTextWidget; - MyGUI::ProgressPtr mBarWidget; + MyGUI::ProgressBar* mBarWidget; MyGUI::TextBox* mBarTextWidget; }; typedef MWDynamicStat* MWDynamicStatPtr; - - - - - // --------------------------------------------------------------------------------------------------------------------- - - - - class AutoSizedWidget - { - public: - virtual MyGUI::IntSize getRequestedSize() = 0; - - protected: - void notifySizeChange(MyGUI::Widget* w); - - MyGUI::Align mExpandDirection; - }; - - class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox - { - MYGUI_RTTI_DERIVED( AutoSizedTextBox ) - - public: - virtual MyGUI::IntSize getRequestedSize(); - virtual void setCaption(const MyGUI::UString& _value); - - protected: - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - }; - - class AutoSizedEditBox : public AutoSizedWidget, public MyGUI::EditBox - { - MYGUI_RTTI_DERIVED( AutoSizedEditBox ) - - public: - virtual MyGUI::IntSize getRequestedSize(); - virtual void setCaption(const MyGUI::UString& _value); - - protected: - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - }; - - class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button - { - MYGUI_RTTI_DERIVED( AutoSizedButton ) - - public: - virtual MyGUI::IntSize getRequestedSize(); - virtual void setCaption(const MyGUI::UString& _value); - - protected: - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - }; - - /** - * @brief A container widget that automatically sizes its children - * @note the box being an AutoSizedWidget as well allows to put boxes inside a box - */ - class Box : public AutoSizedWidget - { - public: - Box(); - - void notifyChildrenSizeChanged(); - - protected: - virtual void align() = 0; - - virtual void _setPropertyImpl(const std::string& _key, const std::string& _value); - - int mSpacing; // how much space to put between elements - - int mPadding; // outer padding - - bool mAutoResize; // auto resize the box so that it exactly fits all elements - }; - - class HBox : public Box, public MyGUI::Widget - { - MYGUI_RTTI_DERIVED( HBox ) - - public: - virtual void setSize (const MyGUI::IntSize &_value); - virtual void setCoord (const MyGUI::IntCoord &_value); - - protected: - virtual void align(); - virtual MyGUI::IntSize getRequestedSize(); - - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - - virtual void onWidgetCreated(MyGUI::Widget* _widget); - }; - - class VBox : public Box, public MyGUI::Widget - { - MYGUI_RTTI_DERIVED( VBox) - - public: - virtual void setSize (const MyGUI::IntSize &_value); - virtual void setCoord (const MyGUI::IntCoord &_value); - - protected: - virtual void align(); - virtual MyGUI::IntSize getRequestedSize(); - - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - - virtual void onWidgetCreated(MyGUI::Widget* _widget); - }; - + // Should be removed when upgrading to MyGUI 3.2.2 (current git), it has ScrollBar autorepeat support class MWScrollBar : public MyGUI::ScrollBar { MYGUI_RTTI_DERIVED(MWScrollBar) @@ -418,9 +307,6 @@ namespace MWGui MWScrollBar(); virtual ~MWScrollBar(); - void setEnableRepeat(bool enable); - bool getEnableRepeat(); - void getRepeat(float &trigger, float &step); void setRepeat(float trigger, float step); protected: diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 4af0afc1f6..432510a3e4 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -23,6 +23,7 @@ void WindowBase::setVisible(bool visible) close(); // This is needed as invisible widgets can retain key focus. + // Remove for MyGUI 3.2.2 if (!visible) { MyGUI::Widget* keyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); @@ -96,11 +97,16 @@ void NoDrop::onFrame(float dt) if (mTransparent) { mWidget->setNeedMouseFocus(false); // Allow click-through - mWidget->setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5)); + setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5)); } else { mWidget->setNeedMouseFocus(true); - mWidget->setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5)); + setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5)); } } + +void NoDrop::setAlpha(float alpha) +{ + mWidget->setAlpha(alpha); +} diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 81073d419a..bf74c8bf0f 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -32,11 +32,6 @@ namespace MWGui ///Returns the visibility state of the window virtual bool isVisible(); void center(); - - /** Event : Dialog finished, OK button clicked.\n - signature : void method()\n - */ - EventHandle_WindowBase eventDone; }; @@ -60,6 +55,7 @@ namespace MWGui NoDrop(DragAndDrop* drag, MyGUI::Widget* widget); void onFrame(float dt); + virtual void setAlpha(float alpha); private: MyGUI::Widget* mWidget; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 668211db29..fabdf4dae8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -16,6 +16,11 @@ #include +#include + +#include +#include + #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" @@ -23,6 +28,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwmechanics/npcstats.hpp" + #include "../mwsound/soundmanagerimp.hpp" #include "console.hpp" @@ -61,10 +68,10 @@ #include "inventorywindow.hpp" #include "bookpage.hpp" #include "itemview.hpp" -#include "fontloader.hpp" #include "videowidget.hpp" #include "backgroundimage.hpp" #include "itemwidget.hpp" +#include "screenfader.hpp" namespace MWGui { @@ -72,7 +79,7 @@ namespace MWGui WindowManager::WindowManager( const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *ogre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding) + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap) : mConsoleOnlyScripts(consoleOnlyScripts) , mGuiManager(NULL) , mRendering(ogre) @@ -112,6 +119,7 @@ namespace MWGui , mCompanionWindow(NULL) , mVideoBackground(NULL) , mVideoWidget(NULL) + , mScreenFader(NULL) , mTranslationDataStorage (translationDataStorage) , mCharGen(NULL) , mInputBlocker(NULL) @@ -139,14 +147,16 @@ namespace MWGui , mTriangleCount(0) , mBatchCount(0) , mCurrentModals() + , mFallbackMap(fallbackMap) { // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); - mGui = mGuiManager->getGui(); + + MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Load fonts - FontLoader fontLoader (encoding); - fontLoader.loadAllFonts(); + Gui::FontLoader fontLoader (encoding); + fontLoader.loadAllFonts(exportFonts); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -155,13 +165,6 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -169,20 +172,15 @@ namespace MWGui BookPage::registerMyGUIComponents (); ItemView::registerComponents(); ItemWidget::registerComponents(); + Gui::registerAllWidgets(); - MyGUI::FactoryManager::getInstance().registerFactory("Controller"); + MyGUI::FactoryManager::getInstance().registerFactory("Controller"); + MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); - MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); - - // Get size info from the Gui object - int w = MyGUI::RenderManager::getInstance().getViewSize().width; - int h = MyGUI::RenderManager::getInstance().getViewSize().height; - mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow ()); - mLoadingScreen->onResChange (w,h); //set up the hardware cursor manager mCursorManager = new SFO::SDLCursorManager(); @@ -192,7 +190,6 @@ namespace MWGui MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - //SDL_ShowCursor(false); mCursorManager->setEnabled(true); @@ -209,6 +206,13 @@ namespace MWGui mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); mVideoWidget->setNeedMouseFocus(true); mVideoWidget->setNeedKeyFocus(true); + + // Removes default MyGUI system clipboard implementation, which supports windows only + MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); + MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); + + MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); + MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); } void WindowManager::initUI() @@ -217,17 +221,11 @@ namespace MWGui int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; - MyGUI::Widget* dragAndDropWidget = mGui->createWidgetT("Widget","",0,0,w,h,MyGUI::Align::Default,"DragAndDrop","DragAndDropWidget"); - dragAndDropWidget->setVisible(false); - mDragAndDrop = new DragAndDrop(); - mDragAndDrop->mIsOnDragAndDrop = false; - mDragAndDrop->mDraggedWidget = 0; - mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; mRecharge = new Recharge(); mMenu = new MainMenu(w,h); - mMap = new MapWindow(mDragAndDrop, ""); + mMap = new MapWindow(mCustomMarkers, mDragAndDrop, ""); trackWindow(mMap, "map"); mStatsWindow = new StatsWindow(mDragAndDrop); trackWindow(mStatsWindow, "stats"); @@ -245,7 +243,7 @@ namespace MWGui trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); trackWindow(mContainerWindow, "container"); - mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop); + mHud = new HUD(mCustomMarkers, mShowFPSLevel, mDragAndDrop); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); mBookWindow = new BookWindow(); @@ -267,8 +265,9 @@ namespace MWGui mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); + mScreenFader = new ScreenFader(); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows"); + mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"Overlay"); mHud->setVisible(mHudEnabled); @@ -357,6 +356,7 @@ namespace MWGui delete mCursorManager; delete mRecharge; delete mCompanionWindow; + delete mScreenFader; cleanupGarbage(); @@ -785,6 +785,7 @@ namespace MWGui } else { mMessageBoxManager->createInteractiveMessageBox(message, buttons); MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); + updateVisible(); } } @@ -845,7 +846,6 @@ namespace MWGui mHud->onFrame(frameDuration); mTrainingWindow->onFrame (frameDuration); - mTradeWindow->onFrame(frameDuration); mTrainingWindow->checkReferenceAvailable(); mDialogueWindow->checkReferenceAvailable(); @@ -857,6 +857,8 @@ namespace MWGui mCompanionWindow->checkReferenceAvailable(); mConsole->checkReferenceAvailable(); mCompanionWindow->onFrame(); + + mScreenFader->update(frameDuration); } void WindowManager::changeCell(MWWorld::CellStore* cell) @@ -905,6 +907,7 @@ namespace MWGui void WindowManager::setPlayerDir(const float x, const float y) { mMap->setPlayerDir(x,y); + mMap->setGlobalMapPlayerDir(x, y); mHud->setPlayerDir(x,y); } @@ -983,10 +986,14 @@ namespace MWGui std::string tokenToFind = "sCell="; size_t tokenLength = tokenToFind.length(); - if (tag.substr(0, tokenLength) == tokenToFind) + if (tag.compare(0, tokenLength, tokenToFind) == 0) { _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); } + else if (Gui::replaceTag(tag, _result, mFallbackMap)) + { + return; + } else { const ESM::GameSetting *setting = @@ -1018,7 +1025,6 @@ namespace MWGui { sizeVideo(x, y); mGuiManager->windowResized(); - mLoadingScreen->onResChange (x,y); if (!mHud) return; // UI not initialized yet @@ -1032,7 +1038,6 @@ namespace MWGui it->first->setSize(size); } - mHud->onResChange(x, y); mConsole->onResChange(x, y); mMenu->onResChange(x, y); mSettingsWindow->center(); @@ -1041,8 +1046,6 @@ namespace MWGui mBookWindow->center(); mQuickKeysMenu->center(); mSpellBuyingWindow->center(); - mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); - mInputBlocker->setSize(MyGUI::IntSize(x,y)); } void WindowManager::pushGuiMode(GuiMode mode) @@ -1204,8 +1207,6 @@ namespace MWGui mBatchCount = batchCount; } - MyGUI::Gui* WindowManager::getGui() const { return mGui; } - MWGui::DialogueWindow* WindowManager::getDialogueWindow() { return mDialogueWindow; } MWGui::ContainerWindow* WindowManager::getContainerWindow() { return mContainerWindow; } MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; } @@ -1347,12 +1348,6 @@ namespace MWGui return mSubtitlesEnabled; } - void WindowManager::toggleHud () - { - mHudEnabled = !mHudEnabled; - mHud->setVisible (mHudEnabled); - } - bool WindowManager::toggleGui() { mGuiEnabled = !mGuiEnabled; @@ -1443,8 +1438,13 @@ namespace MWGui void WindowManager::updatePlayer() { mInventoryWindow->updatePlayer(); + + const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getNpcStats(player).isWerewolf()) + forceHide((GuiWindow)(MWGui::GW_Inventory | MWGui::GW_Magic)); } + // Remove this method for MyGUI 3.2.2 void WindowManager::setKeyFocusWidget(MyGUI::Widget *widget) { if (widget == NULL) @@ -1531,6 +1531,10 @@ namespace MWGui mSelectedSpell.clear(); + mCustomMarkers.clear(); + + mForceHidden = GW_None; + mGuiModes.clear(); MWBase::Environment::get().getInputManager()->changeInputMode(false); updateVisible(); @@ -1550,6 +1554,14 @@ namespace MWGui writer.endRecord(ESM::REC_ASPL); progress.increaseProgress(); } + + for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + { + writer.startRecord(ESM::REC_MARK); + (*it).save(writer); + writer.endRecord(ESM::REC_MARK); + progress.increaseProgress(); + } } void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type) @@ -1563,12 +1575,19 @@ namespace MWGui reader.getSubNameIs("ID__"); mSelectedSpell = reader.getHString(); } + else if (type == ESM::REC_MARK) + { + CustomMarker marker; + marker.load(reader); + mCustomMarkers.addMarker(marker, false); + } } int WindowManager::countSavedGameRecords() const { return 1 // Global map + 1 // QuickKeysMenu + + mCustomMarkers.size() + (!mSelectedSpell.empty() ? 1 : 0); } @@ -1644,7 +1663,7 @@ namespace MWGui WindowModal* WindowManager::getCurrentModal() const { - if(mCurrentModals.size() > 0) + if(!mCurrentModals.empty()) return mCurrentModals.top(); else return NULL; @@ -1654,7 +1673,7 @@ namespace MWGui { // Only remove the top if it matches the current pointer. A lot of things hide their visibility before showing it, //so just popping the top would cause massive issues. - if(mCurrentModals.size() > 0) + if(!mCurrentModals.empty()) if(input == mCurrentModals.top()) mCurrentModals.pop(); } @@ -1687,4 +1706,47 @@ namespace MWGui updateVisible(); } + + void WindowManager::fadeScreenIn(const float time) + { + mScreenFader->fadeIn(time); + } + + void WindowManager::fadeScreenOut(const float time) + { + mScreenFader->fadeOut(time); + } + + void WindowManager::fadeScreenTo(const int percent, const float time) + { + mScreenFader->fadeTo(percent, time); + } + + void WindowManager::setScreenFactor(float factor) + { + mScreenFader->setFactor(factor); + } + + void WindowManager::onClipboardChanged(const std::string &_type, const std::string &_data) + { + if (_type == "Text") + SDL_SetClipboardText(MyGUI::TextIterator::getOnlyText(MyGUI::UString(_data)).asUTF8().c_str()); + } + + void WindowManager::onClipboardRequested(const std::string &_type, std::string &_data) + { + if (_type != "Text") + return; + char* text=0; + text = SDL_GetClipboardText(); + if (text) + { + // MyGUI's clipboard might still have color information, to retain that information, only set the new text + // if it actually changed (clipboard inserted by an external application) + if (MyGUI::TextIterator::getOnlyText(_data) != text) + _data = text; + } + SDL_free(text); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 8093d637e2..e7853b84f1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -12,6 +12,8 @@ #include "../mwbase/windowmanager.hpp" +#include "mapwindow.hpp" + #include #include @@ -86,6 +88,7 @@ namespace MWGui class CompanionWindow; class VideoWidget; class WindowModal; + class ScreenFader; class WindowManager : public MWBase::WindowManager { @@ -96,7 +99,7 @@ namespace MWGui WindowManager(const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding); + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap); virtual ~WindowManager(); void initUI(); @@ -158,8 +161,6 @@ namespace MWGui virtual MWGui::SpellWindow* getSpellWindow(); virtual MWGui::Console* getConsole(); - virtual MyGUI::Gui* getGui() const; - virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount); ///< Set value for the given ID. @@ -221,7 +222,6 @@ namespace MWGui virtual void showCrosshair(bool show); virtual bool getSubtitlesEnabled(); - virtual void toggleHud(); /// Turn visibility of *all* GUI elements on or off (HUD and all windows, except the console) virtual bool toggleGui(); @@ -324,6 +324,15 @@ namespace MWGui virtual void pinWindow (MWGui::GuiWindow window); + /// Fade the screen in, over \a time seconds + virtual void fadeScreenIn(const float time); + /// Fade the screen out to black, over \a time seconds + virtual void fadeScreenOut(const float time); + /// Fade the screen to a specified percentage of black, over \a time seconds + virtual void fadeScreenTo(const int percent, const float time); + /// Darken the screen by \a factor (1.0 = no darkening). Works independently from screen fading. + virtual void setScreenFactor (float factor); + private: bool mConsoleOnlyScripts; @@ -335,6 +344,9 @@ namespace MWGui std::stack mCurrentModals; + // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map window). + CustomMarkerCollection mCustomMarkers; + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; @@ -373,6 +385,7 @@ namespace MWGui CompanionWindow* mCompanionWindow; MyGUI::ImageBox* mVideoBackground; VideoWidget* mVideoWidget; + ScreenFader* mScreenFader; Translation::Storage& mTranslationDataStorage; Cursor* mSoftwareCursor; @@ -424,9 +437,17 @@ namespace MWGui unsigned int mTriangleCount; unsigned int mBatchCount; + std::map mFallbackMap; + /** - * Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string, - * so this method will retrieve the GMST with the name \a _tag and place the result in \a _result + * 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 + * #{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. + * #{fontcolourhtml=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, + * in the format "#xxxxxx" where x are hexadecimal numbers. Useful in an EditBox's caption to change the color of following text. */ void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result); @@ -437,6 +458,9 @@ namespace MWGui void onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); void sizeVideo(int screenWidth, int screenHeight); + + void onClipboardChanged(const std::string& _type, const std::string& _data); + void onClipboardRequested(const std::string& _type, std::string& _data); }; } diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 919d315f27..f9bbca6653 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -7,10 +7,21 @@ namespace MWGui WindowPinnableBase::WindowPinnableBase(const std::string& parLayout) : WindowBase(parLayout), mPinned(false) { - ExposedWindow* window = static_cast(mMainWidget); + ExposedWindow* window = mMainWidget->castType(); mPinButton = window->getSkinWidget ("Button"); mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked); + + MyGUI::Button* button = NULL; + MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); + for (MyGUI::VectorWidgetPtr::iterator it = widgets.begin(); it != widgets.end(); ++it) + { + if ((*it)->isUserString("HideWindowOnDoubleClick")) + button = (*it)->castType(); + } + + if (button) + button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowPinnableBase::onDoubleClick); } void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender) @@ -25,6 +36,11 @@ namespace MWGui onPinToggled(); } + void WindowPinnableBase::onDoubleClick(MyGUI::Widget *_sender) + { + onTitleDoubleClicked(); + } + void WindowPinnableBase::setPinned(bool pinned) { if (pinned != mPinned) diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index 3aad60988c..8b7bbefaf9 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -17,9 +17,11 @@ namespace MWGui private: void onPinButtonClicked(MyGUI::Widget* _sender); + void onDoubleClick(MyGUI::Widget* _sender); protected: virtual void onPinToggled() = 0; + virtual void onTitleDoubleClicked() = 0; MyGUI::Widget* mPinButton; bool mPinned; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 8d4c53921c..86b01cdc4e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -498,59 +498,25 @@ namespace MWInput void InputManager::keyPressed( const SDL_KeyboardEvent &arg ) { - // Cut, copy & paste - MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - if (focus) - { - MyGUI::EditBox* edit = focus->castType(false); - if (edit && !edit->getEditReadOnly()) - { - if (arg.keysym.sym == SDLK_v && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) - { - char* text = SDL_GetClipboardText(); - - if (text) - { - edit->insertText(MyGUI::UString(text), edit->getTextCursor()); - SDL_free(text); - } - } - if (arg.keysym.sym == SDLK_x && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) - { - // Discard color codes and other escape characters - std::string text = MyGUI::TextIterator::getOnlyText(edit->getTextSelection()); - if (text.length()) - { - SDL_SetClipboardText(text.c_str()); - edit->deleteTextSelection(); - } - } - } - if (edit && !edit->getEditStatic()) - { - if (arg.keysym.sym == SDLK_c && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) - { - // Discard color codes and other escape characters - std::string text = MyGUI::TextIterator::getOnlyText(edit->getTextSelection()); - if (text.length()) - SDL_SetClipboardText(text.c_str()); - } - } - } - + // HACK: to make Morrowind's default keybinding for the console work without printing an extra "^" upon closing + // This assumes that SDL_TextInput events always come *after* the key event + // (which is somewhat reasonable, and hopefully true for all SDL platforms) OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); + if (mInputBinder->getKeyBinding(mInputBinder->getControl(A_Console), ICS::Control::INCREASE) + == arg.keysym.scancode + && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console) + SDL_StopTextInput(); + bool consumed = false; if (kc != OIS::KC_UNASSIGNED) { + consumed = SDL_IsTextInputActive() && + ( !(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym)); // Little trick to check if key is printable bool guiFocus = MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); setPlayerControlsEnabled(!guiFocus); } - if (!mControlsDisabled) + if (!mControlsDisabled && !consumed) mInputBinder->keyPressed (arg); - - // Clear MyGUI's clipboard, so it doesn't interfere with our own clipboard implementation. - // We do not use MyGUI's clipboard manager because it doesn't support system clipboard integration with SDL. - MyGUI::ClipboardManager::getInstance().clearClipboardData("Text"); } void InputManager::textInput(const SDL_TextInputEvent &arg) @@ -588,9 +554,10 @@ namespace MWInput } setPlayerControlsEnabled(!guiMode); - mInputBinder->mousePressed (arg, id); - + // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible + if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings) + mInputBinder->mousePressed (arg, id); } void InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) @@ -686,23 +653,13 @@ namespace MWInput return; } - if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) { //Give access to the main menu when at a choice in dialogue - if(MWBase::Environment::get().getDialogueManager()->isInChoice()) { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - MWBase::Environment::get().getSoundManager()->pauseSounds (MWBase::SoundManager::Play_TypeSfx); - return; - } - } - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - MWBase::Environment::get().getSoundManager()->pauseSounds (MWBase::SoundManager::Play_TypeSfx); } else //Close current GUI { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); - MWBase::Environment::get().getSoundManager()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); } } @@ -813,16 +770,15 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - if((!MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) + if(MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Journal && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); } - else if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Journal) + else if(MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Journal)) { - MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Journal); } } @@ -914,41 +870,41 @@ namespace MWInput { // using hardcoded key defaults is inevitable, if we want the configuration files to stay valid // across different versions of OpenMW (in the case where another input action is added) - std::map defaultKeyBindings; + std::map defaultKeyBindings; //Gets the Keyvalue from the Scancode; gives the button in the same place reguardless of keyboard format - defaultKeyBindings[A_Activate] = SDL_GetKeyFromScancode(SDL_SCANCODE_SPACE); - defaultKeyBindings[A_MoveBackward] = SDL_GetKeyFromScancode(SDL_SCANCODE_S); - defaultKeyBindings[A_MoveForward] = SDL_GetKeyFromScancode(SDL_SCANCODE_W); - defaultKeyBindings[A_MoveLeft] = SDL_GetKeyFromScancode(SDL_SCANCODE_A); - defaultKeyBindings[A_MoveRight] = SDL_GetKeyFromScancode(SDL_SCANCODE_D); - defaultKeyBindings[A_ToggleWeapon] = SDL_GetKeyFromScancode(SDL_SCANCODE_F); - defaultKeyBindings[A_ToggleSpell] = SDL_GetKeyFromScancode(SDL_SCANCODE_R); - defaultKeyBindings[A_QuickKeysMenu] = SDL_GetKeyFromScancode(SDL_SCANCODE_F1); - defaultKeyBindings[A_Console] = SDL_GetKeyFromScancode(SDL_SCANCODE_F2); - defaultKeyBindings[A_Run] = SDL_GetKeyFromScancode(SDL_SCANCODE_LSHIFT); - defaultKeyBindings[A_Sneak] = SDL_GetKeyFromScancode(SDL_SCANCODE_LCTRL); - defaultKeyBindings[A_AutoMove] = SDL_GetKeyFromScancode(SDL_SCANCODE_Q); - defaultKeyBindings[A_Jump] = SDL_GetKeyFromScancode(SDL_SCANCODE_E); - defaultKeyBindings[A_Journal] = SDL_GetKeyFromScancode(SDL_SCANCODE_J); - defaultKeyBindings[A_Rest] = SDL_GetKeyFromScancode(SDL_SCANCODE_T); - defaultKeyBindings[A_GameMenu] = SDL_GetKeyFromScancode(SDL_SCANCODE_ESCAPE); - defaultKeyBindings[A_TogglePOV] = SDL_GetKeyFromScancode(SDL_SCANCODE_TAB); - defaultKeyBindings[A_QuickKey1] = SDL_GetKeyFromScancode(SDL_SCANCODE_1); - defaultKeyBindings[A_QuickKey2] = SDL_GetKeyFromScancode(SDL_SCANCODE_2); - defaultKeyBindings[A_QuickKey3] = SDL_GetKeyFromScancode(SDL_SCANCODE_3); - defaultKeyBindings[A_QuickKey4] = SDL_GetKeyFromScancode(SDL_SCANCODE_4); - defaultKeyBindings[A_QuickKey5] = SDL_GetKeyFromScancode(SDL_SCANCODE_5); - defaultKeyBindings[A_QuickKey6] = SDL_GetKeyFromScancode(SDL_SCANCODE_6); - defaultKeyBindings[A_QuickKey7] = SDL_GetKeyFromScancode(SDL_SCANCODE_7); - defaultKeyBindings[A_QuickKey8] = SDL_GetKeyFromScancode(SDL_SCANCODE_8); - defaultKeyBindings[A_QuickKey9] = SDL_GetKeyFromScancode(SDL_SCANCODE_9); - defaultKeyBindings[A_QuickKey10] = SDL_GetKeyFromScancode(SDL_SCANCODE_0); - defaultKeyBindings[A_Screenshot] = SDL_GetKeyFromScancode(SDL_SCANCODE_F12); - defaultKeyBindings[A_ToggleHUD] = SDL_GetKeyFromScancode(SDL_SCANCODE_F11); - defaultKeyBindings[A_AlwaysRun] = SDL_GetKeyFromScancode(SDL_SCANCODE_Y); - defaultKeyBindings[A_QuickSave] = SDL_GetKeyFromScancode(SDL_SCANCODE_F5); - defaultKeyBindings[A_QuickLoad] = SDL_GetKeyFromScancode(SDL_SCANCODE_F9); + defaultKeyBindings[A_Activate] = SDL_SCANCODE_SPACE; + defaultKeyBindings[A_MoveBackward] = SDL_SCANCODE_S; + defaultKeyBindings[A_MoveForward] = SDL_SCANCODE_W; + defaultKeyBindings[A_MoveLeft] = SDL_SCANCODE_A; + defaultKeyBindings[A_MoveRight] = SDL_SCANCODE_D; + defaultKeyBindings[A_ToggleWeapon] = SDL_SCANCODE_F; + defaultKeyBindings[A_ToggleSpell] = SDL_SCANCODE_R; + defaultKeyBindings[A_QuickKeysMenu] = SDL_SCANCODE_F1; + defaultKeyBindings[A_Console] = SDL_SCANCODE_GRAVE; + defaultKeyBindings[A_Run] = SDL_SCANCODE_LSHIFT; + defaultKeyBindings[A_Sneak] = SDL_SCANCODE_LCTRL; + defaultKeyBindings[A_AutoMove] = SDL_SCANCODE_Q; + defaultKeyBindings[A_Jump] = SDL_SCANCODE_E; + defaultKeyBindings[A_Journal] = SDL_SCANCODE_J; + defaultKeyBindings[A_Rest] = SDL_SCANCODE_T; + defaultKeyBindings[A_GameMenu] = SDL_SCANCODE_ESCAPE; + defaultKeyBindings[A_TogglePOV] = SDL_SCANCODE_TAB; + defaultKeyBindings[A_QuickKey1] = SDL_SCANCODE_1; + defaultKeyBindings[A_QuickKey2] = SDL_SCANCODE_2; + defaultKeyBindings[A_QuickKey3] = SDL_SCANCODE_3; + defaultKeyBindings[A_QuickKey4] = SDL_SCANCODE_4; + defaultKeyBindings[A_QuickKey5] = SDL_SCANCODE_5; + defaultKeyBindings[A_QuickKey6] = SDL_SCANCODE_6; + defaultKeyBindings[A_QuickKey7] = SDL_SCANCODE_7; + defaultKeyBindings[A_QuickKey8] = SDL_SCANCODE_8; + defaultKeyBindings[A_QuickKey9] = SDL_SCANCODE_9; + defaultKeyBindings[A_QuickKey10] = SDL_SCANCODE_0; + defaultKeyBindings[A_Screenshot] = SDL_SCANCODE_F12; + defaultKeyBindings[A_ToggleHUD] = SDL_SCANCODE_F11; + defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; + defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5; + defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9; std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; @@ -970,14 +926,14 @@ namespace MWInput } if (!controlExists || force || - ( mInputBinder->getKeyBinding (control, ICS::Control::INCREASE) == SDLK_UNKNOWN + ( mInputBinder->getKeyBinding (control, ICS::Control::INCREASE) == SDL_SCANCODE_UNKNOWN && mInputBinder->getMouseButtonBinding (control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS )) { clearAllBindings (control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end()) - mInputBinder->addKeyBinding(control, static_cast(defaultKeyBindings[i]), ICS::Control::INCREASE); + mInputBinder->addKeyBinding(control, defaultKeyBindings[i], ICS::Control::INCREASE); else if (defaultMouseButtonBindings.find(i) != defaultMouseButtonBindings.end()) mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } @@ -1036,8 +992,8 @@ namespace MWInput ICS::Control* c = mInputBinder->getChannel (action)->getAttachedControls ().front().control; - if (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE) != SDLK_UNKNOWN) - return mInputBinder->keyCodeToString (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE)); + if (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE) != SDL_SCANCODE_UNKNOWN) + return mInputBinder->scancodeToString (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE)); else if (mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) return "#{sMouse} " + boost::lexical_cast(mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE)); else @@ -1098,10 +1054,10 @@ namespace MWInput } void InputManager::keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control - , SDL_Keycode key, ICS::Control::ControlChangingDirection direction) + , SDL_Scancode key, ICS::Control::ControlChangingDirection direction) { //Disallow binding escape key - if(key==SDLK_ESCAPE) + if(key==SDL_SCANCODE_ESCAPE) return; clearAllBindings(control); @@ -1152,7 +1108,7 @@ namespace MWInput void InputManager::clearAllBindings (ICS::Control* control) { // right now we don't really need multiple bindings for the same action, so remove all others first - if (mInputBinder->getKeyBinding (control, ICS::Control::INCREASE) != SDLK_UNKNOWN) + if (mInputBinder->getKeyBinding (control, ICS::Control::INCREASE) != SDL_SCANCODE_UNKNOWN) mInputBinder->removeKeyBinding (mInputBinder->getKeyBinding (control, ICS::Control::INCREASE)); if (mInputBinder->getMouseButtonBinding (control, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) mInputBinder->removeMouseButtonBinding (mInputBinder->getMouseButtonBinding (control, ICS::Control::INCREASE)); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 6bf1ad6b00..a94b61c8ba 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -108,7 +108,7 @@ namespace MWInput , ICS::InputControlSystem::NamedAxis axis, ICS::Control::ControlChangingDirection direction); virtual void keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control - , SDL_Keycode key, ICS::Control::ControlChangingDirection direction); + , SDL_Scancode key, ICS::Control::ControlChangingDirection direction); virtual void mouseButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control , unsigned int button, ICS::Control::ControlChangingDirection direction); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6ea65275ba..efca99b4eb 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -89,12 +89,64 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) return false; } +class CheckActorCommanded : public MWMechanics::EffectSourceVisitor +{ + MWWorld::Ptr mActor; +public: + bool mCommanded; + CheckActorCommanded(MWWorld::Ptr actor) + : mActor(actor) + , mCommanded(false){} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, int casterActorId, + float magnitude, float remainingTime = -1) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + 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() + && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) + mCommanded = true; + } +}; + +void adjustCommandedActor (const MWWorld::Ptr& actor) +{ + CheckActorCommanded check(actor); + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + stats.getActiveSpells().visitEffectSources(check); + + bool hasCommandPackage = false; + + std::list::const_iterator it; + for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && + dynamic_cast(*it)->isCommanded()) + { + hasCommandPackage = true; + break; + } + } + + if (check.mCommanded && !hasCommandPackage) + { + MWMechanics::AiFollow package("player", true); + stats.getAiSequence().stack(package, actor); + } + else if (!check.mCommanded && hasCommandPackage) + { + stats.getAiSequence().erase(it); + } +} + void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).mMagnitude > 0; + bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); health = 0.1 * endurance; @@ -107,6 +159,30 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float } } +void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) +{ + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) + { + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWBase::Environment::get().getWorld()->deleteObject(ptr); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + } + else + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); + graveyard.push_back(creatureActorId); + } +} + } namespace MWMechanics @@ -172,6 +248,15 @@ namespace MWMechanics if (caster.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Soul_Trap"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(mCreature.getRefData().getPosition().pos)); + + MWBase::Environment::get().getSoundManager()->playSound3D(mCreature, "conjuration hit", 1.f, 1.f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); } }; @@ -184,18 +269,26 @@ namespace MWMechanics calculateCreatureStatModifiers (ptr, duration); // fatigue restoration - calculateRestoration(ptr, duration, false); + calculateRestoration(ptr, duration); } void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) { CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); - - if (againstPlayer && creatureStats.isHostile()) return; // already fighting against player + + if (actor2.getClass().getCreatureStats(actor2).isDead() + || actor1.getClass().getCreatureStats(actor1).isDead()) + return; + + const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); + const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); + float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + if (sqrDist > 7168*7168) + return; // pure water creatures won't try to fight with the target on the ground // except that creature is already hostile - if ((againstPlayer || !creatureStats.isHostile()) + if ((againstPlayer || !creatureStats.getAiSequence().isInCombat()) && ((actor1.getClass().canSwim(actor1) && !actor1.getClass().canWalk(actor1) // pure water creature && !MWBase::Environment::get().getWorld()->isSwimming(actor2)) || (!actor1.getClass().canSwim(actor1) && MWBase::Environment::get().getWorld()->isSwimming(actor2)))) // creature can't swim to target @@ -203,39 +296,70 @@ namespace MWMechanics bool aggressive; - if (againstPlayer) + if (againstPlayer) + { + // followers with high fight should not engage in combat with the player (e.g. bm_bear_black_summon) + const std::list& followers = getActorsFollowing(actor2); + if (std::find(followers.begin(), followers.end(), actor1) != followers.end()) + return; + aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); + } else { aggressive = false; - // if one of actors is creature then we should make a decision to start combat or not - // NOTE: function doesn't take into account combat between 2 creatures - if (!actor1.getClass().isNpc()) + + // Make guards fight aggressive creatures + if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) { - // if creature is hostile then it is necessarily to start combat - if (creatureStats.isHostile()) aggressive = true; - else aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); + if (creatureStats.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2)) + aggressive = true; + } + } + + // start combat if target actor is in combat with one of our followers + const std::list& followers = getActorsFollowing(actor1); + const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); + for (std::list::const_iterator it = followers.begin(); it != followers.end(); ++it) + { + // need to check both ways since player doesn't use AI packages + if ((creatureStats2.getAiSequence().isInCombat(*it) + || it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2)) + && !creatureStats.getAiSequence().isInCombat(*it)) + aggressive = true; + } + + // start combat if target actor is in combat with someone we are following + for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) + { + MWWorld::Ptr followTarget = dynamic_cast(*it)->getTarget(); + if (followTarget.isEmpty()) + continue; + + if (creatureStats.getAiSequence().isInCombat(followTarget)) + continue; + + // need to check both ways since player doesn't use AI packages + if (creatureStats2.getAiSequence().isInCombat(followTarget) + || followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2)) + aggressive = true; } } if(aggressive) { - const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); - const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); - float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); - if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d)) + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); + + if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); + + if (LOS) { - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); - - if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); - - if (LOS) + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + if (!againstPlayer) // start combat between each other { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); - if (!againstPlayer) // start combat between each other - { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); - } + MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); } } } @@ -251,6 +375,8 @@ namespace MWMechanics void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) { CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); + if (creatureStats.isDead()) + return; MagicEffects now = creatureStats.getSpells().getMagicEffects(); @@ -262,28 +388,30 @@ namespace MWMechanics now += creatureStats.getActiveSpells().getMagicEffects(); - //MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now); - - creatureStats.setMagicEffects(now); - - // TODO apply diff to other stats + creatureStats.modifyMagicEffects(now); } void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) { CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getBase(); - int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); + int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getModified(); + int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); + int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); + int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); + int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getModified(); - double magickaFactor = - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5; + float base = 1.f; + if (ptr.getCellRef().getRefId() == "player") + base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->getFloat(); + else + base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->getFloat(); + + double magickaFactor = base + + creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; DynamicStat magicka = creatureStats.getMagicka(); - float diff = (static_cast(intelligence + magickaFactor*intelligence)) - magicka.getBase(); + float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); magicka.modify(diff); creatureStats.setMagicka(magicka); @@ -293,7 +421,7 @@ namespace MWMechanics creatureStats.setFatigue(fatigue); } - void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep) + void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep) { if (ptr.getClass().getCreatureStats(ptr).isDead()) return; @@ -331,10 +459,30 @@ namespace MWMechanics float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); x *= fEndFatigueMult * endurance; + DynamicStat fatigue = stats.getFatigue(); + fatigue.setCurrent (fatigue.getCurrent() + 3600 * x); + stats.setFatigue (fatigue); + } + + void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) + { + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return; + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); + + int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + + // restore fatigue + float fFatigueReturnBase = settings.find("fFatigueReturnBase")->getFloat (); + float fFatigueReturnMult = settings.find("fFatigueReturnMult")->getFloat (); + + float x = fFatigueReturnBase + fFatigueReturnMult * endurance; + DynamicStat fatigue = stats.getFatigue(); fatigue.setCurrent (fatigue.getCurrent() + duration * x); stats.setFatigue (fatigue); - } void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) @@ -348,24 +496,41 @@ namespace MWMechanics for(int i = 0;i < ESM::Attribute::Length;++i) { AttributeValue stat = creatureStats.getAttribute(i); - stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude); + stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude() - + effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() - + effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude()); creatureStats.setAttribute(i, stat); } + { + Spells & spells = creatureStats.getSpells(); + for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + if (spells.getCorprusSpells().find(it->first) != spells.getCorprusSpells().end()) + { + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= spells.getCorprusSpells().at(it->first).mNextWorsening) + { + spells.worsenCorprus(it->first); + + if (ptr.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); + } + } + } + } + // dynamic stats for(int i = 0;i < 3;++i) { DynamicStat stat = creatureStats.getDynamic(i); - stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).mMagnitude - - effects.get(ESM::MagicEffect::DrainHealth+i).mMagnitude); + stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).getMagnitude() - + effects.get(ESM::MagicEffect::DrainHealth+i).getMagnitude()); - float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).mMagnitude - - creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).mMagnitude - - creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).mMagnitude; + 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); @@ -379,27 +544,27 @@ namespace MWMechanics if (!creature || ptr.get()->mBase->mData.mType == ESM::Creature::Creatures) { Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); - stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid+creature).mMagnitude - - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).mMagnitude); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid+creature).getMagnitude() + - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude()); creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid+creature).mMagnitude - - creatureStats.getMagicEffects().get(ESM::MagicEffect::RallyHumanoid+creature).mMagnitude); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid+creature).getMagnitude() + - creatureStats.getMagicEffects().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(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).mMagnitude); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).getMagnitude()); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } // Apply disintegration (reduces item health) - float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).mMagnitude; + 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).mMagnitude; + float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).getMagnitude(); if (disintegrateArmor > 0) { // According to UESP @@ -431,7 +596,7 @@ namespace MWMechanics DynamicStat health = creatureStats.getHealth(); for (unsigned int i=0; i::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) { bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); - int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; + int magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude(); if (found != (magnitude > 0)) { std::string itemGmst = it->second; @@ -575,11 +740,11 @@ namespace MWMechanics summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; } + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); bool found = creatureMap.find(it->first) != creatureMap.end(); - int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; + int magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude(); if (found != (magnitude > 0)) { if (magnitude > 0) @@ -608,7 +773,7 @@ namespace MWMechanics MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); // Make the summoned creature follow its master and help in fights - AiFollow package(ptr.getRefData().getHandle()); + AiFollow package(ptr.getCellRef().getRefId()); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); int creatureActorId = summonedCreatureStats.getActorId(); @@ -628,34 +793,31 @@ namespace MWMechanics } else { - // Summon lifetime has expired. Try to delete the creature. - int actorId = creatureMap[it->first]; - creatureMap.erase(it->first); - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); - if (!ptr.isEmpty()) - { - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWBase::Environment::get().getWorld()->deleteObject(ptr); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - } - else - { - // We didn't find the creature. It's probably in an inactive cell. - // Add to graveyard so we can delete it when the cell becomes active. - std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); - graveyard.push_back(actorId); - } + // Effect has ended + std::map::iterator foundCreature = creatureMap.find(it->first); + cleanupSummonedCreature(creatureStats, foundCreature->second); + creatureMap.erase(foundCreature); } } } + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) + { + // Purge the magic effect so a new creature can be summoned if desired + creatureStats.getActiveSpells().purgeEffect(it->first); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first); + + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + } + else + ++it; + } + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) { @@ -686,9 +848,9 @@ namespace MWMechanics for(int i = 0;i < ESM::Skill::Length;++i) { SkillValue& skill = npcStats.getSkill(i); - skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude); + skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() - + effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() - + effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude()); } } @@ -697,7 +859,7 @@ namespace MWMechanics MWBase::World *world = MWBase::Environment::get().getWorld(); NpcStats &stats = ptr.getClass().getNpcStats(ptr); if(world->isSubmerged(ptr) && - stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).mMagnitude == 0) + stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { float timeLeft = 0.0f; if(stats.getFatigue().getCurrent() == 0) @@ -757,7 +919,7 @@ namespace MWMechanics { if (torch != inventoryStore.end()) { - if (!ptr.getClass().getCreatureStats (ptr).isHostile()) + if (!ptr.getClass().getCreatureStats (ptr).getAiSequence().isInCombat()) { // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) @@ -837,10 +999,13 @@ namespace MWMechanics CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) + if (player.getClass().getNpcStats(player).isWerewolf()) + return; + + if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()); + int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); // Force dialogue on sight if bounty is greater than the cutoff // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) if ( player.getClass().getNpcStats(player).getBounty() >= cutoff @@ -848,7 +1013,11 @@ namespace MWMechanics && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - creatureStats.getAiSequence().stack(AiPursue(player), ptr); + static int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); + if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) + MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); + else + creatureStats.getAiSequence().stack(AiPursue(player), ptr); creatureStats.setAlarmed(true); npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId()); } @@ -866,7 +1035,6 @@ namespace MWMechanics creatureStats.getAiSequence().stopCombat(); // Reset factors to attack - creatureStats.setHostile(false); creatureStats.setAttacked(false); creatureStats.setAlarmed(false); @@ -881,19 +1049,11 @@ namespace MWMechanics Actors::~Actors() { - PtrControllerMap::iterator it(mActors.begin()); - for (; it != mActors.end(); ++it) - { - delete it->second; - it->second = NULL; - } + clear(); } void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { - // erase previous death events since we are currently only tracking them while in an active cell - ptr.getClass().getCreatureStats(ptr).clearHasDied(); - removeActor(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); @@ -940,19 +1100,10 @@ namespace MWMechanics } } - static Ogre::Vector3 sBasePoint; - bool comparePtrDist (const MWWorld::Ptr& ptr1, const MWWorld::Ptr& ptr2) - { - return (sBasePoint.squaredDistance(Ogre::Vector3(ptr1.getRefData().getPosition().pos)) - < sBasePoint.squaredDistance(Ogre::Vector3(ptr2.getRefData().getPosition().pos))); - } - void Actors::update (float duration, bool paused) { if(!paused) { - std::list listGuards; // at the moment only guards certainly will fight with creatures - static float timerUpdateAITargets = 0; // target lists get updated once every 1.0 sec @@ -965,15 +1116,11 @@ namespace MWMechanics // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation // (below) iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); - - // add guards to list to later make them fight with creatures - if (timerUpdateAITargets == 0 && iter->first.getClass().isClass(iter->first, "Guard")) - listGuards.push_back(iter->first); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - listGuards.push_back(player); + int hostilesCount = 0; // need to know this to play Battle music // AI and magic effects update for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) @@ -982,27 +1129,38 @@ namespace MWMechanics { updateActor(iter->first, duration); - if (MWBase::Environment::get().getMechanicsManager()->isAIActive()) + // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this + // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) + // This distance could be made configurable later, but the setting must be marked with a big warning: + // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) + if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && + Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(iter->first.getRefData().getPosition().pos)) + <= 7168*7168) { - // make guards and creatures fight each other - if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) + if (timerUpdateAITargets == 0) { - sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); - listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest guard - - for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) + if (iter->first != player) + adjustCommandedActor(iter->first); + + for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { - engageCombat(iter->first, *it, *it == player); + if (it->first == iter->first || iter->first == player) // player is not AI-controlled + continue; + engageCombat(iter->first, it->first, it->first == player); } } - if (iter->first != player) engageCombat(iter->first, player, true); - if (iter->first.getClass().isNpc() && iter->first != player) updateCrimePersuit(iter->first, duration); if (iter->first != player) iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first, duration); + + CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); + if(!stats.isDead()) + { + if (stats.getAiSequence().isInCombat()) hostilesCount++; + } } if(iter->first.getTypeName() == typeid(ESM::NPC).name()) @@ -1021,17 +1179,25 @@ namespace MWMechanics iter->second->updateContinuousVfx(); // Animation/movement update + CharacterController* playerCharacter = NULL; for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( - ESM::MagicEffect::Paralyze).mMagnitude > 0) + ESM::MagicEffect::Paralyze).getMagnitude() > 0) iter->second->skipAnim(); + + // Handle player last, in case a cell transition occurs by casting a teleportation spell + // (would invalidate the iterator) + if (iter->first.getCellRef().getRefId() == "player") + { + playerCharacter = iter->second; + continue; + } iter->second->update(duration); } - // Kill dead actors, update some variables - - int hostilesCount = 0; // need to know this to play Battle music + if (playerCharacter) + playerCharacter->update(duration); for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { @@ -1041,98 +1207,43 @@ namespace MWMechanics //KnockedOutOneFrameLogic //Used for "OnKnockedOut" command //Put here to ensure that it's run for PRECISELY one frame. - if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Start it for one frame if nessesary + if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) + { //Start it for one frame if nessesary stats.setKnockedDownOneFrame(true); } - else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Turn off KnockedOutOneframe + else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) + { //Turn off KnockedOutOneframe stats.setKnockedDownOneFrame(false); stats.setKnockedDownOverOneFrame(true); } - - if(!stats.isDead()) - { - if (stats.isHostile()) hostilesCount++; - - if(iter->second->isDead()) - { - // Actor has been resurrected. Notify the CharacterController and re-enable collision. - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); - iter->second->resurrect(); - } - - if(!stats.isDead()) - continue; - } - - // If it's the player and God Mode is turned on, keep it alive - if (iter->first.getRefData().getHandle()=="player" && - MWBase::Environment::get().getWorld()->getGodModeState()) - { - MWMechanics::DynamicStat stat (stats.getHealth()); - - if (stat.getModified()<1) - { - stat.setModified(1, 0); - stats.setHealth(stat); - } - stats.resurrect(); - continue; - } - - if (iter->second->kill()) - { - ++mDeathCount[cls.getId(iter->first)]; - - // Make sure spell effects with CasterLinked flag are removed - for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) - { - MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); - spells.purge(stats.getActorId()); - } - - // Apply soultrap - if (iter->first.getTypeName() == typeid(ESM::Creature).name()) - { - SoulTrap soulTrap (iter->first); - stats.getActiveSpells().visitEffectSources(soulTrap); - } - - // Reset magic effects and recalculate derived effects - // One case where we need this is to make sure bound items are removed upon death - stats.setMagicEffects(MWMechanics::MagicEffects()); - stats.getActiveSpells().clear(); - calculateCreatureStatModifiers(iter->first, 0); - - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); - - if (cls.isEssential(iter->first)) - MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); - } } + killDeadActors(); + // check if we still have any player enemies to switch music static bool isBattleMusic = false; - if (isBattleMusic && hostilesCount == 0) + if (isBattleMusic && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && + MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); isBattleMusic = false; } - else if (!isBattleMusic && hostilesCount > 0) + else if (!isBattleMusic && hostilesCount > 0) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); isBattleMusic = true; } static float sneakTimer = 0.f; // times update of sneak icon - static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" // if player is in sneak state see if anyone detects him if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) { + static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); const int radius = esmStore.get().find("fSneakUseDist")->getInt(); - bool detected = false; static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->getFloat(); @@ -1144,6 +1255,8 @@ namespace MWMechanics // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. bool avoidedNotice = false; + bool detected = false; + for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first == player) // not the player @@ -1184,10 +1297,65 @@ namespace MWMechanics } } } + + void Actors::killDeadActors() + { + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); + + if(!stats.isDead()) + { + if(iter->second->isDead()) + { + // Actor has been resurrected. Notify the CharacterController and re-enable collision. + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); + iter->second->resurrect(); + } + + if(!stats.isDead()) + continue; + } + + if (iter->second->kill()) + { + iter->first.getClass().getCreatureStats(iter->first).notifyDied(); + + ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; + + // Make sure spell effects with CasterLinked flag are removed + for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + { + MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); + spells.purge(stats.getActorId()); + } + + // Apply soultrap + if (iter->first.getTypeName() == typeid(ESM::Creature).name()) + { + SoulTrap soulTrap (iter->first); + stats.getActiveSpells().visitEffectSources(soulTrap); + } + + // Reset magic effects and recalculate derived effects + // One case where we need this is to make sure bound items are removed upon death + stats.modifyMagicEffects(MWMechanics::MagicEffects()); + stats.getActiveSpells().clear(); + calculateCreatureStatModifiers(iter->first, 0); + + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); + + if (cls.isEssential(iter->first)) + MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); + } + } + } + void Actors::restoreDynamicStats(bool sleep) { for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - calculateRestoration(iter->first, 3600, sleep); + restoreDynamicStats(iter->first, sleep); } int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const @@ -1256,15 +1424,26 @@ namespace MWMechanics std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); - if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow) + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package + for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) { - MWMechanics::AiFollow* package = static_cast(stats.getAiSequence().getActivePackage()); - if(package->getFollowedActor() == actor.getCellRef().getRefId()) - list.push_front(iter->first); + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) + { + MWWorld::Ptr followTarget = dynamic_cast(*it)->getTarget(); + if (followTarget.isEmpty()) + continue; + if (followTarget == actor) + list.push_back(iter->first); + } + else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + break; } } return list; @@ -1277,16 +1456,12 @@ namespace MWMechanics getObjectsInRange(position, MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(), neighbors); //only care about those within the alarm disance - for(std::vector::iterator iter(neighbors.begin());iter != neighbors.end();iter++) + for(std::vector::iterator iter(neighbors.begin());iter != neighbors.end();++iter) { const MWWorld::Class &cls = iter->getClass(); CreatureStats &stats = cls.getCreatureStats(*iter); - if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat) - { - MWMechanics::AiCombat* package = static_cast(stats.getAiSequence().getActivePackage()); - if(package->getTarget() == actor) - list.push_front(*iter); - } + if (!stats.isDead() && stats.getAiSequence().isInCombat(actor)) + list.push_front(*iter); } return list; } @@ -1320,6 +1495,21 @@ namespace MWMechanics void Actors::clear() { + PtrControllerMap::iterator it(mActors.begin()); + for (; it != mActors.end(); ++it) + { + delete it->second; + it->second = NULL; + } + mActors.clear(); mDeathCount.clear(); } + + void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) + { + adjustMagicEffects(ptr); + calculateCreatureStatModifiers(ptr, 0.f); + if (ptr.getClass().isNpc()) + calculateNpcStatModifiers(ptr); + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 4784162f48..55f1719f62 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -36,7 +36,7 @@ namespace MWMechanics void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); - void calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep); + void calculateRestoration (const MWWorld::Ptr& ptr, float duration); void updateDrowning (const MWWorld::Ptr& ptr, float duration); @@ -44,6 +44,8 @@ namespace MWMechanics void updateCrimePersuit (const MWWorld::Ptr& ptr, float duration); + void killDeadActors (); + public: Actors(); @@ -56,7 +58,7 @@ namespace MWMechanics /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) - void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } + void updateMagicEffects (const MWWorld::Ptr& ptr); void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); ///< Register an actor for stats management @@ -90,6 +92,8 @@ namespace MWMechanics void restoreDynamicStats(bool sleep); ///< If the player is sleeping, this should be called every hour. + void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); + int getHoursToRest(const MWWorld::Ptr& ptr) const; ///< Calculate how many hours the given actor needs to rest in order to be fully healed diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 9e01c3fe72..54bcd67b5a 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -5,6 +5,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -24,7 +26,12 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow - if(target == MWWorld::Ptr()) + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + + if(target == MWWorld::Ptr() || + !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered + // with the MechanicsManager + ) return true; //Target doesn't exist //Set the target desition from the actor diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 8003c2a361..2a0f67709f 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -35,8 +35,6 @@ namespace MWMechanics private: std::string mObjectId; - int mCellX; - int mCellY; }; } #endif // GAME_MWMECHANICS_AIACTIVATE_H diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index bab8bca280..7cb4f1c255 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -51,17 +51,20 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door float x = pos.pos[0] - tPos.pos[0]; float y = pos.pos[1] - tPos.pos[1]; - float dirToDoor = std::atan2(x,y) + pos.rot[2] + mAdjAngle; //Calculates the direction to the door, relative to the direction of the NPC - // For example, if the NPC is directly facing the door this will be pi/2 - // Make actor move away from the door - actor.getClass().getMovementSettings(actor).mPosition[1] = -1 * std::sin(dirToDoor); //I knew I'd use trig someday - actor.getClass().getMovementSettings(actor).mPosition[0] = -1 * std::cos(dirToDoor); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - //Make all nearby actors also avoid the door + // Turn away from the door and move when turn completed + if (zTurn(actor, Ogre::Radian(std::atan2(x,y) + mAdjAngle), Ogre::Degree(5))) + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + else + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; + + // Make all nearby actors also avoid the door std::vector actors; MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors); - for(std::vector::iterator it = actors.begin(); it != actors.end(); it++) { + for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { if(*it != MWBase::Environment::get().getWorld()->getPlayerPtr()) { //Not the player MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index c0a97a1b1f..0bea76c581 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -22,6 +22,8 @@ #include "movement.hpp" #include "character.hpp" // fixme: for getActiveWeapon +#include "aicombataction.hpp" + namespace { static float sgn(Ogre::Radian a) @@ -107,6 +109,7 @@ namespace MWMechanics void AiCombat::init() { + mActionCooldown = 0; mTimerAttack = 0; mTimerReact = 0; mTimerCombatMove = 0; @@ -174,8 +177,12 @@ namespace MWMechanics return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (target.isEmpty()) + return false; - if(target.getClass().getCreatureStats(target).isDead()) + if(!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered + // with the MechanicsManager + || target.getClass().getCreatureStats(target).isDead()) return true; const MWWorld::Class& actorClass = actor.getClass(); @@ -188,8 +195,6 @@ namespace MWMechanics // 2. creature can't swim to target || (!actorClass.canSwim(actor) && world->isSwimming(target)))) { - if (target == world->getPlayerPtr()) - actorClass.getCreatureStats(actor).setHostile(false); actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; } @@ -246,6 +251,8 @@ namespace MWMechanics actorClass.getCreatureStats(actor).setAttackingOrSpell(mAttack); + mActionCooldown -= duration; + float tReaction = 0.25f; if(mTimerReact < tReaction) { @@ -263,19 +270,33 @@ namespace MWMechanics mCell = actor.getCell(); } - const ESM::Weapon *weapon = NULL; - MWMechanics::WeaponType weaptype; - float weapRange = 1.0f; + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); + if (!anim) // shouldn't happen + return false; actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + if (mActionCooldown > 0) + return false; + + float rangeAttack = 0; + float rangeFollow = 0; + if (anim->upperBodyReady()) + { + mCurrentAction = prepareNextAction(actor, target); + mActionCooldown = mCurrentAction->getActionCooldown(); + } + if (mCurrentAction.get()) + mCurrentAction->getCombatRange(rangeAttack, rangeFollow); + + // FIXME: consider moving this stuff to ActionWeapon::getCombatRange + const ESM::Weapon *weapon = NULL; + MWMechanics::WeaponType weaptype = WeapType_None; + float weapRange = 1.0f; + // Get weapon characteristics if (actorClass.hasInventoryStore(actor)) { - MWMechanics::DrawState_ state = actorClass.getCreatureStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actorClass.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - // TODO: Check equipped weapon and equip a different one if we can't attack with it // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) @@ -285,11 +306,11 @@ namespace MWMechanics if (weaptype == WeapType_HandToHand) { - static float fHandToHandReach = + static float fHandToHandReach = world->getStore().get().find("fHandToHandReach")->getFloat(); weapRange = fHandToHandReach; } - else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell) + else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) { // All other WeapTypes are actually weapons, so get is safe. weapon = weaponSlot->get()->mBase; @@ -299,23 +320,30 @@ namespace MWMechanics } else //is creature { - weaptype = WeapType_HandToHand; //doesn't matter, should only reflect if it is melee or distant weapon + weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) } - float rangeAttack; - float rangeFollow; bool distantCombat = false; - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + if (weaptype != WeapType_Spell) { - rangeAttack = 1000; // TODO: should depend on archer skill - rangeFollow = 0; // not needed in ranged combat - distantCombat = true; + // TODO: move to ActionWeapon + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + { + rangeAttack = 1000; + rangeFollow = 0; // not needed in ranged combat + distantCombat = true; + } + else + { + rangeAttack = weapRange; + rangeFollow = 300; + } } else { - rangeAttack = weapRange; - rangeFollow = 300; + distantCombat = (rangeAttack > 500); + weapRange = 150.f; } // start new attack @@ -345,7 +373,7 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } } - } + } } @@ -744,13 +772,15 @@ void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations // get weapon information: type and speed const ESM::Weapon *weapon = NULL; - MWMechanics::WeaponType weaptype; + MWMechanics::WeaponType weaptype = MWMechanics::WeapType_None; MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); float weapSpeed; - if (weaptype != MWMechanics::WeapType_HandToHand) + if (weaptype != MWMechanics::WeapType_HandToHand + && weaptype != MWMechanics::WeapType_Spell + && weaptype != MWMechanics::WeapType_None) { weapon = weaponSlot->get()->mBase; weapSpeed = weapon->mData.mSpeed; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 311dee6179..916a1a1d5e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -14,6 +14,8 @@ #include "../mwbase/world.hpp" +#include + namespace ESM { namespace AiSequence @@ -24,6 +26,8 @@ namespace ESM namespace MWMechanics { + class Action; + /// \brief Causes the actor to fight another actor class AiCombat : public AiPackage { @@ -79,6 +83,9 @@ namespace MWMechanics const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; + boost::shared_ptr mCurrentAction; + float mActionCooldown; + void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp new file mode 100644 index 0000000000..152854af99 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -0,0 +1,536 @@ +#include "aicombataction.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/actionequip.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include +#include + +namespace +{ + +// RangeTypes using bitflags to allow multiple range types, as can be the case with spells having multiple effects. +enum RangeTypes +{ + Self = 0x1, + Touch = 0x10, + Target = 0x100 +}; + +int getRangeTypes (const ESM::EffectList& effects) +{ + int types = 0; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + if (it->mRange == ESM::RT_Self) + types |= Self; + else if (it->mRange == ESM::RT_Touch) + types |= Touch; + else if (it->mRange == ESM::RT_Target) + types |= Target; + } + return types; +} + +void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) +{ + if (rangeTypes & Touch) + { + rangeAttack = 100.f; + rangeFollow = 300.f; + } + else if (rangeTypes & Target) + { + rangeAttack = 1000.f; + rangeFollow = 0.f; + } + else + { + // For Self spells, distance doesn't matter, so back away slightly to avoid enemy hits + rangeAttack = 600.f; + rangeFollow = 0.f; + } +} + +int numEffectsToCure (const MWWorld::Ptr& actor, int effectFilter=-1) +{ + int toCure=0; + const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); + for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) + { + const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; + for (std::vector::const_iterator effectIt = params.mEffects.begin(); + effectIt != params.mEffects.end(); ++effectIt) + { + int effectId = effectIt->mEffectId; + if (effectFilter != -1 && effectId != effectFilter) + continue; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful + && effectIt->mDuration > 3 // Don't attempt to cure if effect runs out shortly anyway + ) + ++toCure; + } + } + return toCure; +} + +} + +namespace MWMechanics +{ + + float ratePotion (const MWWorld::Ptr &item, const MWWorld::Ptr& actor) + { + if (item.getTypeName() != typeid(ESM::Potion).name()) + return 0.f; + + const ESM::Potion* potion = item.get()->mBase; + return rateEffects(potion->mEffects, actor, MWWorld::Ptr()); + } + + float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, int type, + float arrowRating, float boltRating) + { + if (item.getTypeName() != typeid(ESM::Weapon).name()) + return 0.f; + + const ESM::Weapon* weapon = item.get()->mBase; + + if (type != -1 && weapon->mData.mType != type) + return 0.f; + + float rating=0.f; + + if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + { + rating = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; + } + else + { + for (int i=0; i<2; ++i) + { + rating += weapon->mData.mSlash[i]; + rating += weapon->mData.mThrust[i]; + rating += weapon->mData.mChop[i]; + } + rating /= 6.f; + } + + if (item.getClass().hasItemHealth(item)) + { + if (item.getClass().getItemHealth(item) == 0) + return 0.f; + rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item)); + } + + if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + { + if (arrowRating <= 0.f) + rating = 0.f; + else + rating += arrowRating; + } + else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + if (boltRating <= 0.f) + rating = 0.f; + else + rating += boltRating; + } + + if (!weapon->mEnchant.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(weapon->mEnchant); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes + && (item.getCellRef().getEnchantmentCharge() == -1 + || item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) + rating += rateEffects(enchantment->mEffects, actor, target); + } + + int skill = item.getClass().getEquipmentSkill(item); + if (skill != -1) + rating *= actor.getClass().getSkill(actor, skill) / 100.f; + + return rating; + } + + float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + { + const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + // Never casting racial spells (ST_Power and F_Always) + if (spell->mData.mType != ESM::Spell::ST_Spell || spell->mData.mFlags & ESM::Spell::F_Always) + return 0.f; + + if (spell->mData.mCost > stats.getMagicka().getCurrent()) + return 0.f; + + // Spells don't stack, so early out if the spell is still active on the target + int types = getRangeTypes(spell->mEffects); + if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) + return 0.f; + if ( ((types & Touch) || (types & Target)) && target.getClass().getCreatureStats(target).getActiveSpells().isSpellActive(spell->mId)) + return 0.f; + + return rateEffects(spell->mEffects, actor, target); + } + + float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + { + if (ptr.getClass().getEnchantment(ptr).empty()) + return 0.f; + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getClass().getEnchantment(ptr)); + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + { + return rateEffects(enchantment->mEffects, actor, target); + } + else + return 0.f; + } + + float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + { + // NOTE: target may be empty + + float rating = 1; + switch (effect.mEffectID) + { + case ESM::MagicEffect::Soultrap: + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::CalmHumanoid: + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::FrenzyHumanoid: + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::RallyHumanoid: + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::Charm: + case ESM::MagicEffect::DetectAnimal: + case ESM::MagicEffect::DetectEnchantment: + case ESM::MagicEffect::DetectKey: + case ESM::MagicEffect::Telekinesis: + case ESM::MagicEffect::Mark: + case ESM::MagicEffect::Recall: + case ESM::MagicEffect::Jump: + case ESM::MagicEffect::WaterBreathing: + case ESM::MagicEffect::SwiftSwim: + case ESM::MagicEffect::WaterWalking: + case ESM::MagicEffect::SlowFall: + case ESM::MagicEffect::Light: + case ESM::MagicEffect::Lock: + case ESM::MagicEffect::Open: + case ESM::MagicEffect::TurnUndead: + case ESM::MagicEffect::WeaknessToCommonDisease: + case ESM::MagicEffect::WeaknessToBlightDisease: + case ESM::MagicEffect::WeaknessToCorprusDisease: + case ESM::MagicEffect::CureCommonDisease: + case ESM::MagicEffect::CureBlightDisease: + case ESM::MagicEffect::CureCorprusDisease: + case ESM::MagicEffect::Invisibility: + return 0.f; + case ESM::MagicEffect::Feather: + if (actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor) >= 0) + return 100.f; + else + return 0.f; + case ESM::MagicEffect::Levitate: + return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundHelm: + if (actor.getClass().isNpc()) + { + // Beast races can't wear helmets or boots + std::string raceid = actor.get()->mBase->mRace; + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceid); + if (race->mData.mFlags & ESM::Race::Beast) + return 0.f; + } + // Intended fall-through + // Creatures can not wear armor + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundGloves: + if (!actor.getClass().isNpc()) + return 0.f; + break; + + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + if (effect.mRange == ESM::RT_Self) + { + int priority = 1; + if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) + priority = 10; + const DynamicStat& current = actor.getClass().getCreatureStats(actor). + getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); + float toHeal = (effect.mMagnMin + effect.mMagnMax)/2.f * effect.mDuration; + // Effect doesn't heal more than we need, *or* we are below 1/2 health + if (current.getModified() - current.getCurrent() > toHeal + || current.getCurrent() < current.getModified()*0.5) + return 10000.f * priority; + else + return -10000.f * priority; // Save for later + } + break; + + // Prefer Cure effects over Dispel, because Dispel also removes positive effects + case ESM::MagicEffect::Dispel: + return 1000.f * numEffectsToCure(actor); + case ESM::MagicEffect::CureParalyzation: + return 1001.f * numEffectsToCure(actor, ESM::MagicEffect::Paralyze); + case ESM::MagicEffect::CurePoison: + return 1001.f * numEffectsToCure(actor, ESM::MagicEffect::Poison); + + case ESM::MagicEffect::DisintegrateArmor: // TODO: check if actor is wearing armor + case ESM::MagicEffect::DisintegrateWeapon: // TODO: check if actor is wearing weapon + break; + + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::DrainAttribute: + if (!target.isEmpty() && target.getClass().getCreatureStats(target).getAttribute(effect.mAttribute).getModified() <= 0) + return 0.f; + { + if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) + { + const float attributePriorities[ESM::Attribute::Length] = { + 1.f, // Strength + 0.5, // Intelligence + 0.6, // Willpower + 0.7, // Agility + 0.5, // Speed + 0.8, // Endurance + 0.7, // Personality + 0.3 // Luck + }; + rating *= attributePriorities[effect.mAttribute]; + } + } + break; + + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::DrainSkill: + if (target.isEmpty() || !target.getClass().isNpc()) + return 0.f; + if (target.getClass().getNpcStats(target).getSkill(effect.mSkill).getModified() <= 0) + return 0.f; + break; + + default: + break; + } + + // TODO: for non-cumulative effects (e.g. paralyze), check if the target is already suffering from them + + // TODO: could take into account target's resistance/weakness against the effect + + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + + rating *= magicEffect->mData.mBaseCost; + + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + rating *= (effect.mMagnMin + effect.mMagnMax)/2.f; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + rating *= effect.mDuration; + + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + rating *= -1.f; + + // Currently treating all "on target" or "on touch" effects to target the enemy actor. + // Combat AI is egoistic, so doesn't consider applying positive effects to friendly actors. + if (effect.mRange != ESM::RT_Self) + rating *= -1.f; + return rating; + } + + float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + // NOTE: target may be empty + float rating = 0.f; + for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) + { + rating += rateEffect(*it, actor, target); + } + return rating; + } + + void ActionSpell::prepare(const MWWorld::Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(mSpellId); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); + if (actor.getClass().hasInventoryStore(actor)) + { + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + inv.setSelectedEnchantItem(inv.end()); + } + } + + void ActionSpell::getCombatRange(float& rangeAttack, float& rangeFollow) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); + int types = getRangeTypes(spell->mEffects); + suggestCombatRange(types, rangeAttack, rangeFollow); + } + + void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(std::string()); + actor.getClass().getInventoryStore(actor).setSelectedEnchantItem(mItem); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); + } + + void ActionEnchantedItem::getCombatRange(float& rangeAttack, float& rangeFollow) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); + int types = getRangeTypes(enchantment->mEffects); + suggestCombatRange(types, rangeAttack, rangeFollow); + } + + void ActionPotion::getCombatRange(float& rangeAttack, float& rangeFollow) + { + // distance doesn't matter, so back away slightly to avoid enemy hits + rangeAttack = 600.f; + rangeFollow = 0.f; + } + + void ActionPotion::prepare(const MWWorld::Ptr &actor) + { + actor.getClass().apply(actor, mPotion.getCellRef().getRefId(), actor); + actor.getClass().getContainerStore(actor).remove(mPotion, 1, actor); + } + + void ActionWeapon::prepare(const MWWorld::Ptr &actor) + { + if (actor.getClass().hasInventoryStore(actor)) + { + if (mWeapon.isEmpty()) + actor.getClass().getInventoryStore(actor).unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); + else + { + MWWorld::ActionEquip equip(mWeapon); + equip.execute(actor); + } + + if (!mAmmunition.isEmpty()) + { + MWWorld::ActionEquip equip(mAmmunition); + equip.execute(actor); + } + } + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); + } + + void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow) + { + // Already done in AiCombat itself + } + + boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + { + Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); + + float bestActionRating = 0.f; + // Default to hand-to-hand combat + boost::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); + + if (actor.getClass().hasInventoryStore(actor)) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = ratePotion(*it, actor); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionPotion(*it)); + } + } + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateMagicItem(*it, actor, target); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionEnchantedItem(it)); + } + } + + float bestArrowRating = 0; + MWWorld::Ptr bestArrow; + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateWeapon(*it, actor, target, ESM::Weapon::Arrow); + if (rating > bestArrowRating) + { + bestArrowRating = rating; + bestArrow = *it; + } + } + + float bestBoltRating = 0; + MWWorld::Ptr bestBolt; + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateWeapon(*it, actor, target, ESM::Weapon::Bolt); + if (rating > bestBoltRating) + { + bestBoltRating = rating; + bestBolt = *it; + } + } + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + std::vector equipmentSlots = it->getClass().getEquipmentSlots(*it).first; + if (std::find(equipmentSlots.begin(), equipmentSlots.end(), (int)MWWorld::InventoryStore::Slot_CarriedRight) + == equipmentSlots.end()) + continue; + + float rating = rateWeapon(*it, actor, target, -1, bestArrowRating, bestBoltRating); + if (rating > bestActionRating) + { + const ESM::Weapon* weapon = it->get()->mBase; + + MWWorld::Ptr ammo; + if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + ammo = bestArrow; + else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow) + ammo = bestBolt; + + bestActionRating = rating; + bestAction.reset(new ActionWeapon(*it, ammo)); + } + } + } + + for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + + float rating = rateSpell(spell, actor, target); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionSpell(spell->mId)); + } + } + + if (bestAction.get()) + bestAction->prepare(actor); + + return bestAction; + } + +} diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp new file mode 100644 index 0000000000..1c7451c32b --- /dev/null +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -0,0 +1,90 @@ +#ifndef OPENMW_AICOMBAT_ACTION_H +#define OPENMW_AICOMBAT_ACTION_H + +#include + +#include "../mwworld/ptr.hpp" +#include "../mwworld/containerstore.hpp" + +#include + +namespace MWMechanics +{ + + class Action + { + public: + virtual ~Action() {} + virtual void prepare(const MWWorld::Ptr& actor) = 0; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow) = 0; + virtual float getActionCooldown() { return 0.f; } + }; + + class ActionSpell : public Action + { + public: + ActionSpell(const std::string& spellId) : mSpellId(spellId) {} + std::string mSpellId; + /// Sets the given spell as selected on the actor's spell list. + virtual void prepare(const MWWorld::Ptr& actor); + + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + }; + + class ActionEnchantedItem : public Action + { + public: + ActionEnchantedItem(const MWWorld::ContainerStoreIterator& item) : mItem(item) {} + MWWorld::ContainerStoreIterator mItem; + /// Sets the given item as selected enchanted item in the actor's InventoryStore. + virtual void prepare(const MWWorld::Ptr& actor); + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + + /// Since this action has no animation, apply a small cool down for using it + virtual float getActionCooldown() { return 1.f; } + }; + + class ActionPotion : public Action + { + public: + ActionPotion(const MWWorld::Ptr& potion) : mPotion(potion) {} + MWWorld::Ptr mPotion; + /// Drinks the given potion. + virtual void prepare(const MWWorld::Ptr& actor); + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + + /// Since this action has no animation, apply a small cool down for using it + virtual float getActionCooldown() { return 1.f; } + }; + + class ActionWeapon : public Action + { + private: + MWWorld::Ptr mAmmunition; + MWWorld::Ptr mWeapon; + + public: + /// \a weapon may be empty for hand-to-hand combat + ActionWeapon(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo = MWWorld::Ptr()) + : mWeapon(weapon), mAmmunition(ammo) {} + /// Equips the given weapon. + virtual void prepare(const MWWorld::Ptr& actor); + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + }; + + float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr &target); + float ratePotion (const MWWorld::Ptr& item, const MWWorld::Ptr &actor); + /// @param type Skip all weapons that are not of this type (i.e. return rating 0) + float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, + int type=-1, float arrowRating=0.f, float boltRating=0.f); + + /// @note target may be empty + float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + /// @note target may be empty + float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + + boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& target); +} + +#endif diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 3f5724077f..98fc64090f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -9,6 +9,8 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "steering.hpp" #include "movement.hpp" @@ -25,7 +27,7 @@ namespace MWMechanics , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { - mMaxDist = 470; + mMaxDist = 450; // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. @@ -38,7 +40,7 @@ namespace MWMechanics , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { - mMaxDist = 470; + mMaxDist = 450; // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is given, it "trumps" the duration so it will simply escort to that location. @@ -52,6 +54,7 @@ namespace MWMechanics , mCellY(std::numeric_limits::max()) , mCellId(escort->mCellId) , mRemainingDuration(escort->mRemainingDuration) + , mMaxDist(450) { } @@ -72,6 +75,9 @@ namespace MWMechanics return true; } + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); + const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; @@ -88,14 +94,14 @@ namespace MWMechanics { if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete return true; - mMaxDist = 470; + mMaxDist = 450; } else { // Stop moving if the player is to far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - mMaxDist = 330; + mMaxDist = 250; } return false; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5ab7e17305..abde80c71e 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -16,24 +16,41 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") +: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +, mActorRefId(actorId), mCellId(""), mActorId(-1) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) +: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +, mActorRefId(actorId), mCellId(cellId), mActorId(-1) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") +MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded) +: mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0) +, mActorRefId(actorId), mCellId(""), mActorId(-1) { } +MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) + : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) + , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) + , mActorRefId(follow->mTargetId), mActorId(-1), mCellId(follow->mCellId) + , mCommanded(follow->mCommanded) +{ + +} + bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); //The target to follow + MWWorld::Ptr target = getTarget(); - if(target == MWWorld::Ptr()) return true; //Target doesn't exist + if (target.isEmpty() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered + // with the MechanicsManager + ) + return true; //Target doesn't exist + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); ESM::Position pos = actor.getRefData().getPosition(); //position of the actor @@ -74,9 +91,9 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } //Check if you're far away - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 1000) + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 800) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; @@ -84,7 +101,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) std::string MWMechanics::AiFollow::getFollowedActor() { - return mActorId; + return mActorRefId; } MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const @@ -97,16 +114,22 @@ int MWMechanics::AiFollow::getTypeId() const return TypeIdFollow; } +bool MWMechanics::AiFollow::isCommanded() const +{ + return mCommanded; +} + void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr follow(new ESM::AiSequence::AiFollow()); follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; - follow->mTargetId = mActorId; + follow->mTargetId = mActorRefId; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; + follow->mCommanded = mCommanded; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; @@ -114,10 +137,25 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co sequence.mPackages.push_back(package); } -MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) - : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) - , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) - , mActorId(follow->mTargetId), mCellId(follow->mCellId) +MWWorld::Ptr MWMechanics::AiFollow::getTarget() { + if (mActorId == -2) + return MWWorld::Ptr(); + if (mActorId == -1) + { + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorRefId, false); + if (target.isEmpty()) + { + mActorId = -2; + return target; + } + else + mActorId = target.getClass().getCreatureStats(target).getActorId(); + } + + if (mActorId != -1) + return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); + else + return MWWorld::Ptr(); } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index e9587b36eb..483901b698 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -27,10 +27,12 @@ namespace MWMechanics /// Follow Actor for duration or until you arrive at a position in a cell AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); /// Follow Actor indefinitively - AiFollow(const std::string &ActorId); + AiFollow(const std::string &ActorId, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); + MWWorld::Ptr getTarget(); + virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -42,15 +44,19 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + bool isCommanded() const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; + bool mCommanded; float mRemainingDuration; // Seconds float mX; float mY; float mZ; - std::string mActorId; + std::string mActorRefId; + int mActorId; std::string mCellId; }; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 7790942b2f..f015bb8a4e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -16,7 +16,7 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : mLastDoorChecked(MWWorld::Ptr()), mTimer(.26), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild +MWMechanics::AiPackage::AiPackage() : mTimer(.26), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild } @@ -92,22 +92,19 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po { /// 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]) < 10 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care + 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().getTrap().empty() && mLastDoorChecked != door) { //Open the door if untrapped - door.getClass().activate(door, actor).get()->execute(actor); - mLastDoorChecked = door; + 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 { - // 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; + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // change the angle a bit, too zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); } @@ -115,7 +112,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po else { //Not stuck, so reset things mStuckTimer = 0; mStuckPos = pos; - mLastDoorChecked = MWWorld::Ptr(); //Resets it, in case he gets stuck behind the door again actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward } } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 983777c0ad..ff3e84b98a 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -69,12 +69,9 @@ namespace MWMechanics PathFinder mPathFinder; ObstacleCheck mObstacleCheck; - float mDoorCheckDuration; float mTimer; float mStuckTimer; - MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; }; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 60f671c12a..3ef0e8e963 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -32,12 +32,22 @@ AiPursue *MWMechanics::AiPursue::clone() const } bool AiPursue::execute (const MWWorld::Ptr& actor, float duration) { + if(actor.getClass().getCreatureStats(actor).isDead()) + return true; + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow - if(target == MWWorld::Ptr()) + if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered + // with the MechanicsManager + ) return true; //Target doesn't exist + if(target.getClass().getCreatureStats(target).isDead()) + return true; + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 18a22b6763..a6eef2984b 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -41,8 +41,6 @@ namespace MWMechanics private: int mTargetActorId; // The actor to pursue - int mCellX; - int mCellY; }; } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 3aeeee65a4..0341c9745d 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -31,9 +31,11 @@ void AiSequence::copy (const AiSequence& sequence) AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {} -AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) +AiSequence::AiSequence (const AiSequence& sequence) { copy (sequence); + mDone = sequence.mDone; + mLastAiPackage = sequence.mLastAiPackage; } AiSequence& AiSequence::operator= (const AiSequence& sequence) @@ -43,6 +45,7 @@ AiSequence& AiSequence::operator= (const AiSequence& sequence) clear(); copy (sequence); mDone = sequence.mDone; + mLastAiPackage = sequence.mLastAiPackage; } return *this; @@ -72,31 +75,52 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const return true; } -bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const +std::list::const_iterator AiSequence::begin() const { - bool firstCombatFound = false; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + return mPackages.begin(); +} +std::list::const_iterator AiSequence::end() const +{ + return mPackages.end(); +} + +void AiSequence::erase(std::list::const_iterator package) +{ + // Not sure if manually terminated packages should trigger mDone, probably not? + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if (package == it) + { + mPackages.erase(it); + return; + } + } + throw std::runtime_error("can't find package to erase"); +} + +bool AiSequence::isInCombat() const +{ + for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + return true; + } + return false; +} + +bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const +{ for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - firstCombatFound = true; - const AiCombat *combat = static_cast(*it); - if (combat->getTarget() != player ) return false; // only 1 non-player target allowed - else - { - // add new target only if current target (player) is farther - const ESM::Position &targetPos = combat->getTarget().getRefData().getPosition(); - - float distToCurrTarget = (Ogre::Vector3(targetPos.pos) - Ogre::Vector3(actorPos.pos)).length(); - return (distToCurrTarget > distToTarget); - } + if (combat->getTarget() == actor) + return true; } - else if (firstCombatFound) break; // assumes combat packages go one-by-one in packages list } - return true; + return false; } void AiSequence::stopCombat() @@ -211,6 +235,9 @@ void AiSequence::clear() void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("Can't add AI packages to player"); + if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) { // Notify AiWander of our current position so we can return to it after combat finished @@ -244,11 +271,6 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) mPackages.push_front (package.clone()); } -void AiSequence::queue (const AiPackage& package) -{ - mPackages.push_back (package.clone()); -} - AiPackage* MWMechanics::AiSequence::getActivePackage() { if(mPackages.empty()) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index b789d33cd0..56f5dee313 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -50,6 +50,12 @@ namespace MWMechanics virtual ~AiSequence(); + /// Iterator may be invalidated by any function calls other than begin() or end(). + std::list::const_iterator begin() const; + std::list::const_iterator end() const; + + void erase (std::list::const_iterator package); + /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ int getTypeId() const; @@ -63,6 +69,12 @@ namespace MWMechanics /// Return true and assign target if combat package is currently active, return false otherwise bool getCombatTarget (MWWorld::Ptr &targetActor) const; + /// Is there any combat package? + bool isInCombat () const; + + /// Are we in combat with this particular actor? + bool isInCombat (const MWWorld::Ptr& actor) const; + bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; ///< Function assumes that actor can have only 1 target apart player @@ -86,10 +98,6 @@ namespace MWMechanics @param actor The actor that owns this AiSequence **/ void stack (const AiPackage& package, const MWWorld::Ptr& actor); - /// Add \a package to the end of the sequence - /** Executed after all other packages have been completed **/ - void queue (const AiPackage& package); - /// Return the current active package. /** If there is no active package, it will throw an exception **/ AiPackage* getActivePackage(); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index db137037d5..7278e74f2f 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include #include "../mwbase/world.hpp" @@ -44,6 +46,8 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { @@ -68,6 +72,12 @@ namespace MWMechanics } } + // Maximum travel distance for vanilla compatibility. + // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. + // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. + if (Ogre::Vector3(mX, mY, mZ).squaredDistance(Ogre::Vector3(pos.pos)) > 7168*7168) + return false; + bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; if(!mPathFinder.isPathConstructed() || cellChange) { diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index b97554e2a6..aee3e654dd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -22,6 +22,9 @@ namespace MWMechanics { static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed 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; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) @@ -32,6 +35,8 @@ namespace MWMechanics void AiWander::init() { + // NOTE: mDistance and mDuration must be set already + mCellX = std::numeric_limits::max(); mCellY = std::numeric_limits::max(); mXCell = 0; @@ -43,7 +48,8 @@ namespace MWMechanics mReaction = 0; mRotate = false; mTargetAngle = 0; - mSaidGreeting = false; + mSaidGreeting = Greet_None; + mGreetingTimer = 0; mHasReturnPosition = false; mReturnPosition = Ogre::Vector3(0,0,0); @@ -221,14 +227,14 @@ namespace MWMechanics } mReaction += duration; - if(mReaction < 0.25f) // FIXME: hard coded constant + if(mReaction < REACTION_INTERVAL) { return false; } else mReaction = 0; - // NOTE: everything below get updated every 0.25 seconds + // NOTE: everything below get updated every REACTION_INTERVAL seconds MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) @@ -400,14 +406,18 @@ namespace MWMechanics static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() .get().find("fVoiceIdleOdds")->getFloat(); - if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) + // 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 < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500 + && MWBase::Environment::get().getWorld()->getLOS(player, actor)) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } } // Allow interrupting a walking actor to trigger a greeting - if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState() && mDistance)) + if(mIdleNow || mWalking) { // Play a random voice greeting if the player gets too close int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); @@ -421,9 +431,26 @@ namespace MWMechanics Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); float playerDistSqr = playerPos.squaredDistance(actorPos); - - if(playerDistSqr <= helloDistance*helloDistance) + + if (mSaidGreeting == 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)) + mGreetingTimer++; + + if (mGreetingTimer >= GREETING_SHOULD_START) + { + mSaidGreeting = Greet_InProgress; + MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + mGreetingTimer = 0; + } + } + + if(mSaidGreeting == Greet_InProgress) + { + mGreetingTimer++; + if(mWalking) { stopWalking(actor); @@ -449,31 +476,25 @@ namespace MWMechanics mRotate = true; } } - } - - if (!mSaidGreeting) - { - // TODO: check if actor is aware / has line of sight - if (playerDistSqr <= helloDistance*helloDistance - // Only play a greeting if the player is not moving - && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0) + + if (mGreetingTimer >= GREETING_SHOULD_END) { - mSaidGreeting = true; - MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + mSaidGreeting = Greet_Done; + mGreetingTimer = 0; } } - else + + if (mSaidGreeting == MWMechanics::AiWander::Greet_Done) { static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() .get().find("fGreetDistanceReset")->getFloat(); - if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset * iGreetDistanceMultiplier*iGreetDistanceMultiplier) - mSaidGreeting = false; + if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) + mSaidGreeting = Greet_None; } // Check if idle animation finished - // FIXME: don't stay forever - if(!checkIdle(actor, mPlayedIdle) && playerDistSqr > helloDistance*helloDistance) + if(!checkIdle(actor, mPlayedIdle) && (playerDistSqr > helloDistance*helloDistance || mSaidGreeting == MWMechanics::AiWander::Greet_Done)) { mPlayedIdle = 0; mIdleNow = false; @@ -665,17 +686,16 @@ namespace MWMechanics } AiWander::AiWander (const ESM::AiSequence::AiWander* wander) + : mDistance(wander->mData.mDistance) + , mDuration(wander->mData.mDuration) + , mStartTime(MWWorld::TimeStamp(wander->mStartTime)) + , mTimeOfDay(wander->mData.mTimeOfDay) + , mRepeat(wander->mData.mShouldRepeat) { - init(); - - mDistance = wander->mData.mDistance; - mDuration = wander->mData.mDuration; - mStartTime = MWWorld::TimeStamp(wander->mStartTime); - mTimeOfDay = wander->mData.mTimeOfDay; for (int i=0; i<8; ++i) mIdle.push_back(wander->mData.mIdle[i]); - mRepeat = wander->mData.mShouldRepeat; + init(); } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 7abd19e27a..59a51446ef 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -36,6 +36,7 @@ namespace MWMechanics AiWander (const ESM::AiSequence::AiWander* wander); + // NOTE: mDistance and mDuration must be set already void init(); virtual AiPackage *clone() const; @@ -62,7 +63,13 @@ namespace MWMechanics std::vector mIdle; bool mRepeat; - bool mSaidGreeting; + enum GreetingState { + Greet_None, + Greet_InProgress, + Greet_Done + }; + GreetingState mSaidGreeting; + int mGreetingTimer; bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 2e03122d5b..24e7b5aa11 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -27,6 +27,11 @@ #include "creaturestats.hpp" #include "npcstats.hpp" +MWMechanics::Alchemy::Alchemy() + : mValue(0) +{ +} + std::set MWMechanics::Alchemy::listEffects() const { std::map effects; @@ -181,7 +186,13 @@ void MWMechanics::Alchemy::updateEffects() ESM::ENAMstruct effect; effect.mEffectID = iter->mId; - effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work + effect.mAttribute = -1; + effect.mSkill = -1; + + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + effect.mSkill = iter->mArg; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + effect.mAttribute = iter->mArg; effect.mRange = 0; effect.mArea = 0; @@ -300,6 +311,9 @@ float MWMechanics::Alchemy::getChance() const const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats (mAlchemist); const NpcStats& npcStats = mAlchemist.getClass().getNpcStats (mAlchemist); + if (beginEffects() == endEffects()) + return 0.f; + return (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + 0.1 * creatureStats.getAttribute (1).getModified() @@ -444,7 +458,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na if (name.empty() && getPotionName().empty()) return Result_NoName; - if (beginEffects()==endEffects()) + if (listEffects().empty()) return Result_NoEffects; if (getChance() (RAND_MAX)*100) diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index 31cafa4dc7..a2429ca8ef 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -22,6 +22,8 @@ namespace MWMechanics { public: + Alchemy(); + typedef std::vector TToolsContainer; typedef TToolsContainer::const_iterator TToolsIterator; @@ -50,15 +52,12 @@ namespace MWMechanics TEffectsContainer mEffects; int mValue; - std::set listEffects() const; - ///< List all effects shared by at least two ingredients. - void applyTools (int flags, float& value) const; void updateEffects(); const ESM::Potion *getRecord() const; - ///< Return existing recrod for created potion (may return 0) + ///< Return existing record for created potion (may return 0) void removeIngredients(); ///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and @@ -75,6 +74,10 @@ namespace MWMechanics int countIngredients() const; + TEffectsIterator beginEffects() const; + + TEffectsIterator endEffects() const; + public: void setAlchemist (const MWWorld::Ptr& npc); @@ -94,6 +97,9 @@ namespace MWMechanics void clear(); ///< Remove alchemist, tools and ingredients. + std::set listEffects() const; + ///< List all effects shared by at least two ingredients. + int addIngredient (const MWWorld::Ptr& ingredient); ///< Add ingredient into the next free slot. /// @@ -103,10 +109,6 @@ namespace MWMechanics void removeIngredient (int index); ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). - TEffectsIterator beginEffects() const; - - TEffectsIterator endEffects() const; - std::string getPotionName() const; ///< Return the name of the potion that would be created when calling create (if a record for such /// a potion already exists) or return an empty string. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d4ddf53cd0..8f1a8f7187 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -196,7 +196,8 @@ 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) { mHitState = CharState_KnockOut; mCurrentHit = "knockout"; @@ -221,6 +222,21 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mCurrentHit = "shield"; mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); } + + // Cancel upper body animations + if (mHitState == CharState_KnockDown || mHitState == CharState_KnockOut) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_Nothing; + } + } } else if(!mAnimation->isPlaying(mCurrentHit)) { @@ -242,6 +258,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); + if (!mPtr.getClass().hasInventoryStore(mPtr)) + weap = sWeaponTypeListEnd; if(force || idle != mIdleState) { @@ -292,7 +310,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } } - if(mJumpState == JumpState_Falling) + if(mJumpState == JumpState_InAir) { int mode = ((jump == mCurrentJump) ? 2 : 1); @@ -380,22 +398,35 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat CharacterState walkState = runStateToWalkState(mMovementState); 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 + // 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; + } + 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) + { + // 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); + mMovementAnimationControlled = false; + } } - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - { - speedmult = mMovementSpeed / vel; - } - else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) - speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed - else if (mMovementSpeed > 0.0f) - { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); - mMovementAnimationControlled = false; - } mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } @@ -528,11 +559,11 @@ void CharacterController::playRandomDeath(float startpoint) { mDeathState = CharState_SwimDeath; } - else if (mHitState == CharState_KnockDown) + else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) { mDeathState = CharState_DeathKnockDown; } - else if (mHitState == CharState_KnockOut) + else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) { mDeathState = CharState_DeathKnockOut; } @@ -551,6 +582,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mIdleState(CharState_None) , mMovementState(CharState_None) , mMovementSpeed(0.0f) + , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) , mHitState(CharState_None) @@ -560,6 +592,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSkipAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) + , mTurnAnimationThreshold(0) { if(!mAnimation) return; @@ -607,6 +640,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if(mDeathState == CharState_None) refreshCurrentAnims(mIdleState, mMovementState, true); + + mAnimation->runAnimation(0.f); } CharacterController::~CharacterController() @@ -634,10 +669,10 @@ void CharacterController::updateIdleStormState() mAnimation->getInfo("idlestorm", &complete); if (complete == 0) - mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, 1.0f, "start", "loop start", 0.0f, 0); else if (complete == 1) - mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, 1.0f, "loop start", "loop stop", 0.0f, ~0ul); } else @@ -648,7 +683,7 @@ void CharacterController::updateIdleStormState() { if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) { - mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, true, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -658,32 +693,111 @@ void CharacterController::updateIdleStormState() } } +void CharacterController::castSpell(const std::string &spellid) +{ + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + const ESM::MagicEffect *effect; + effect = store.get().find(effectentry.mEffectID); + + const ESM::Static* castStatic; + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mCastSound.empty()) + sndMgr->playSound3D(mPtr, effect->mCastSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); +} + bool CharacterController::updateCreatureState() { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); + WeaponType weapType = WeapType_None; + if(stats.getDrawState() == DrawState_Weapon) + weapType = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weapType = WeapType_Spell; + + if (weapType != mWeaponType) + { + mWeaponType = weapType; + if (mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->disable(mCurrentWeapon); + } + if(stats.getAttackingOrSpell()) { if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - // These are unique animations and not linked to movement type. Just pick one randomly. - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 3; // [0, 2] - if (roll == 0) - mCurrentWeapon = "attack1"; - else if (roll == 1) - mCurrentWeapon = "attack2"; - else - mCurrentWeapon = "attack3"; + std::string startKey = "start"; + std::string stopKey = "stop"; + if (weapType == WeapType_Spell) + { + const std::string spellid = stats.getSpells().getSelectedSpell(); + if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) + { + castSpell(spellid); - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_All, true, - 1, "start", "stop", - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; + if (!mAnimation->hasAnimation("spellcast")) + MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately + else + { + const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + switch(effectentry.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + startKey = mAttackType + " " + startKey; + stopKey = mAttackType + " " + stopKey; + mCurrentWeapon = "spellcast"; + } + } + else + mCurrentWeapon = ""; + } + if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation + { + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 3; // [0, 2] + if (roll == 0) + mCurrentWeapon = "attack1"; + else if (roll == 1) + mCurrentWeapon = "attack2"; + else + mCurrentWeapon = "attack3"; + } + + if (!mCurrentWeapon.empty()) + { + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_All, true, + 1, startKey, stopKey, + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + } } + + stats.setAttackingOrSpell(false); } bool animPlaying = mAnimation->getInfo(mCurrentWeapon); @@ -763,6 +877,7 @@ bool CharacterController::updateWeaponState() { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + && mHasMovedInXY && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mWeaponType == WeapType_None) { @@ -820,9 +935,7 @@ bool CharacterController::updateWeaponState() if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + castSpell(spellid); const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); @@ -830,19 +943,17 @@ bool CharacterController::updateWeaponState() const ESM::MagicEffect *effect; effect = store.get().find(effectentry.mEffectID); - const ESM::Static* castStatic; - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + if (mAnimation->getNode("Left Hand")) + { + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); + } else - castStatic = store.get().find ("VFX_DefaultCast"); - - mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); - - castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); - //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); + { + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); + } switch(effectentry.mRange) { @@ -856,12 +967,6 @@ bool CharacterController::updateWeaponState() weapSpeed, mAttackType+" start", mAttackType+" stop", 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!effect->mCastSound.empty()) - sndMgr->playSound3D(mPtr, effect->mCastSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } if (inv.getSelectedEnchantItem() != inv.end()) { @@ -872,6 +977,7 @@ bool CharacterController::updateWeaponState() else if(mWeaponType == WeapType_PickProbe) { MWWorld::Ptr item = *weapon; + // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); std::string resultMessage, resultSound; @@ -921,6 +1027,15 @@ bool CharacterController::updateWeaponState() animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) { + float attackStrength = complete; + if (!mPtr.getClass().isNpc()) + { + // most creatures don't actually have an attack wind-up animation, so use a uniform random value + // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) + // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. + attackStrength = std::min(1.f, 0.1f + std::rand() / float(RAND_MAX)); + } + if(mAttackType != "shoot") { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -935,15 +1050,15 @@ bool CharacterController::updateWeaponState() else { std::string sound = "SwishM"; - if(complete < 0.5f) + if(attackStrength < 0.5f) sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack - else if(complete < 1.0f) + else if(attackStrength < 1.0f) sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack else sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack } } - stats.setAttackStrength(complete); + stats.setAttackStrength(attackStrength); mAnimation->disable(mCurrentWeapon); mAnimation->play(mCurrentWeapon, Priority_Weapon, @@ -1157,17 +1272,38 @@ void CharacterController::update(float duration) bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool flying = world->isFlying(mPtr); - //Ogre::Vector3 vec = cls.getMovementVector(mPtr); - Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); - if(vec.z > 0.0f) // to avoid slow-down when jumping + CreatureStats &stats = cls.getCreatureStats(mPtr); + + //Force Jump Logic + + bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + if(!inwater && !flying) { - Ogre::Vector2 vecXY = Ogre::Vector2(vec.x, vec.y); - vecXY.normalise(); - vec.x = vecXY.x; - vec.y = vecXY.y; + //Force Jump + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) + { + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + //Force Move Jump, only jump if they're otherwise moving + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) + { + + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } } - else - vec.normalise(); + + Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); + vec.normalise(); if(mHitState != CharState_None && mJumpState == JumpState_None) vec = Ogre::Vector3(0.0f); @@ -1182,11 +1318,12 @@ void CharacterController::update(float duration) CharacterState idlestate = CharState_SpecialIdle; bool forcestateupdate = false; - isrunning = isrunning && std::abs(vec[0])+std::abs(vec[1]) > 0.0f; + mHasMovedInXY = std::abs(vec[0])+std::abs(vec[1]) > 0.0f; + isrunning = isrunning && mHasMovedInXY; // advance athletics - if(std::abs(vec[0])+std::abs(vec[1]) > 0.0f && mPtr.getRefData().getHandle() == "player") + if(mHasMovedInXY && mPtr.getRefData().getHandle() == "player") { if(inwater) { @@ -1260,32 +1397,28 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).land(); } - forcestateupdate = (mJumpState != JumpState_Falling); - mJumpState = JumpState_Falling; + forcestateupdate = (mJumpState != JumpState_InAir); + mJumpState = JumpState_InAir; // This is a guess. All that seems to be known is that "While the player is in the - // air, fJumpMoveBase and fJumpMoveMult governs air control." Assuming Acrobatics - // plays a role, this makes the most sense. - float mult = 0.0f; - if(cls.isNpc()) - { - const NpcStats &stats = cls.getNpcStats(mPtr); - static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + // air, fJumpMoveBase and fJumpMoveMult governs air control". What does fJumpMoveMult do? + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - mult = fJumpMoveBase + - (stats.getSkill(ESM::Skill::Acrobatics).getModified()/100.0f * - fJumpMoveMult); - } - - vec.x *= mult; - vec.y *= mult; + vec.x *= fJumpMoveBase; + vec.y *= fJumpMoveBase; vec.z = 0.0f; } else if(vec.z > 0.0f && mJumpState == JumpState_None) { // Started a jump. - vec.z = cls.getJump(mPtr); + float z = cls.getJump(mPtr); + if(vec.x == 0 && vec.y == 0) + vec = Ogre::Vector3(0.0f, 0.0f, z); + else + { + Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); + vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + } // advance acrobatics if (mPtr.getRefData().getHandle() == "player") @@ -1301,7 +1434,7 @@ void CharacterController::update(float duration) fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); cls.getCreatureStats(mPtr).setFatigue(fatigue); } - else if(mJumpState == JumpState_Falling) + else if(mJumpState == JumpState_InAir) { forcestateupdate = true; mJumpState = JumpState_Landing; @@ -1335,8 +1468,7 @@ void CharacterController::update(float duration) } else { - if(!(vec.z > 0.0f)) - mJumpState = JumpState_None; + mJumpState = JumpState_None; vec.z = 0.0f; inJump = false; @@ -1375,6 +1507,15 @@ void CharacterController::update(float duration) } } + mTurnAnimationThreshold -= duration; + if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft) + mTurnAnimationThreshold = 0.05; + else if (movestate == CharState_None && (mMovementState == CharState_TurnRight || mMovementState == CharState_TurnLeft) + && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; + } + if (onground) cls.getCreatureStats(mPtr).land(); @@ -1405,6 +1546,12 @@ void CharacterController::update(float duration) if (inJump) mMovementAnimationControlled = false; + if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + { + if (duration > 0) + mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z) / duration / Ogre::Math::PI)); + } + if (!mSkipAnim) { rot *= Ogre::Math::RadiansToDegrees(1.0f); @@ -1423,7 +1570,9 @@ void CharacterController::update(float duration) world->queueMovement(mPtr, Ogre::Vector3(0.0f)); movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0; + cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; + // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame + // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. } else if(cls.getCreatureStats(mPtr).isDead()) { @@ -1465,6 +1614,8 @@ void CharacterController::update(float duration) else if (mAnimation) mAnimation->updateEffects(duration); mSkipAnim = false; + + mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); } @@ -1552,6 +1703,10 @@ bool CharacterController::kill() mIdleState = CharState_None; mCurrentIdle.clear(); + // Play Death Music if it was the player dying + if(mPtr.getRefData().getHandle()=="player") + MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); + return true; } @@ -1578,7 +1733,7 @@ void CharacterController::updateContinuousVfx() for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) { if (mPtr.getClass().getCreatureStats(mPtr).isDead() - || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).mMagnitude <= 0) + || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0) mAnimation->removeEffect(*it); } } @@ -1588,20 +1743,23 @@ void CharacterController::updateVisibility() if (!mPtr.getClass().isActor()) return; float alpha = 1.f; - if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude) + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()) { if (mPtr.getRefData().getHandle() == "player") alpha = 0.4f; else alpha = 0.f; } - float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude; + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); if (chameleon) { alpha *= std::max(0.2f, (100.f - chameleon)/100.f); } mAnimation->setAlpha(alpha); + + float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); + mAnimation->setLightEffect(light); } void CharacterController::determineAttackType() @@ -1610,9 +1768,9 @@ void CharacterController::determineAttackType() if(mPtr.getClass().hasInventoryStore(mPtr)) { - if (move[1]) // forward-backward + if (move[1] && !move[0]) // forward-backward mAttackType = "thrust"; - else if (move[0]) //sideway + else if (move[0] && !move[1]) //sideway mAttackType = "slash"; else mAttackType = "chop"; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index b1e1738bd9..550cae5fc0 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -32,6 +32,7 @@ enum Priority { Priority_Weapon, Priority_Knockdown, Priority_Torch, + Priority_Storm, Priority_Death, @@ -129,7 +130,7 @@ enum UpperBodyCharacterState { enum JumpingState { JumpState_None, - JumpState_Falling, + JumpState_InAir, JumpState_Landing }; @@ -147,6 +148,7 @@ class CharacterController CharacterState mMovementState; std::string mCurrentMovement; float mMovementSpeed; + bool mHasMovedInXY; bool mMovementAnimationControlled; CharacterState mDeathState; @@ -169,6 +171,8 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; + float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning + std::string mAttackType; // slash, chop or thrust void determineAttackType(); @@ -180,6 +184,8 @@ class CharacterController bool updateCreatureState(); void updateIdleStormState(); + void castSpell(const std::string& spellid); + void updateVisibility(); void playDeath(float startpoint, CharacterState death); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 6feb5879b0..f48e82324d 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -10,6 +10,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/difficultyscaling.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -54,11 +55,24 @@ namespace MWMechanics if (!blocker.getClass().hasInventoryStore(blocker)) return false; - if (blocker.getClass().getCreatureStats(blocker).getKnockedDown() - || blocker.getClass().getCreatureStats(blocker).getHitRecovery()) + MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); + + if (blockerStats.getKnockedDown() // Used for both knockout or knockdown + || blockerStats.getHitRecovery() + || blockerStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + return false; + + // Don't block when in spellcasting state (shield is equipped, but not visible) + if (blockerStats.getDrawState() == DrawState_Spell) return false; MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); + + // Don't block when in hand-to-hand combat (shield is equipped, but not visible) + if (blockerStats.getDrawState() == DrawState_Weapon && + inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) + return false; + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) return false; @@ -72,17 +86,6 @@ namespace MWMechanics if (angle.valueDegrees() > gmst.find("fCombatBlockRightAngle")->getFloat()) return false; - MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); - - // Don't block when in spellcasting state (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Spell) - return false; - - // Don't block when in hand-to-hand combat (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Weapon && - inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) - return false; - MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2 * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() @@ -130,7 +133,7 @@ namespace MWMechanics blockerStats.setBlock(true); - if (blocker.getClass().isNpc()) + if (blocker.getCellRef().getRefId() == "player") blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); return true; @@ -141,14 +144,10 @@ 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).mMagnitude - - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).mMagnitude); + float resistance = std::min(100.f, stats.getMagicEffects().get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() + - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude()); - float multiplier = 0; - if (resistance >= 0) - multiplier = 1 - resistance / 100.f; - else - multiplier = -(resistance-100) / 100.f; + float multiplier = 1.f - resistance / 100.f; if (!(weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) @@ -170,12 +169,12 @@ namespace MWMechanics MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); - const MWWorld::Class &othercls = victim.getClass(); - if(!othercls.isActor()) // Can't hit non-actors - return; - MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); - if(otherstats.isDead()) // Can't hit dead actors + if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) + // Can't hit non-actors or dead actors + { + reduceWeaponCondition(0.f, false, weapon, attacker); return; + } if(attacker.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setEnemy(victim); @@ -190,16 +189,15 @@ namespace MWMechanics if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); + MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); return; } - float damage = 0.0f; - float fDamageStrengthBase = gmst.find("fDamageStrengthBase")->getFloat(); float fDamageStrengthMult = gmst.find("fDamageStrengthMult")->getFloat(); const unsigned char* attack = weapon.get()->mBase->mData.mChop; - damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage + float damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage if (weapon != projectile) { // Arrow/bolt damage @@ -210,16 +208,12 @@ namespace MWMechanics damage *= fDamageStrengthBase + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); + adjustWeaponDamage(damage, weapon); + reduceWeaponCondition(damage, true, weapon, attacker); + if(attacker.getRefData().getHandle() == "player") attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); - bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); - if(!detected) - { - damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); - } if (victim.getClass().getCreatureStats(victim).getKnockedDown()) damage *= gmst.find("fCombatKODamageMult")->getFloat(); @@ -250,10 +244,94 @@ namespace MWMechanics (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - - mageffects.get(ESM::MagicEffect::Blind).mMagnitude; + hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).getMagnitude() - + mageffects.get(ESM::MagicEffect::Blind).getMagnitude(); hitchance -= victim.getClass().getCreatureStats(victim).getEvasion(); return hitchance; } + void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim) + { + for (int i=0; i<3; ++i) + { + float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude(); + + if (!magnitude) + continue; + + CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); + float saveTerm = attacker.getClass().getSkill(attacker, ESM::Skill::Destruction) + + 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified() + + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); + + int fatigueMax = attackerStats.getFatigue().getModified(); + int fatigueCurrent = attackerStats.getFatigue().getCurrent(); + + float normalisedFatigue = fatigueMax==0 ? 1 : std::max (0.0f, static_cast (fatigueCurrent)/fatigueMax); + + saveTerm *= 1.25f * normalisedFatigue; + + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + float x = std::max(0.f, saveTerm - roll); + + int element = ESM::MagicEffect::FireDamage; + if (i == 1) + element = ESM::MagicEffect::ShockDamage; + if (i == 2) + element = ESM::MagicEffect::FrostDamage; + + float elementResistance = MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects()); + + x = std::min(100.f, x + elementResistance); + + static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get().find("fElementalShieldMult")->getFloat(); + x = fElementalShieldMult * magnitude * (1.f - 0.01f * x); + + // Note swapped victim and attacker, since the attacker takes the damage here. + x = scaleDamage(x, victim, attacker); + + MWMechanics::DynamicStat health = attackerStats.getHealth(); + health.setCurrent(health.getCurrent() - x); + attackerStats.setHealth(health); + } + } + + void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr &weapon, const MWWorld::Ptr &attacker) + { + if (weapon.isEmpty()) + return; + + if (!hit) + damage = 0.f; + + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + + const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->getFloat(); + float x = std::max(1.f, fWeaponDamageMult * damage); + + weaphealth -= std::min(int(x), weaphealth); + weapon.getCellRef().setCharge(weaphealth); + + // Weapon broken? unequip it + if (weaphealth == 0) + weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker); + } + } + + void adjustWeaponDamage(float &damage, const MWWorld::Ptr &weapon) + { + if (weapon.isEmpty()) + return; + + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + damage *= (float(weaphealth) / weapmaxhealth); + } + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index bc58227bf6..0d3009510b 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -13,12 +13,25 @@ bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt +/// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const Ogre::Vector3& hitPosition); /// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); +/// Applies damage to attacker based on the victim's elemental shields. +void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); + +/// @param damage Unmitigated weapon damage of the attack +/// @param hit Was the attack successful? +/// @param weapon The weapon used. +/// @note if the weapon is unequipped as result of condition damage, a new Ptr will be assigned to \a weapon. +void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); + +/// Adjust weapon damage based on its condition. A used weapon will be less effective. +void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon); + } #endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index af35a109a6..c4d316ad62 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" namespace MWMechanics { @@ -16,7 +17,7 @@ namespace MWMechanics CreatureStats::CreatureStats() : mLevel (0), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), - mAttacked (false), mHostile (false), + mAttacked (false), mAttackingOrSpell(false), mIsWerewolf(false), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), @@ -119,11 +120,6 @@ namespace MWMechanics return mSpells; } - void CreatureStats::setSpells(const Spells &spells) - { - mSpells = spells; - } - ActiveSpells &CreatureStats::getActiveSpells() { return mActiveSpells; @@ -187,10 +183,10 @@ namespace MWMechanics if (index==0 && mDynamic[index].getCurrent()<1) { - if (!mDead) - mDied = true; - mDead = true; + + if (MWBase::Environment::get().getWorld()->getGodModeState()) + MWBase::Environment::get().getMechanicsManager()->keepPlayerAlive(); } } @@ -199,18 +195,13 @@ namespace MWMechanics mLevel = level; } - void CreatureStats::setActiveSpells(const ActiveSpells &active) + void CreatureStats::modifyMagicEffects(const MagicEffects &effects) { - mActiveSpells = active; - } - - void CreatureStats::setMagicEffects(const MagicEffects &effects) - { - if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude - != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude) + if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() + != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()) mRecalcDynamicStats = true; - mMagicEffects = effects; + mMagicEffects.setModifiers(effects); } void CreatureStats::setAttackingOrSpell(bool attackingOrSpell) @@ -235,6 +226,11 @@ namespace MWMechanics return mDead; } + void CreatureStats::notifyDied() + { + mDied = true; + } + bool CreatureStats::hasDied() const { return mDied; @@ -264,11 +260,7 @@ namespace MWMechanics { if (mDead) { - if (mDynamic[0].getCurrent()<1) - { - mDynamic[0].setModified(mDynamic[0].getModified(), 1); - mDynamic[0].setCurrent(1); - } + mDynamic[0].setCurrent(mDynamic[0].getModified()); if (mDynamic[0].getCurrent()>=1) mDead = false; } @@ -324,16 +316,6 @@ namespace MWMechanics mAttacked = attacked; } - bool CreatureStats::isHostile() const - { - return mHostile; - } - - void CreatureStats::setHostile (bool hostile) - { - mHostile = hostile; - } - bool CreatureStats::getCreatureTargetted() const { MWWorld::Ptr targetPtr; @@ -349,7 +331,7 @@ namespace MWMechanics float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); evasion *= getFatigueTerm(); - evasion += mMagicEffects.get(ESM::MagicEffect::Sanctuary).mMagnitude; + evasion += mMagicEffects.get(ESM::MagicEffect::Sanctuary).getMagnitude(); return evasion; } @@ -386,6 +368,11 @@ namespace MWMechanics return false; } + void CreatureStats::setNeedRecalcDynamicStats(bool val) + { + mRecalcDynamicStats = val; + } + void CreatureStats::setKnockedDown(bool value) { mKnockdown = value; @@ -499,7 +486,6 @@ namespace MWMechanics state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; state.mAttacked = mAttacked; - state.mHostile = mHostile; state.mAttackingOrSpell = mAttackingOrSpell; // TODO: rewrite. does this really need 3 separate bools? state.mKnockdown = mKnockdown; @@ -520,6 +506,7 @@ namespace MWMechanics mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); mAiSequence.writeState(state.mAiSequence); + mMagicEffects.writeState(state.mMagicEffects); state.mSummonedCreatureMap = mSummonedCreatures; state.mSummonGraveyard = mSummonGraveyard; @@ -539,7 +526,6 @@ namespace MWMechanics mLastRestock = MWWorld::TimeStamp(state.mTradeTime); mGoldPool = state.mGoldPool; - mFallHeight = state.mFallHeight; mDead = state.mDead; mDied = state.mDied; @@ -548,7 +534,6 @@ namespace MWMechanics mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; mAttacked = state.mAttacked; - mHostile = state.mHostile; mAttackingOrSpell = state.mAttackingOrSpell; // TODO: rewrite. does this really need 3 separate bools? mKnockdown = state.mKnockdown; @@ -569,6 +554,7 @@ namespace MWMechanics mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); mAiSequence.readState(state.mAiSequence); + mMagicEffects.readState(state.mMagicEffects); mSummonedCreatures = state.mSummonedCreatureMap; mSummonGraveyard = state.mSummonGraveyard; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index b365a0b89c..5e169ffb03 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -40,7 +40,6 @@ namespace MWMechanics bool mTalkedTo; bool mAlarmed; bool mAttacked; - bool mHostile; bool mAttackingOrSpell; bool mKnockdown; bool mKnockdownOneFrame; @@ -92,6 +91,7 @@ namespace MWMechanics void setAttackStrength(float value); bool needToRecalcDynamicStats(); + void setNeedRecalcDynamicStats(bool val); void addToFallHeight(float height); @@ -137,11 +137,8 @@ namespace MWMechanics void setDynamic (int index, const DynamicStat &value); - void setSpells(const Spells &spells); - - void setActiveSpells(const ActiveSpells &active); - - void setMagicEffects(const MagicEffects &effects); + /// Set Modifier for each magic effect according to \a effects. Does not touch Base values. + void modifyMagicEffects(const MagicEffects &effects); void setAttackingOrSpell(bool attackingOrSpell); @@ -167,6 +164,8 @@ namespace MWMechanics bool isDead() const; + void notifyDied(); + bool hasDied() const; void clearHasDied(); @@ -200,15 +199,13 @@ namespace MWMechanics bool getAttacked() const; void setAttacked (bool attacked); - bool isHostile() const; - void setHostile (bool hostile); - bool getCreatureTargetted() const; float getEvasion() const; void setKnockedDown(bool value); - ///Returns true for the entire duration of the actor being knocked down + /// Returns true for the entire duration of the actor being knocked down or knocked out, + /// including transition animations (falling down & standing up) bool getKnockedDown() const; void setKnockedDownOneFrame(bool value); ///Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command @@ -229,7 +226,9 @@ namespace MWMechanics Flag_ForceRun = 1, Flag_ForceSneak = 2, Flag_Run = 4, - Flag_Sneak = 8 + Flag_Sneak = 8, + Flag_ForceJump = 16, + Flag_ForceMoveJump = 32 }; enum Stance { diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp new file mode 100644 index 0000000000..05ab12ccd2 --- /dev/null +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -0,0 +1,38 @@ +#include "difficultyscaling.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/esmstore.hpp" + +#include + +float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) +{ + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // [-100, 100] + int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); + + static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->getFloat(); + + float difficultyTerm = 0.01f * difficultySetting; + + float x = 0; + if (victim == player) + { + if (difficultyTerm > 0) + x = fDifficultyMult * difficultyTerm; + else + x = difficultyTerm / fDifficultyMult; + } + else if (attacker == player) + { + if (difficultyTerm > 0) + x = -difficultyTerm / fDifficultyMult; + else + x = fDifficultyMult * (-difficultyTerm); + } + + damage *= 1 + x; + return damage; +} diff --git a/apps/openmw/mwmechanics/difficultyscaling.hpp b/apps/openmw/mwmechanics/difficultyscaling.hpp new file mode 100644 index 0000000000..168cf10556 --- /dev/null +++ b/apps/openmw/mwmechanics/difficultyscaling.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_MWMECHANICS_DIFFICULTYSCALING_H +#define OPENMW_MWMECHANICS_DIFFICULTYSCALING_H + +namespace MWWorld +{ + class Ptr; +} + +/// Scales damage dealt to an actor based on difficulty setting +float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); + +#endif diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 5f73e8acd9..fa97f15417 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -14,9 +14,11 @@ namespace MWMechanics { /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him) + /// @param actor The actor that will potentially catch diseases. Currently only the player can catch diseases. + /// @param carrier The disease carrier. inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) { - if (!carrier.getClass().isActor()) + if (!carrier.getClass().isActor() || actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) return; float fDiseaseXferChance = @@ -27,25 +29,47 @@ namespace MWMechanics for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - if (spell->mData.mType == ESM::Spell::ST_Disease - || spell->mData.mType == ESM::Spell::ST_Blight) - { - float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < fDiseaseXferChance) - { - // Contracted disease! - actor.getClass().getCreatureStats(actor).getSpells().add(it->first); - if (actor.getRefData().getHandle() == "player") - { - std::string msg = "sMagicContractDisease"; - msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); - if (msg.find("%s") != std::string::npos) - msg.replace(msg.find("%s"), 2, spell->mName); - MWBase::Environment::get().getWindowManager()->messageBox(msg); - } + if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) + continue; + + bool hasCorprusEffect = false; + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mEffectID == ESM::MagicEffect::Corprus) + { + hasCorprusEffect = true; + break; } } + + float resist = 0.f; + if (hasCorprusEffect) + resist = 1.f - 0.01 * (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() + - actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); + else if (spell->mData.mType == ESM::Spell::ST_Disease) + resist = 1.f - 0.01 * (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() + - actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::WeaknessToCommonDisease).getMagnitude()); + else if (spell->mData.mType == ESM::Spell::ST_Blight) + resist = 1.f - 0.01 * (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).getMagnitude() + - actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::WeaknessToBlightDisease).getMagnitude()); + else + continue; + + int x = fDiseaseXferChance * 100 * resist; + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 10000; // [0, 9999] + + if (roll < x) + { + // Contracted disease! + actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + + std::string msg = "sMagicContractDisease"; + msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); + if (msg.find("%s") != std::string::npos) + msg.replace(msg.find("%s"), 2, spell->mName); + MWBase::Environment::get().getWindowManager()->messageBox(msg); + } } } } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index f3f6795db8..63ffbc7e8c 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -62,7 +62,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() (RAND_MAX)*100) + if(std::rand()/static_cast (RAND_MAX)*100 < getEnchantChance()) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); @@ -166,16 +166,15 @@ namespace MWMechanics float enchantmentCost = 0; int effectsLeftCnt = mEffects.size(); - float baseCost, magnitudeCost, areaCost; - int magMin, magMax, area; for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - baseCost = (store.get().find(it->mEffectID))->mData.mBaseCost; + float baseCost = (store.get().find(it->mEffectID))->mData.mBaseCost; // To reflect vanilla behavior - magMin = (it->mMagnMin == 0) ? 1 : it->mMagnMin; - magMax = (it->mMagnMax == 0) ? 1 : it->mMagnMax; - area = (it->mArea == 0) ? 1 : it->mArea; + int magMin = (it->mMagnMin == 0) ? 1 : it->mMagnMin; + int magMax = (it->mMagnMax == 0) ? 1 : it->mMagnMax; + int area = (it->mArea == 0) ? 1 : it->mArea; + float magnitudeCost = 0; if (mCastStyle == ESM::Enchantment::ConstantEffect) { magnitudeCost = (magMin + magMax) * baseCost * 2.5; @@ -187,7 +186,7 @@ namespace MWMechanics magnitudeCost *= 1.5; } - areaCost = area * 0.025 * baseCost; + float areaCost = area * 0.025 * baseCost; if (it->mRange == ESM::RT_Target) areaCost *= 1.5; @@ -292,5 +291,9 @@ namespace MWMechanics MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); + + // add gold to NPC trading gold pool + CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter); + enchanterStats.setGoldPool(enchanterStats.getGoldPool() + getEnchantPrice()); } } diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 5be0854aba..0b19df0a82 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace MWMechanics { @@ -40,20 +41,57 @@ namespace MWMechanics return left.mArgsecond.setModifier(effects.get(it->first).getModifier()); + } + + for (Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + mCollection[it->first].setModifier(it->second.getModifier()); + } + } + MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) { if (this==&effects) @@ -137,4 +193,25 @@ namespace MWMechanics return result; } + + void MagicEffects::writeState(ESM::MagicEffects &state) const + { + // Don't need to save Modifiers, they are recalculated every frame anyway. + for (Collection::const_iterator iter (begin()); iter!=end(); ++iter) + { + if (iter->second.getBase() != 0) + { + // Don't worry about mArg, never used by magic effect script instructions + state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase())); + } + } + } + + void MagicEffects::readState(const ESM::MagicEffects &state) + { + for (std::map::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it) + { + mCollection[EffectKey(it->first)].setBase(it->second); + } + } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 4fd5e159aa..0a8392dabd 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -8,6 +8,8 @@ namespace ESM { struct ENAMstruct; struct EffectList; + + struct MagicEffects; } namespace MWMechanics @@ -28,12 +30,27 @@ namespace MWMechanics struct EffectParam { - // Note usually this would be int, but applying partial resistance might introduce decimal point. - float mMagnitude; + private: + // Note usually this would be int, but applying partial resistance might introduce a decimal point. + float mModifier; + + int mBase; + + public: + /// Get the total magnitude including base and modifier. + float getMagnitude() const; + + void setModifier(float mod); + float getModifier() const; + + /// Change mBase by \a diff + void modifyBase(int diff); + void setBase(int base); + int getBase() const; EffectParam(); - EffectParam(float magnitude) : mMagnitude(magnitude) {} + EffectParam(float magnitude) : mModifier(magnitude), mBase(0) {} EffectParam& operator+= (const EffectParam& param); @@ -77,7 +94,16 @@ namespace MWMechanics Collection::const_iterator end() const { return mCollection.end(); } + void readState (const ESM::MagicEffects& state); + void writeState (ESM::MagicEffects& state) const; + void add (const EffectKey& key, const EffectParam& param); + void remove (const EffectKey& key); + + void modifyBase (const EffectKey& key, int diff); + + /// Copy Modifier values from \a effects, but keep original mBase values. + void setModifiers(const MagicEffects& effects); MagicEffects& operator+= (const MagicEffects& effects); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index d7c3e2f56d..6ca4a43362 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -21,6 +21,8 @@ #include "spellcasting.hpp" #include "autocalcspell.hpp" +#include + namespace { /// @return is \a ptr allowed to take/use \a item or is it a crime? @@ -34,10 +36,19 @@ namespace if (!faction.empty() && ptr.getClass().isNpc()) { const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); - if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end()) + std::map::const_iterator found = factions.find(Misc::StringUtils::lowerCase(faction)); + if (found == factions.end() + || found->second < item.getCellRef().getFactionRank()) isFactionOwned = true; } + const std::string& globalVariable = item.getCellRef().getGlobalVariable(); + if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(Misc::StringUtils::lowerCase(globalVariable)) == 1) + { + isOwned = false; + isFactionOwned = false; + } + if (!item.getCellRef().getOwner().empty()) victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().getOwner(), true); @@ -59,7 +70,7 @@ namespace MWMechanics // reset creatureStats.setLevel(player->mNpdt52.mLevel); creatureStats.getSpells().clear(); - creatureStats.setMagicEffects(MagicEffects()); + creatureStats.modifyMagicEffects(MagicEffects()); for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->mNpdt52.mSkills[i]); @@ -277,7 +288,7 @@ namespace MWMechanics } MechanicsManager::MechanicsManager() - : mUpdatePlayer (true), mClassSelected (false), + : mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false), mRaceSelected (false), mAI(true) { //buildPlayer no longer here, needs to be done explicitely after all subsystems are up and running @@ -339,7 +350,7 @@ namespace MWMechanics const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched); for(int i = 0;i < ESM::Attribute::Length;++i) { - if(stats.getAttribute(i) != mWatchedStats.getAttribute(i)) + if(stats.getAttribute(i) != mWatchedStats.getAttribute(i) || mWatchedStatsEmpty) { std::stringstream attrname; attrname << "AttribVal"<<(i+1); @@ -349,19 +360,19 @@ namespace MWMechanics } } - if(stats.getHealth() != mWatchedStats.getHealth()) + if(stats.getHealth() != mWatchedStats.getHealth() || mWatchedStatsEmpty) { static const std::string hbar("HBar"); mWatchedStats.setHealth(stats.getHealth()); winMgr->setValue(hbar, stats.getHealth()); } - if(stats.getMagicka() != mWatchedStats.getMagicka()) + if(stats.getMagicka() != mWatchedStats.getMagicka() || mWatchedStatsEmpty) { static const std::string mbar("MBar"); mWatchedStats.setMagicka(stats.getMagicka()); winMgr->setValue(mbar, stats.getMagicka()); } - if(stats.getFatigue() != mWatchedStats.getFatigue()) + if(stats.getFatigue() != mWatchedStats.getFatigue() || mWatchedStatsEmpty) { static const std::string fbar("FBar"); mWatchedStats.setFatigue(stats.getFatigue()); @@ -387,7 +398,7 @@ namespace MWMechanics //Loop over ESM::Skill::SkillEnum for(int i = 0; i < ESM::Skill::Length; ++i) { - if(stats.getSkill(i) != mWatchedStats.getSkill(i)) + if(stats.getSkill(i) != mWatchedStats.getSkill(i) || mWatchedStatsEmpty) { update = true; mWatchedStats.getSkill(i) = stats.getSkill(i); @@ -400,6 +411,8 @@ namespace MWMechanics winMgr->setValue("level", stats.getLevel()); + mWatchedStatsEmpty = false; + // Update the equipped weapon icon MWWorld::InventoryStore& inv = mWatched.getClass().getInventoryStore(mWatched); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -604,7 +617,7 @@ namespace MWMechanics if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispWeaponDrawn")->getFloat(); - x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).mMagnitude; + x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude(); int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used return effective_disposition; @@ -653,7 +666,6 @@ namespace MWMechanics return mActors.countDeaths (id); } - void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) { @@ -859,6 +871,12 @@ namespace MWMechanics bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) { + if (ptr.getClass().getNpcStats(ptr).isWerewolf()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + return true; + } + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); return true; @@ -960,6 +978,9 @@ namespace MWMechanics if (!it->getClass().isNpc()) continue; + if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) + continue; + // Will the witness report the crime? if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { @@ -1058,6 +1079,9 @@ namespace MWMechanics if (*it != victim && type == OT_Assault) aggression = iFightAttacking; + if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) + continue; + if (it->getClass().isClass(*it, "guard")) { // Mark as Alarmed for dialogue @@ -1087,6 +1111,52 @@ namespace MWMechanics } } + bool MechanicsManager::actorAttacked(const MWWorld::Ptr &ptr, const MWWorld::Ptr &attacker) + { + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + return false; + + std::list followers = getActorsFollowing(attacker); + if (std::find(followers.begin(), followers.end(), ptr) != followers.end()) + { + ptr.getClass().getCreatureStats(ptr).friendlyHit(); + + if (ptr.getClass().getCreatureStats(ptr).getFriendlyHits() < 4) + { + MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); + return false; + } + } + + // Attacking an NPC that is already in combat with any other NPC is not a crime + AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + bool isFightingNpc = false; + for (std::list::const_iterator it = seq.begin(); it != seq.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + { + MWWorld::Ptr target = static_cast(*it)->getTarget(); + if (!target.isEmpty() && target.getClass().isNpc()) + isFightingNpc = true; + } + } + + if (ptr.getClass().isNpc() && !attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().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 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. + startCombat(ptr, attacker); + } + + return true; + } + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) { if (observer.getClass().getCreatureStats(observer).isDead()) @@ -1096,7 +1166,7 @@ namespace MWMechanics CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude; + float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude(); if (invisibility > 0) return false; @@ -1128,13 +1198,13 @@ namespace MWMechanics Ogre::Vector3 pos2 (observer.getRefData().getPosition().pos); float distTerm = fSneakDistBase + fSneakDistMult * pos1.distance(pos2); - float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude; + float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility; CreatureStats& observerStats = observer.getClass().getCreatureStats(observer); int obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified(); int obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; + float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude(); int obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak); float obsTerm = obsSneak + 0.2 * obsAgility + 0.1 * obsLuck - obsBlind; @@ -1158,11 +1228,11 @@ namespace MWMechanics void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { + 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()) { - ptr.getClass().getCreatureStats(ptr).setHostile(true); - // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) { @@ -1251,6 +1321,34 @@ namespace MWMechanics + ((50 - disposition) * fFightDispMult)) + bias; + if (ptr.getClass().isNpc() && target.getClass().isNpc()) + { + if (target.getClass().getNpcStats(target).isWerewolf() || + (target == MWBase::Environment::get().getWorld()->getPlayerPtr() && + MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) + { + const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().search("iWerewolfFightMod"); + fight += iWerewolfFightMod->getInt(); + } + } + return (fight >= 100); } + + void MechanicsManager::keepPlayerAlive() + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + CreatureStats& stats = player.getClass().getCreatureStats(player); + if (stats.isDead()) + { + MWMechanics::DynamicStat stat (stats.getHealth()); + + if (stat.getModified()<1) + { + stat.setModified(1, 0); + stats.setHealth(stat); + } + stats.resurrect(); + } + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 596e6887ec..dc5479ecd8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -26,6 +26,7 @@ namespace MWMechanics { MWWorld::Ptr mWatched; NpcStats mWatchedStats; + bool mWatchedStatsEmpty; bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; @@ -119,6 +120,8 @@ namespace MWMechanics OffenseType type, int arg=0); virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); + /// @return false if the attack was considered a "friendly hit" and forgiven + virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); /// Utility to check if taking this item is illegal and calling commitCrime if so virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count); /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so @@ -160,6 +163,8 @@ namespace MWMechanics /// @param bias Can be used to add an additional aggression bias towards the target, /// making it more likely for the function to return true. virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false); + + virtual void keepPlayerAlive(); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 579969f9d2..370c47b1f9 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -34,7 +34,6 @@ MWMechanics::NpcStats::NpcStats() , mProfit(0) , mTimeToStartDrowning(20.0) , mLastDrowningHit(0) -, mLevelHealthBonus(0) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -108,7 +107,7 @@ bool MWMechanics::NpcStats::isSameFaction (const NpcStats& npcStats) const } float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& class_, int usageType, - int level) const + int level, float extraFactor) const { if (level<0) level = static_cast (getSkill (skillIndex).getBase()); @@ -132,6 +131,8 @@ float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& cla return 0; } + skillFactor *= extraFactor; + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); @@ -168,7 +169,7 @@ float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& cla return 1.0 / ((level+1) * (1.0/skillFactor) * typeFactor * specialisationFactor); } -void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType) +void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) { // Don't increase skills as a werewolf if(mIsWerewolf) @@ -176,7 +177,7 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, MWMechanics::SkillValue& value = getSkill (skillIndex); - value.setProgress(value.getProgress() + getSkillGain (skillIndex, class_, usageType)); + value.setProgress(value.getProgress() + getSkillGain (skillIndex, class_, usageType, -1, extraFactor)); if (value.getProgress()>=1) { @@ -262,8 +263,7 @@ void MWMechanics::NpcStats::levelUp() // "When you gain a level, in addition to increasing three primary attributes, your Health // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, // the Health increase is calculated from the increased Endurance" - mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); - updateHealth(); + setHealth(getHealth().getBase() + endurance * gmst.find("fLevelUpHealthEndMult")->getFloat()); setLevel(getLevel()+1); } @@ -273,7 +273,7 @@ void MWMechanics::NpcStats::updateHealth() const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); const int strength = getAttribute(ESM::Attribute::Strength).getBase(); - setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); + setHealth(static_cast (0.5 * (strength + endurance))); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const @@ -497,7 +497,6 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mTimeToStartDrowning = mTimeToStartDrowning; state.mLastDrowningHit = mLastDrowningHit; - state.mLevelHealthBonus = mLevelHealthBonus; } void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) @@ -549,5 +548,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mTimeToStartDrowning = state.mTimeToStartDrowning; mLastDrowningHit = state.mLastDrowningHit; - mLevelHealthBonus = state.mLevelHealthBonus; } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index a066760d00..4ea5d45786 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -19,9 +19,6 @@ namespace ESM namespace MWMechanics { /// \brief Additional stats for NPCs - /// - /// \note For technical reasons the spell list and the currently selected spell is also handled by - /// CreatureStats, even though they are actually NPC stats. class NpcStats : public CreatureStats { @@ -52,8 +49,6 @@ namespace MWMechanics /// time since last hit from drowning float mLastDrowningHit; - float mLevelHealthBonus; - public: NpcStats(); @@ -86,12 +81,12 @@ namespace MWMechanics ///< Do *this and \a npcStats share a faction? float getSkillGain (int skillIndex, const ESM::Class& class_, int usageType = -1, - int level = -1) const; + int level = -1, float extraFactor=1.f) const; ///< \param usageType: Usage specific factor, specified in the respective skill record; /// -1: use a factor of 1.0 instead. /// \param level Level to base calculation on; -1: use current level. - void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1); + void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1, float extraFactor=1.f); ///< Increase skill by usage. void increaseSkill (int skillIndex, const ESM::Class& class_, bool preserveProgress); @@ -104,7 +99,7 @@ namespace MWMechanics void updateHealth(); ///< Calculate health based on endurance and strength. - /// Called at character creation and at level up. + /// Called at character creation. void flagAsUsed (const std::string& id); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 62c1756c27..62e23db585 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -283,13 +283,6 @@ namespace MWMechanics return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees(); } - // Used by AiCombat, use Euclidean distance - float PathFinder::getDistToNext(float x, float y, float z) - { - ESM::Pathgrid::Point nextPoint = *mPath.begin(); - return distance(nextPoint, x, y, z); - } - bool PathFinder::checkWaypoint(float x, float y, float z) { if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 603a04f8c5..482808dacd 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -47,8 +47,6 @@ namespace MWMechanics float getZAngleToNext(float x, float y) const; - float getDistToNext(float x, float y, float z); - bool isPathConstructed() const { return mIsPathConstructed; diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 9f2c851cf8..6d6f889edc 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -57,10 +57,13 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair)); itemToRepair.getCellRef().setCharge(charge); + // attempt to re-stack item, in case it was fully repaired + MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair); + // set the OnPCRepair variable on the item's script - std::string script = itemToRepair.getClass().getScript(itemToRepair); + std::string script = stacked->getClass().getScript(itemToRepair); if(script != "") - itemToRepair.getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); + stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1b0c444ab7..373ca7af93 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -72,11 +72,11 @@ namespace MWMechanics return schoolSkillMap[school]; } - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) { CreatureStats& stats = actor.getClass().getCreatureStats(actor); - if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) + if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()) return 0; float y = FLT_MAX; @@ -114,7 +114,7 @@ namespace MWMechanics if (spell->mData.mFlags & ESM::Spell::F_Always) return 100; - int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; + int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); @@ -123,14 +123,17 @@ namespace MWMechanics if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player") castChance = 100; - return std::max(0.f, std::min(100.f, castChance)); + if (!cap) + return std::max(0.f, castChance); + else + return std::max(0.f, std::min(100.f, castChance)); } - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool) + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor, effectiveSchool); + return getSpellSuccessChance(spell, actor, effectiveSchool, cap); } @@ -148,6 +151,41 @@ namespace MWMechanics return school; } + bool spellIncreasesSkill(const ESM::Spell *spell) + { + if (spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always)) + return true; + return false; + } + + bool spellIncreasesSkill(const std::string &spellId) + { + const ESM::Spell* spell = + MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + return spellIncreasesSkill(spell); + } + + float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) + { + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += actorEffects->get(resistanceEffect).getMagnitude(); + if (weaknessEffect != -1) + resistance -= actorEffects->get(weaknessEffect).getMagnitude(); + + if (effectId == ESM::MagicEffect::FireDamage) + resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude(); + if (effectId == ESM::MagicEffect::ShockDamage) + resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude(); + if (effectId == ESM::MagicEffect::FrostDamage) + resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude(); + + return resistance; + } + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { @@ -163,28 +201,24 @@ namespace MWMechanics float resisted = 0; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { + // Effects with no resistance attribute belonging to them can not be resisted + if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) + return 0.f; - short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); - short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); - - float resistance = 0; - if (resistanceEffect != -1) - resistance += magicEffects->get(resistanceEffect).mMagnitude; - if (weaknessEffect != -1) - resistance -= magicEffects->get(weaknessEffect).mMagnitude; - + float resistance = getEffectResistanceAttribute(effectId, magicEffects); float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa + float castChance = 100.f; if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) { - float castChance = getSpellSuccessChance(spell, caster); - if (castChance > 0) - x *= 50 / castChance; + castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance } + if (castChance > 0) + x *= 50 / castChance; float roll = static_cast(std::rand()) / RAND_MAX * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) @@ -218,6 +252,44 @@ namespace MWMechanics return -(resistance-100) / 100.f; } + /// Check if the given affect can be applied to the target. If \a castByPlayer, emits a message box on failure. + bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, bool castByPlayer) + { + switch (effectId) + { + case ESM::MagicEffect::Levitate: + if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return false; + } + break; + case ESM::MagicEffect::Soultrap: + if (!target.getClass().isNpc() // no messagebox for NPCs + && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); + return true; // must still apply to get visual effect and have target regard it as attack + } + break; + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::Mark: + case ESM::MagicEffect::Recall: + if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return false; + } + break; + } + + return true; + } + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) : mCaster(caster) , mTarget(target) @@ -248,8 +320,8 @@ namespace MWMechanics if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? - target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude - : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; + target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() + : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).getMagnitude(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (roll <= x) @@ -275,6 +347,27 @@ namespace MWMechanics bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + bool absorbed = false; + if (spell && caster != target && target.getClass().isActor()) + { + int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + absorbed = (roll < absorb); + if (absorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + } + } + for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) { @@ -285,23 +378,8 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); - if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + if (!checkEffectTarget(effectIt->mEffectID, target, castByPlayer)) continue; - } - - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() && - (effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention || - effectIt->mEffectID == ESM::MagicEffect::DivineIntervention || - effectIt->mEffectID == ESM::MagicEffect::Mark || - effectIt->mEffectID == ESM::MagicEffect::Recall)) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - continue; - } // If player is healing someone, show the target's HP bar if (castByPlayer && target != caster @@ -314,35 +392,17 @@ namespace MWMechanics { anyHarmfulEffect = true; + if (absorbed) // Absorbed, and we know there was a harmful effect (figuring that out is the only reason we are in this loop) + break; + // If player is attempting to cast a harmful spell, show the target's HP bar if (castByPlayer && target != caster) MWBase::Environment::get().getWindowManager()->setEnemy(target); - // Try absorbing if it's a spell - // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. - if (spell && caster != target) - { - int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - bool isAbsorbed = (roll < absorb); - if (isAbsorbed) - { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - target.getClass().getCreatureStats(target).setMagicka(magicka); - magnitudeMult = 0; - } - } - // Try reflecting if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { - int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; + int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] bool isReflected = (roll < reflect); if (isReflected) @@ -364,14 +424,13 @@ namespace MWMechanics // Fully resisted, show message if (target.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else + else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } } } - - if (magnitudeMult > 0) + if (magnitudeMult > 0 && !absorbed) { float random = std::rand() / static_cast(RAND_MAX); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; @@ -460,7 +519,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, mId, mSourceName); if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true); + inflict(caster, target, reflectedEffects, range, true, exploded); if (!appliedLastingEffects.empty()) { @@ -484,7 +543,11 @@ namespace MWMechanics 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.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); target.getCellRef().setLockLevel(magnitude); + } } else if (effectId == ESM::MagicEffect::Open) { @@ -492,12 +555,14 @@ namespace MWMechanics { if (target.getCellRef().getLockLevel() > 0) { - //Door not already unlocked MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); if (!caster.isEmpty() && caster.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); + + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); } - target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); //unlocks the door + target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); @@ -619,9 +684,23 @@ namespace MWMechanics if (item.getCellRef().getEnchantmentCharge() < castCost) { - // TODO: Should there be a sound here? if (mCaster.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + + // Failure sound + int school = 0; + for (std::vector::const_iterator effectIt (enchantment->mEffects.mList.begin()); + effectIt!=enchantment->mEffects.mList.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + school = magicEffect->mData.mSchool; + break; + } + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); return false; } // Reduce charge @@ -724,7 +803,7 @@ namespace MWMechanics } } - if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell) + if (mCaster.getRefData().getHandle() == "player" && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); @@ -823,5 +902,4 @@ namespace MWMechanics return true; } - } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index b526a43530..66e91d055d 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -26,15 +26,25 @@ namespace MWMechanics * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here - * @attention actor has to be an NPC and not a creature! - * @return success chance from 0 to 100 (in percent) + * @param cap cap the result to 100%? + * @note actor can be an NPC or a creature + * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. */ - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); + /// Get whether or not the given spell contributes to skill progress. + bool spellIncreasesSkill(const ESM::Spell* spell); + bool spellIncreasesSkill(const std::string& spellId); + + /// Get the resistance attribute against an effect for a given actor. This will add together + /// ResistX and Weakness to X effects relevant against the given effect. + float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects); + + /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). /// @return >=100 for fully resisted. can also return negative value for damage amplification. /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index dee1a1b05d..681f01f04b 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -44,6 +44,15 @@ namespace MWMechanics } } + if (hasCorprusEffect(spell)) + { + CorprusStats corprus; + corprus.mWorsenings = 0; + corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; + + mCorprusSpells[spellId] = corprus; + } + mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random)); } } @@ -52,6 +61,24 @@ namespace MWMechanics { std::string lower = Misc::StringUtils::lowerCase(spellId); TContainer::iterator iter = mSpells.find (lower); + std::map::iterator corprusIt = mCorprusSpells.find(lower); + + // if it's corprus, remove negative and keep positive effects + if (corprusIt != mCorprusSpells.end()) + { + worsenCorprus(lower); + if (mPermanentSpellEffects.find(lower) != mPermanentSpellEffects.end()) + { + MagicEffects & effects = mPermanentSpellEffects[lower]; + for (MagicEffects::Collection::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->first.mId); + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + effects.remove(effectIt->first); + } + } + mCorprusSpells.erase(corprusIt); + } if (iter!=mSpells.end()) mSpells.erase (iter); @@ -87,6 +114,11 @@ namespace MWMechanics } } + for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + { + effects += it->second; + } + return effects; } @@ -154,7 +186,7 @@ namespace MWMechanics const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - if (spell->mData.mType == ESM::Spell::ST_Blight) + if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell)) mSpells.erase(iter++); else ++iter; @@ -168,7 +200,7 @@ namespace MWMechanics const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - if (Misc::StringUtils::ciEqual(spell->mId, "corprus")) + if (hasCorprusEffect(spell)) mSpells.erase(iter++); else ++iter; @@ -216,6 +248,48 @@ namespace MWMechanics } } + void Spells::worsenCorprus(const std::string &corpSpellId) + { + mCorprusSpells[corpSpellId].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; + mCorprusSpells[corpSpellId].mWorsenings++; + + // update worsened effects + mPermanentSpellEffects[corpSpellId] = MagicEffects(); + const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().find(corpSpellId); + int i=0; + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt, ++i) + { + const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + if ((effectIt->mEffectID != ESM::MagicEffect::Corprus) && (magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) // APPLIED_ONCE + { + float random = 1.f; + if (mSpells[corpSpellId].find(i) != mSpells[corpSpellId].end()) + random = mSpells[corpSpellId].at(i); + + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + magnitude *= std::max(1, mCorprusSpells[corpSpellId].mWorsenings); + mPermanentSpellEffects[corpSpellId].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); + } + } + } + + bool Spells::hasCorprusEffect(const ESM::Spell *spell) + { + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mEffectID == ESM::MagicEffect::Corprus) + { + return true; + } + } + return false; + } + + const std::map &Spells::getCorprusSpells() const + { + return mCorprusSpells; + } + bool Spells::canUsePower(const std::string &power) const { std::map::const_iterator it = mUsedPowers.find(power); @@ -252,6 +326,30 @@ namespace MWMechanics // No need to discard spells here (doesn't really matter if non existent ids are kept) for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) mUsedPowers[it->first] = MWWorld::TimeStamp(it->second); + + for (std::map >::const_iterator it = + state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) + { + const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) + continue; + + mPermanentSpellEffects[it->first] = MagicEffects(); + for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) + { + mPermanentSpellEffects[it->first].add(EffectKey(effectIt->mId, effectIt->mArg), effectIt->mMagnitude); + } + } + + mCorprusSpells.clear(); + for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) + { + if (mSpells.find(it->first) != mSpells.end()) // Discard unavailable corprus spells + { + mCorprusSpells[it->first].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; + mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); + } + } } void Spells::writeState(ESM::SpellState &state) const @@ -261,5 +359,26 @@ namespace MWMechanics for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) state.mUsedPowers[it->first] = it->second.toEsm(); + + for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + { + std::vector effectList; + for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) + { + ESM::SpellState::PermanentSpellEffectInfo info; + info.mId = effectIt->first.mId; + info.mArg = effectIt->first.mArg; + info.mMagnitude = effectIt->second.getModifier(); + + effectList.push_back(info); + } + state.mPermanentSpellEffects[it->first] = effectList; + } + + for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + { + state.mCorprusSpells[it->first].mWorsenings = mCorprusSpells.at(it->first).mWorsenings; + state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); + } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 6997a9d7ab..7caeba6e8c 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -31,21 +31,37 @@ namespace MWMechanics { public: - typedef std::map > TContainer; // ID, typedef TContainer::const_iterator TIterator; + struct CorprusStats + { + static const int sWorseningPeriod = 24; + + int mWorsenings; + MWWorld::TimeStamp mNextWorsening; + }; + private: TContainer mSpells; + // spell-tied effects that will be applied even after removing the spell (currently used to keep positive effects when corprus is removed) + std::map mPermanentSpellEffects; + // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; std::map mUsedPowers; + std::map mCorprusSpells; + public: + void worsenCorprus(const std::string &corpSpellId); + static bool hasCorprusEffect(const ESM::Spell *spell); + const std::map & getCorprusSpells() const; + bool canUsePower (const std::string& power) const; void usePower (const std::string& power); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f1fcce5f7a..bdc74f5e3b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -72,6 +73,7 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) , mNonAccumCtrl(NULL) , mAccumulate(0.0f) , mNullAnimationTimePtr(OGRE_NEW NullAnimationTime) + , mGlowLight(NULL) { for(size_t i = 0;i < sNumGroups;i++) mAnimationTimePtr[i].bind(OGRE_NEW AnimationTime(this)); @@ -79,6 +81,8 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::~Animation() { + setLightEffect(0); + mEffects.clear(); mAnimSources.clear(); @@ -111,6 +115,11 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); + + // Fast forward auto-play particles, which will have been set up as Emitting by the loader. + for (unsigned int i=0; imParticles.size(); ++i) + mObjectRoot->mParticles[i]->fastForward(1, 0.1); + if(mObjectRoot->mSkelBase) { mSkelBase = mObjectRoot->mSkelBase; @@ -469,7 +478,13 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; - // Have to find keys in reverse (see reset method) + + // Pick the last Loop Stop key and the last Loop Start key. + // This is required because of broken text keys in AshVampire.nif. + // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback + // but the animation velocity calculation uses the second one. + // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, + // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); while(keyiter != keys.rend()) { @@ -478,8 +493,18 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node starttime = keyiter->first; break; } - else if(keyiter->second == loopstop || keyiter->second == stop) + ++keyiter; + } + keyiter = keys.rbegin(); + while(keyiter != keys.rend()) + { + if (keyiter->second == stop) stoptime = keyiter->first; + else if (keyiter->second == loopstop) + { + stoptime = keyiter->first; + break; + } ++keyiter; } @@ -498,7 +523,7 @@ float Animation::getVelocity(const std::string &groupname) const { /* Look in reverse; last-inserted source has priority. */ AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); - for(;animsrc != mAnimSources.rend();animsrc++) + for(;animsrc != mAnimSources.rend();++animsrc) { const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys; if(findGroupStart(keys, groupname) != keys.end()) @@ -782,7 +807,25 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co attachArrow(); else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") - MWBase::Environment::get().getWorld()->castSpell(mPtr); + { + // Make sure this key is actually for the RangeType we are casting. The flame atronach has + // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. + // FIXME: This logic should really be in the CharacterController + const std::string& spellid = mPtr.getClass().getCreatureStats(mPtr).getSpells().getSelectedSpell(); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + int range = 0; + if (evt.compare(off, len, "self release") == 0) + range = 0; + else if (evt.compare(off, len, "touch release") == 0) + range = 1; + else if (evt.compare(off, len, "target release") == 0) + range = 2; + if (effectentry.mRange == range) + { + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + } else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) mPtr.getClass().block(mPtr); @@ -790,8 +833,7 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co void Animation::changeGroups(const std::string &groupname, int groups) { - AnimStateMap::iterator stateiter = mStates.begin(); - stateiter = mStates.find(groupname); + AnimStateMap::iterator stateiter = mStates.find(groupname); if(stateiter != mStates.end()) { if(stateiter->second.mGroups != groups) @@ -850,10 +892,13 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo mStates[groupname] = state; NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); - while(textkey != textkeys.end() && textkey->first <= state.mTime) + if (state.mPlaying) { - handleTextKey(state, groupname, textkey, textkeys); - ++textkey; + while(textkey != textkeys.end() && textkey->first <= state.mTime) + { + handleTextKey(state, groupname, textkey, textkeys); + ++textkey; + } } if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) @@ -864,7 +909,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo if(state.mTime >= state.mLoopStopTime) break; - textkey = textkeys.lower_bound(state.mTime); + NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, groupname, textkey, textkeys); @@ -888,6 +933,13 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo } } +void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) +{ + AnimStateMap::iterator state(mStates.find(groupname)); + if(state != mStates.end()) + state->second.mSpeedMult = speedmult; +} + bool Animation::isPlaying(const std::string &groupname) const { AnimStateMap::const_iterator state(mStates.find(groupname)); @@ -1175,12 +1227,13 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -bool Animation::allowSwitchViewMode() const +bool Animation::upperBodyReady() const { for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) { - if(stateiter->second.mPriority > MWMechanics::Priority_Movement + if((stateiter->second.mPriority > MWMechanics::Priority_Movement && stateiter->second.mPriority < MWMechanics::Priority_Torch) + || stateiter->second.mPriority == MWMechanics::Priority_Death) return false; } return true; @@ -1193,13 +1246,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; - // fix texture extension to .dds - if (texture.size() > 4) - { - texture[texture.size()-3] = 'd'; - texture[texture.size()-2] = 'd'; - texture[texture.size()-1] = 's'; - } + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture); EffectParams params; params.mModelName = model; @@ -1257,7 +1304,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName("textures\\" + texture); + tus->setTextureName(correctedTexture); } } } @@ -1287,7 +1334,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName("textures\\" + texture); + tus->setTextureName(correctedTexture); } } } @@ -1382,6 +1429,37 @@ Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) return result; } +void Animation::setLightEffect(float effect) +{ + if (effect == 0) + { + if (mGlowLight) + { + mInsert->getCreator()->destroySceneNode(mGlowLight->getParentSceneNode()); + mInsert->getCreator()->destroyLight(mGlowLight); + mGlowLight = NULL; + } + } + else + { + if (!mGlowLight) + { + mGlowLight = mInsert->getCreator()->createLight(); + + Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; + for(size_t i = 0;i < mObjectRoot->mEntities.size();i++) + { + Ogre::Entity *ent = mObjectRoot->mEntities[i]; + bounds.merge(ent->getBoundingBox()); + } + mInsert->createChildSceneNode(bounds.getCenter())->attachObject(mGlowLight); + } + mGlowLight->setType(Ogre::Light::LT_POINT); + effect += 3; + mGlowLight->setAttenuation(1.0f / (0.03 * (0.5/effect)), 0, 0.5/effect, 0); + } +} + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) @@ -1417,6 +1495,18 @@ void ObjectAnimation::addLight(const ESM::Light *light) addExtraLight(mInsert->getCreator(), mObjectRoot, light); } +void ObjectAnimation::removeParticles() +{ + for (unsigned int i=0; imParticles.size(); ++i) + { + // Don't destroyParticleSystem, the ParticleSystemController is still holding a pointer to it. + // Don't setVisible, this could conflict with a VisController. + // The following will remove all spawned particles, then set the speed factor to zero so that no new ones will be spawned. + mObjectRoot->mParticles[i]->setSpeedFactor(0.f); + mObjectRoot->mParticles[i]->clear(); + } +} + class FindEntityTransparency { public: diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e15bd6ecbe..8ca3582dc1 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -126,6 +126,8 @@ protected: MWWorld::Ptr mPtr; + Ogre::Light* mGlowLight; + Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; NifOgre::ObjectScenePtr mObjectRoot; @@ -258,11 +260,15 @@ public: float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops); + /** Adjust the speed multiplier of an already playing animation. + */ + void adjustSpeedMult (const std::string& groupname, float speedmult); + /** Returns true if the named animation group is playing. */ bool isPlaying(const std::string &groupname) const; - //Checks if playing any animation which shouldn't be stopped when switching camera view modes - bool allowSwitchViewMode() const; + /// Returns true if no important animations are currently playing on the upper body. + bool upperBodyReady() const; /** Gets info about the given animation group. * \param groupname Animation group to check. @@ -301,11 +307,17 @@ public: /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); + // TODO: move outside of this class + /// Makes this object glow, by placing a Light in its center. + /// @param effect Controls the radius and intensity of the light. + void setLightEffect(float effect); + virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show) {} virtual void attachArrow() {} virtual void releaseArrow() {} void enableLights(bool enable); + virtual void enableHeadAnimation(bool enable) {} Ogre::AxisAlignedBox getWorldBounds(); @@ -323,6 +335,7 @@ public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model); void addLight(const ESM::Light *light); + void removeParticles(); bool canBatch() const; void fillBatch(Ogre::StaticGeometry *sg); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 4580bae70b..bb196f2827 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -26,7 +26,6 @@ namespace MWRender mNearest(30.f), mFurthest(800.f), mIsNearest(false), - mIsFurthest(false), mHeight(128.f), mCameraDistance(300.f), mDistanceAdjusted(false), @@ -107,7 +106,7 @@ namespace MWRender void Camera::update(float duration, bool paused) { - if (mAnimation->allowSwitchViewMode()) + if (mAnimation->upperBodyReady()) { // Now process the view changes we queued earlier if (mVanityToggleQueued) @@ -144,7 +143,7 @@ namespace MWRender { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later - if (!mAnimation->allowSwitchViewMode() && !force) + if (!mAnimation->upperBodyReady() && !force) { mViewModeToggleQueued = true; return; @@ -207,7 +206,7 @@ namespace MWRender void Camera::togglePreviewMode(bool enable) { - if (mFirstPersonView && !mAnimation->allowSwitchViewMode()) + if (mFirstPersonView && !mAnimation->upperBodyReady()) return; if(mPreviewMode == enable) @@ -292,7 +291,6 @@ namespace MWRender if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) return; - mIsFurthest = false; mIsNearest = false; Ogre::Vector3 v(0.f, 0.f, dist); @@ -301,7 +299,6 @@ namespace MWRender } if (v.z >= mFurthest) { v.z = mFurthest; - mIsFurthest = true; } else if (!override && v.z < 10.f) { v.z = 10.f; } else if (override && v.z <= mNearest) { @@ -389,9 +386,4 @@ namespace MWRender { return mIsNearest; } - - bool Camera::isFurthest() - { - return mIsFurthest; - } } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 1e86bfb481..f7b0ae6e82 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -36,7 +36,6 @@ namespace MWRender float mNearest; float mFurthest; bool mIsNearest; - bool mIsFurthest; struct { bool enabled, allowed; @@ -118,8 +117,6 @@ namespace MWRender bool isVanityOrPreviewModeEnabled(); bool isNearest(); - - bool isFurthest(); }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 5e88b22505..1630e4005f 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -37,6 +37,7 @@ namespace MWRender , mViewport(NULL) , mCamera(NULL) , mNode(NULL) + , mRecover(false) { mCharacter.mCell = NULL; } @@ -46,6 +47,16 @@ namespace MWRender } + void CharacterPreview::onFrame() + { + if (mRecover) + { + setupRenderTarget(); + mRenderTarget->update(); + mRecover = false; + } + } + void CharacterPreview::setup () { mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); @@ -83,19 +94,10 @@ namespace MWRender mCamera->setNearClipDistance (0.01); mCamera->setFarClipDistance (1000); - mTexture = Ogre::TextureManager::getSingleton().getByName (mName); - if (mTexture.isNull ()) - mTexture = Ogre::TextureManager::getSingleton().createManual(mName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET); + mTexture = Ogre::TextureManager::getSingleton().createManual(mName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET, this); - mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - mRenderTarget->removeAllViewports (); - mViewport = mRenderTarget->addViewport(mCamera); - mViewport->setOverlaysEnabled(false); - mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); - mViewport->setShadowsEnabled(false); - mRenderTarget->setActive(true); - mRenderTarget->setAutoUpdated (false); + setupRenderTarget(); onSetup (); } @@ -107,6 +109,7 @@ namespace MWRender mSceneMgr->destroyAllCameras(); delete mAnimation; Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); + Ogre::TextureManager::getSingleton().remove(mName); } } @@ -127,12 +130,39 @@ namespace MWRender onSetup(); } + void CharacterPreview::loadResource(Ogre::Resource *resource) + { + Ogre::Texture* tex = dynamic_cast(resource); + if (!tex) + return; + + tex->createInternalResources(); + + mRenderTarget = NULL; + mViewport = NULL; + mRecover = true; + } + + void CharacterPreview::setupRenderTarget() + { + mRenderTarget = mTexture->getBuffer()->getRenderTarget(); + mRenderTarget->removeAllViewports (); + mViewport = mRenderTarget->addViewport(mCamera); + mViewport->setOverlaysEnabled(false); + mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); + mViewport->setShadowsEnabled(false); + mRenderTarget->setActive(true); + mRenderTarget->setAutoUpdated (false); + } + // -------------------------------------------------------------------------------------------------- InventoryPreview::InventoryPreview(MWWorld::Ptr character) : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) , mSelectionBuffer(NULL) + , mSizeX(0) + , mSizeY(0) { } @@ -141,7 +171,21 @@ namespace MWRender delete mSelectionBuffer; } - void InventoryPreview::update(int sizeX, int sizeY) + void InventoryPreview::resize(int sizeX, int sizeY) + { + mSizeX = sizeX; + mSizeY = sizeY; + + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); + + mRenderTarget->update(); + } + + void InventoryPreview::update() { mAnimation->updateParts(); @@ -197,15 +241,25 @@ namespace MWRender mAnimation->runAnimation(0.0f); - mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); - mNode->setOrientation (Ogre::Quaternion::IDENTITY); + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); + mRenderTarget->update(); mSelectionBuffer->update(); } + void InventoryPreview::setupRenderTarget() + { + CharacterPreview::setupRenderTarget(); + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + } + int InventoryPreview::getSlotSelected (int posX, int posY) { return mSelectionBuffer->getSelected (posX, posY); @@ -227,9 +281,9 @@ namespace MWRender RaceSelectionPreview::RaceSelectionPreview() : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayerPtr(), 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 6, -35), Ogre::Vector3(0,125,0)) + , mBase (*mCharacter.get()->mBase) , mRef(&mBase) { - mBase = *mCharacter.get()->mBase; mCharacter = MWWorld::Ptr(&mRef, NULL); } @@ -243,6 +297,10 @@ namespace MWRender void RaceSelectionPreview::render() { + mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); mRenderTarget->update(); } diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 60312455ff..711de0d154 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -22,7 +22,7 @@ namespace MWRender class NpcAnimation; - class CharacterPreview + class CharacterPreview : public Ogre::ManualResourceLoader { public: CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name, @@ -34,6 +34,13 @@ namespace MWRender virtual void rebuild(); + void onFrame(); + + void loadResource(Ogre::Resource *resource); + + private: + bool mRecover; // Texture content was lost and needs to be re-rendered + private: CharacterPreview(const CharacterPreview&); CharacterPreview& operator=(const CharacterPreview&); @@ -41,6 +48,8 @@ namespace MWRender protected: virtual bool renderHeadOnly() { return false; } + virtual void setupRenderTarget(); + Ogre::TexturePtr mTexture; Ogre::RenderTarget* mRenderTarget; Ogre::Viewport* mViewport; @@ -72,11 +81,17 @@ namespace MWRender virtual ~InventoryPreview(); virtual void onSetup(); - void update(int sizeX, int sizeY); + void update(); // Render preview again, e.g. after changed equipment + void resize(int sizeX, int sizeY); int getSlotSelected(int posX, int posY); + protected: + virtual void setupRenderTarget(); + private: + int mSizeX; + int mSizeY; OEngine::Render::SelectionBuffer* mSelectionBuffer; }; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f2447cb702..247a0ba146 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -27,9 +27,9 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr) setObjectRoot(model, false); setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + addAnimSource(model); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\base_anim.nif"); - addAnimSource(model); } } @@ -47,9 +47,9 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) setObjectRoot(model, false); setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + addAnimSource(model); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\base_anim.nif"); - addAnimSource(model); mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); @@ -150,7 +150,7 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo } std::vector >::iterator ctrl(scene->mControllers.begin()); - for(;ctrl != scene->mControllers.end();ctrl++) + for(;ctrl != scene->mControllers.end();++ctrl) { if(ctrl->getSource().isNull()) { diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 968be0f9e9..a48dea8d57 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -1,5 +1,7 @@ #include "effectmanager.hpp" +#include + #include #include #include @@ -21,15 +23,6 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition); sceneNode->setScale(scale,scale,scale); - // fix texture extension to .dds - if (textureOverride.size() > 4) - { - textureOverride[textureOverride.size()-3] = 'd'; - textureOverride[textureOverride.size()-2] = 'd'; - textureOverride[textureOverride.size()-1] = 's'; - } - - NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model); // TODO: turn off shadow casting @@ -44,6 +37,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr if (!textureOverride.empty()) { + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(textureOverride); for(size_t i = 0;i < scene->mParticles.size(); ++i) { Ogre::ParticleSystem* partSys = scene->mParticles[i]; @@ -59,7 +53,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName("textures\\" + textureOverride); + tus->setTextureName(correctedTexture); } } } diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 1ccfd9527c..fd8b919367 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -29,8 +29,13 @@ namespace MWRender , mWidth(0) , mHeight(0) { + mCellSize = Settings::Manager::getInt("global map cell size", "Map"); } + GlobalMap::~GlobalMap() + { + Ogre::TextureManager::getSingleton().remove(mOverlayTexture->getName()); + } void GlobalMap::render (Loading::Listener* loadingListener) { @@ -53,9 +58,8 @@ namespace MWRender mMaxY = it->getGridY(); } - int cellSize = 24; - mWidth = cellSize*(mMaxX-mMinX+1); - mHeight = cellSize*(mMaxY-mMinY+1); + mWidth = mCellSize*(mMaxX-mMinX+1); + mHeight = mCellSize*(mMaxY-mMinY+1); loadingListener->loadingOn(); loadingListener->setLabel("Creating map"); @@ -86,29 +90,29 @@ namespace MWRender land->loadData(mask); } - for (int cellY=0; cellYmLandData->mHeights[vertexY * ESM::Land::LAND_SIZE + vertexX]; - const float mountainHeight = 15000.f; - const float hillHeight = 2500.f; if (landHeight >= 0) { + const float hillHeight = 2500.f; if (landHeight >= hillHeight) { + const float mountainHeight = 15000.f; float factor = std::min(1.f, float(landHeight-hillHeight)/mountainHeight); r = (hillColour.r * (1-factor) + mountainColour.r * factor) * 255; g = (hillColour.g * (1-factor) + mountainColour.g * factor) * 255; @@ -168,9 +172,8 @@ namespace MWRender tex->load(); - mOverlayTexture = Ogre::TextureManager::getSingleton().createManual("GlobalMapOverlay", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC_WRITE_ONLY); + Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC, this); clear(); @@ -194,9 +197,9 @@ namespace MWRender void GlobalMap::exploreCell(int cellX, int cellY) { - float originX = (cellX - mMinX) * 24; + float originX = (cellX - mMinX) * mCellSize; // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is - float originY = mHeight - (cellY+1 - mMinY) * 24; + float originY = mHeight - (cellY+1 - mMinY) * mCellSize; if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; @@ -204,31 +207,47 @@ namespace MWRender Ogre::TexturePtr localMapTexture = Ogre::TextureManager::getSingleton().getByName("Cell_" + boost::lexical_cast(cellX) + "_" + boost::lexical_cast(cellY)); - // mipmap version - can't get ogre to generate automips.. - /*if (!localMapTexture.isNull()) - { - assert(localMapTexture->getBuffer(0, 4)->getWidth() == 64); // 1024 / 2^4 - - mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(0, 4), Ogre::Image::Box(0,0,64, 64), - Ogre::Image::Box(originX,originY,originX+24,originY+24)); - }*/ - if (!localMapTexture.isNull()) { + mOverlayTexture->load(); mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512), - Ogre::Image::Box(originX,originY,originX+24,originY+24)); + Ogre::Image::Box(originX,originY,originX+mCellSize,originY+mCellSize)); + + Ogre::Image backup; + std::vector data; + data.resize(mCellSize*mCellSize*4, 0); + backup.loadDynamicImage(&data[0], mCellSize, mCellSize, Ogre::PF_A8B8G8R8); + + localMapTexture->getBuffer()->blitToMemory(Ogre::Image::Box(0,0,512,512), backup.getPixelBox()); + + for (int x=0; x buffer; - // initialize to (0,0,0,0) - buffer.resize(mWidth * mHeight, 0); + Ogre::uchar* buffer = OGRE_ALLOC_T(Ogre::uchar, mWidth * mHeight * 4, Ogre::MEMCATEGORY_GENERAL); + memset(buffer, 0, mWidth * mHeight * 4); - Ogre::PixelBox pb(mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, &buffer[0]); + mOverlayImage.loadDynamicImage(&buffer[0], mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, true); // pass ownership of buffer to image - mOverlayTexture->getBuffer()->blitFromMemory(pb); + mOverlayTexture->load(); + } + + void GlobalMap::loadResource(Ogre::Resource *resource) + { + Ogre::Texture* tex = dynamic_cast(resource); + Ogre::ConstImagePtrList list; + list.push_back(&mOverlayImage); + tex->_loadImages(list); } void GlobalMap::write(ESM::GlobalMap& map) @@ -238,9 +257,7 @@ namespace MWRender map.mBounds.mMinY = mMinY; map.mBounds.mMaxY = mMaxY; - Ogre::Image image; - mOverlayTexture->convertToImage(image); - Ogre::DataStreamPtr encoded = image.encode("png"); + Ogre::DataStreamPtr encoded = mOverlayImage.encode("png"); map.mImageData.resize(encoded->size()); encoded->read(&map.mImageData[0], encoded->size()); } @@ -273,7 +290,7 @@ namespace MWRender // If cell bounds of the currently loaded content and the loaded savegame do not match, // we need to resize source/dest boxes to accommodate // This means nonexisting cells will be dropped silently - int cellImageSizeDst = 24; + int cellImageSizeDst = mCellSize; // Completely off-screen? -> no need to blit anything if (bounds.mMaxX < mMinX @@ -303,8 +320,16 @@ namespace MWRender image.getHeight(), 0, Ogre::PF_A8B8G8R8); tex->loadImage(image); + mOverlayTexture->load(); mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); + if (srcBox.left == destBox.left && srcBox.right == destBox.right + && srcBox.top == destBox.top && srcBox.bottom == destBox.bottom + && int(image.getWidth()) == mWidth && int(image.getHeight()) == mHeight) + mOverlayImage = image; + else + mOverlayTexture->convertToImage(mOverlayImage); + Ogre::TextureManager::getSingleton().remove("@temp"); } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 6075d042e9..b3ae85b115 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -18,24 +18,27 @@ namespace ESM namespace MWRender { - class GlobalMap + class GlobalMap : public Ogre::ManualResourceLoader { public: GlobalMap(const std::string& cacheDir); + ~GlobalMap(); void render(Loading::Listener* loadingListener); - int getWidth() { return mWidth; } - int getHeight() { return mHeight; } + int getWidth() const { return mWidth; } + int getHeight() const { return mHeight; } + + int getCellSize() const { return mCellSize; } void worldPosToImageSpace(float x, float z, float& imageX, float& imageY); - ///< @param x x ogre coords - /// @param z z ogre coords void cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY); void exploreCell (int cellX, int cellY); + virtual void loadResource(Ogre::Resource* resource); + /// Clears the overlay void clear(); @@ -45,9 +48,12 @@ namespace MWRender private: std::string mCacheDir; + int mCellSize; + std::vector< std::pair > mExploredCells; Ogre::TexturePtr mOverlayTexture; + Ogre::Image mOverlayImage; // Backup in system memory int mWidth; int mHeight; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 62907fcc35..85f73ab7ef 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -98,6 +98,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) return; Ogre::Image image; + tex->load(); tex->convertToImage(image); Ogre::DataStreamPtr encoded = image.encode("tga"); @@ -137,6 +138,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) return; Ogre::Image image; + tex->load(); tex->convertToImage(image); fog->mFogTextures.push_back(ESM::FogTexture()); @@ -322,6 +324,7 @@ void LocalMap::createFogOfWar(const std::string& texturePrefix) buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); // upload to the texture + tex->load(); memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); @@ -340,7 +343,9 @@ Ogre::TexturePtr LocalMap::createFogOfWarTexture(const std::string &texName) sFogOfWarResolution, sFogOfWarResolution, 0, PF_A8R8G8B8, - TU_DYNAMIC_WRITE_ONLY); + TU_DYNAMIC_WRITE_ONLY, + this // ManualResourceLoader required if the texture contents are lost (due to lost devices nonsense that can occur with D3D) + ); } return tex; @@ -426,7 +431,7 @@ void LocalMap::render(const float x, const float y, mRendering->getScene()->setAmbientLight(oldAmbient); } -void LocalMap::getInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y) +void LocalMap::worldToInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y) { pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().y), mAngle); @@ -439,6 +444,18 @@ void LocalMap::getInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, nY = 1.0-(pos.y - min.y - sSize*y)/sSize; } +Ogre::Vector2 LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int y) +{ + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); + Ogre::Vector2 pos; + + pos.x = sSize * (nX + x) + min.x; + pos.y = sSize * (1.0-nY + y) + min.y; + + pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().y), -mAngle); + return pos; +} + bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interior) { std::string texName = (interior ? mInteriorName + "_" : "Cell_") + coordStr(x, y); @@ -457,6 +474,30 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interi return alpha < 200; } +void LocalMap::loadResource(Ogre::Resource* resource) +{ + std::string resourceName = resource->getName(); + size_t pos = resourceName.find("_fog"); + if (pos != std::string::npos) + resourceName = resourceName.substr(0, pos); + if (mBuffers.find(resourceName) == mBuffers.end()) + { + // create a buffer to use for dynamic operations + std::vector buffer; + + // initialize to (0, 0, 0, 1) + buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); + mBuffers[resourceName] = buffer; + } + + std::vector& buffer = mBuffers[resourceName]; + + Ogre::Texture* tex = dynamic_cast(resource); + tex->createInternalResources(); + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); + tex->getBuffer()->unlock(); +} + void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation) { if (sFogOfWarSkip != 0) @@ -473,7 +514,7 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni Vector2 pos(position.x, position.y); if (mInterior) - getInteriorMapPosition(pos, u,v, x,y); + worldToInteriorMapPosition(pos, u,v, x,y); Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis(); @@ -555,6 +596,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni } } + tex->load(); + // copy to the texture // NOTE: Could be optimized later. We actually only need to update the region that changed. // Not a big deal at the moment, the FoW is only 32x32 anyway. diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index babf7224ed..b531c3e294 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace MWWorld { @@ -23,12 +24,14 @@ namespace MWRender /// /// \brief Local map rendering /// - class LocalMap + class LocalMap : public Ogre::ManualResourceLoader { public: LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering); ~LocalMap(); + virtual void loadResource(Ogre::Resource* resource); + /** * Clear all savegame-specific data (i.e. fog of war textures) */ @@ -58,8 +61,6 @@ namespace MWRender * Set the position & direction of the player. * @remarks This is used to draw a "fog of war" effect * to hide areas on the map the player has not discovered yet. - * @param position (OGRE coordinates) - * @param camera orientation (OGRE coordinates) */ void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation); @@ -71,9 +72,11 @@ namespace MWRender /** * Get the interior map texture index and normalized position - * on this texture, given a world position (in ogre coordinates) + * on this texture, given a world position */ - void getInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y); + void worldToInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y); + + Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y); /** * Check if a given position is explored by the player (i.e. not obscured by fog of war) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index be2b262fc2..9e93bd3149 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -66,15 +66,74 @@ std::string getVampireHead(const std::string& race, bool female) namespace MWRender { +HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) + : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0), mEnabled(true) +{ + resetBlinkTimer(); +} + +void HeadAnimationTime::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + +void HeadAnimationTime::resetBlinkTimer() +{ + mBlinkTimer = -(2 + (std::rand() / double(RAND_MAX*1.0)) * 6); +} + +void HeadAnimationTime::update(float dt) +{ + if (!mEnabled) + return; + + if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) + { + mBlinkTimer += dt; + + float duration = mBlinkStop - mBlinkStart; + + if (mBlinkTimer >= 0 && mBlinkTimer <= duration) + { + mValue = mBlinkStart + mBlinkTimer; + } + else + mValue = mBlinkStop; + + if (mBlinkTimer > duration) + resetBlinkTimer(); + } + else + { + mValue = mTalkStart + + (mTalkStop - mTalkStart) * + std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*2); // Rescale a bit (most voices are not very loud) + } +} + float HeadAnimationTime::getValue() const { - // TODO use time from text keys (Talk Start/Stop, Blink Start/Stop) - // TODO: Handle eye blinking - if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) - return 0; - else - // TODO: Use the loudness of the currently playing sound - return 1; + return mValue; +} + +void HeadAnimationTime::setTalkStart(float value) +{ + mTalkStart = value; +} + +void HeadAnimationTime::setTalkStop(float value) +{ + mTalkStop = value; +} + +void HeadAnimationTime::setBlinkStart(float value) +{ + mBlinkStart = value; +} + +void HeadAnimationTime::setBlinkStop(float value) +{ + mBlinkStop = value; } static NpcAnimation::PartBoneMap createPartListMap() @@ -241,7 +300,7 @@ void NpcAnimation::updateParts() MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); NpcType curType = Type_Normal; - if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude > 0) + if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) curType = Type_Vampire; if (cls.getNpcStats(mPtr).isWerewolf()) curType = Type_Werewolf; @@ -351,15 +410,18 @@ void NpcAnimation::updateParts() // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; - static const int Flag_Female = 1<<0; - static const int Flag_FirstPerson = 1<<1; - bool isWerewolf = (mNpcType == Type_Werewolf); int flags = (isWerewolf ? -1 : 0); if(!mNpc->isMale()) + { + static const int Flag_Female = 1<<0; flags |= Flag_Female; + } if(mViewMode == VM_FirstPerson) + { + static const int Flag_FirstPerson = 1<<1; flags |= Flag_FirstPerson; + } std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); std::pair thisCombination = std::make_pair(race, flags); @@ -493,6 +555,10 @@ NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); + // Fast forward auto-play particles, which will have been set up as Emitting by the loader. + for (unsigned int i=0; imParticles.size(); ++i) + objects->mParticles[i]->fastForward(1, 0.1); + if(objects->mSkelBase) { Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); @@ -516,6 +582,8 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { Ogre::Vector3 ret = Animation::runAnimation(timepassed); + mHeadAnimationTime->update(timepassed); + Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); if(mViewMode == VM_FirstPerson) { @@ -539,7 +607,7 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) if (mObjectParts[i].isNull()) continue; std::vector >::iterator ctrl(mObjectParts[i]->mControllers.begin()); - for(;ctrl != mObjectParts[i]->mControllers.end();ctrl++) + for(;ctrl != mObjectParts[i]->mControllers.end();++ctrl) ctrl->update(); Ogre::Entity *ent = mObjectParts[i]->mSkelBase; @@ -613,14 +681,28 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); - for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++) + for(;ctrl != mObjectParts[type]->mControllers.end();++ctrl) { if(ctrl->getSource().isNull()) { ctrl->setSource(mNullAnimationTimePtr); if (type == ESM::PRT_Head) + { ctrl->setSource(mHeadAnimationTime); + const NifOgre::TextKeyMap& keys = mObjectParts[type]->mTextKeys; + for (NifOgre::TextKeyMap::const_iterator it = keys.begin(); it != keys.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->second, "talk: start")) + mHeadAnimationTime->setTalkStart(it->first); + if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) + mHeadAnimationTime->setTalkStop(it->first); + if (Misc::StringUtils::ciEqual(it->second, "blink: start")) + mHeadAnimationTime->setBlinkStart(it->first); + if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) + mHeadAnimationTime->setBlinkStop(it->first); + } + } else if (type == ESM::PRT_Weapon) ctrl->setSource(mWeaponAnimationTime); } @@ -636,7 +718,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector::const_iterator part(parts.begin()); - for(;part != parts.end();part++) + for(;part != parts.end();++part) { const ESM::BodyPart *bodypart = 0; if(!mNpc->isMale() && !part->mFemale.empty()) @@ -797,6 +879,11 @@ void NpcAnimation::setAlpha(float alpha) } } +void NpcAnimation::enableHeadAnimation(bool enable) +{ + mHeadAnimationTime->setEnabled(enable); +} + void NpcAnimation::preRender(Ogre::Camera *camera) { Animation::preRender(camera); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 8ec46facda..979210591c 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -19,8 +19,29 @@ class HeadAnimationTime : public Ogre::ControllerValue { private: MWWorld::Ptr mReference; + float mTalkStart; + float mTalkStop; + float mBlinkStart; + float mBlinkStop; + + float mBlinkTimer; + + bool mEnabled; + + float mValue; +private: + void resetBlinkTimer(); public: - HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference) {} + HeadAnimationTime(MWWorld::Ptr reference); + + void update(float dt); + + void setEnabled(bool enabled); + + void setTalkStart(float value); + void setTalkStop(float value); + void setBlinkStart(float value); + void setBlinkStop(float value); virtual Ogre::Real getValue() const; virtual void setValue(Ogre::Real value) @@ -108,6 +129,8 @@ public: ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); + virtual void enableHeadAnimation(bool enable); + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } virtual Ogre::Vector3 runAnimation(float timepassed); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index d9e20e1f8e..96decbb360 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,14 +73,19 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch, bool addLight) { insertBegin(ptr); std::auto_ptr anim(new ObjectAnimation(ptr, mesh)); if(ptr.getTypeName() == typeid(ESM::Light).name()) - anim->addLight(ptr.get()->mBase); + { + if (addLight) + anim->addLight(ptr.get()->mBase); + else + anim->removeParticles(); + } if (!mesh.empty()) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 02e974e2d8..7f740dbab0 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,7 +41,7 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false, bool addLight=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 157265c964..7b597f0c80 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -24,6 +24,7 @@ #include #include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -46,7 +47,6 @@ #include "globalmap.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" -#include "terraingrid.hpp" using namespace MWRender; using namespace Ogre; @@ -114,13 +114,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mFactory->loadAllFiles(); - // Compressed textures with 0 mip maps are bugged in 1.8, so disable mipmap generator in that case - // ( https://ogre3d.atlassian.net/browse/OGRE-259 ) -#if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); -#else - TextureManager::getSingleton().setDefaultNumMipmaps(0); -#endif // Set default texture filtering options TextureFilterOptions tfo; @@ -213,11 +207,6 @@ MWRender::Actors& RenderingManager::getActors(){ return *mActors; } -OEngine::Render::Fader* RenderingManager::getFader() -{ - return mRendering.getFader(); -} - MWRender::Camera* RenderingManager::getCamera() const { return mCamera; @@ -345,8 +334,8 @@ void RenderingManager::update (float duration, bool paused) MWWorld::Ptr player = world->getPlayerPtr(); - int blind = player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; - mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f))); + int blind = player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude(); + MWBase::Environment::get().getWindowManager()->setScreenFactor(std::max(0.f, 1.f-(blind / 100.f))); setAmbientMode(); // player position @@ -586,24 +575,6 @@ void RenderingManager::configureAmbient(MWWorld::CellStore &mCell) sunEnable(false); } } -// Switch through lighting modes. - -void RenderingManager::toggleLight() -{ - if (mAmbientMode==2) - mAmbientMode = 0; - else - ++mAmbientMode; - - switch (mAmbientMode) - { - case 0: std::cout << "Setting lights to normal\n"; break; - case 1: std::cout << "Turning the lights up\n"; break; - case 2: std::cout << "Turning the lights to full\n"; break; - } - - setAmbientMode(); -} void RenderingManager::setSunColour(const Ogre::ColourValue& colour) { @@ -617,7 +588,7 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) mAmbientColor = colour; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - int nightEye = player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).mMagnitude; + int nightEye = player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude(); Ogre::ColourValue final = colour; final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f)); @@ -707,11 +678,6 @@ void RenderingManager::enableLights(bool sun) sunEnable(sun); } -Shadows* RenderingManager::getShadows() -{ - return mShadows; -} - void RenderingManager::notifyWorldSpaceChanged() { mEffectManager->clear(); @@ -778,13 +744,6 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec || it->second == "resolution y" || it->second == "fullscreen")) changeRes = true; - else if (it->first == "Video" && it->second == "vsync") - { - // setVSyncEnabled is bugged in 1.8 -#if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) - mRendering.getWindow()->setVSyncEnabled(Settings::Manager::getBool("vsync", "Video")); -#endif - } else if (it->second == "field of view" && it->first == "General") mRendering.setFov(Settings::Manager::getFloat("field of view", "General")); else if ((it->second == "texture filtering" && it->first == "General") @@ -876,10 +835,9 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec void RenderingManager::setMenuTransparency(float val) { - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName("transparent.png"); - std::vector buffer; + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName("transparent.png"); std::vector buffer; buffer.resize(1); - buffer[0] = (int(255*val) << 24); + buffer[0] = (int(255*val) << 24) | (255 << 16) | (255 << 8) | 255; memcpy(tex->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], 1*4); tex->getBuffer()->unlock(); } @@ -951,9 +909,14 @@ void RenderingManager::setCameraDistance(float dist, bool adjust, bool override) } } -void RenderingManager::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) +void RenderingManager::worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) { - return mLocalMap->getInteriorMapPosition (position, nX, nY, x, y); + return mLocalMap->worldToInteriorMapPosition (position, nX, nY, x, y); +} + +Ogre::Vector2 RenderingManager::interiorMapToWorldPosition(float nX, float nY, int x, int y) +{ + return mLocalMap->interiorMapToWorldPosition(nX, nY, x, y); } bool RenderingManager::isPositionExplored (float nX, float nY, int x, int y, bool interior) @@ -1051,7 +1014,7 @@ void RenderingManager::enableTerrain(bool enable) mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); else - mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ea7905cf5a..43d22d5ca9 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -4,8 +4,6 @@ #include "sky.hpp" #include "debugging.hpp" -#include - #include #include @@ -95,11 +93,8 @@ public: MWRender::Camera* getCamera() const; - void toggleLight(); bool toggleRenderMode(int mode); - OEngine::Render::Fader* getFader(); - void removeCell (MWWorld::CellStore *store); /// \todo this function should be removed later. Instead the rendering subsystems should track @@ -161,8 +156,6 @@ public: float getTerrainHeightAt (Ogre::Vector3 worldPos); - Shadows* getShadows(); - void notifyWorldSpaceChanged(); void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches); @@ -202,8 +195,11 @@ public: Ogre::Viewport* getViewport() { return mRendering.getViewport(); } - void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); - ///< see MWRender::LocalMap::getInteriorMapPosition + void worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); + ///< see MWRender::LocalMap::worldToInteriorMapPosition + + Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y); + ///< see MWRender::LocalMap::interiorMapToWorldPosition bool isPositionExplored (float nX, float nY, int x, int y, bool interior); ///< see MWRender::LocalMap::isPositionExplored diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 7e7eebc1cf..e203212cf0 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -41,6 +41,9 @@ public: void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); private: + RippleSimulation(const RippleSimulation&); + RippleSimulation& operator=(const RippleSimulation&); + std::vector mEmitters; Ogre::RenderTexture* mRenderTargets[4]; diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 33e3376498..596c6697a9 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -186,13 +186,3 @@ PSSMShadowCameraSetup* Shadows::getPSSMSetup() { return mPSSMSetup; } - -float Shadows::getShadowFar() const -{ - return mShadowFar; -} - -float Shadows::getFadeStart() const -{ - return mFadeStart; -} diff --git a/apps/openmw/mwrender/shadows.hpp b/apps/openmw/mwrender/shadows.hpp index bc2b141f70..fe125f54c3 100644 --- a/apps/openmw/mwrender/shadows.hpp +++ b/apps/openmw/mwrender/shadows.hpp @@ -23,8 +23,6 @@ namespace MWRender void recreate(); Ogre::PSSMShadowCameraSetup* getPSSMSetup(); - float getShadowFar() const; - float getFadeStart() const; protected: OEngine::Render::OgreRenderer* mRendering; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8354cca5d7..4620173b51 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -19,6 +19,7 @@ #include #include +#include #include @@ -187,11 +188,6 @@ void Moon::setPhase(const Moon::Phase& phase) mPhase = phase; } -Moon::Phase Moon::getPhase() const -{ - return mPhase; -} - unsigned int Moon::getPhaseInt() const { if (mPhase == Moon::Phase_New) return 0; @@ -294,7 +290,12 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + NifOgre::ObjectScenePtr objects; + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("meshes\\sky_night_02.nif")) + objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_02.nif"); + else + objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++) { Entity* night1_ent = objects->mEntities[i]; @@ -412,7 +413,6 @@ void SkyManager::updateRain(float dt) // Spawn new rain float rainFrequency = mRainFrequency; - float startHeight = 700; if (mRainEnabled) { mRainTimer += dt; @@ -428,6 +428,7 @@ void SkyManager::updateRain(float dt) // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still // consider the orientation of the parent node for its position, just not for its orientation + float startHeight = 700; Ogre::SceneNode* offsetNode = sceneNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(offsetNode, mRainEffect); @@ -584,13 +585,13 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) if (mClouds != weather.mCloudTexture) { - sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "textures\\"+weather.mCloudTexture); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", Misc::ResourceHelpers::correctTexturePath(weather.mCloudTexture)); mClouds = weather.mCloudTexture; } if (mNextClouds != weather.mNextCloudTexture) { - sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", "textures\\"+weather.mNextCloudTexture); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", Misc::ResourceHelpers::correctTexturePath(weather.mNextCloudTexture)); mNextClouds = weather.mNextCloudTexture; } @@ -746,16 +747,6 @@ void SkyManager::setLightningStrength(const float factor) else mLightning->setVisible(false); } -void SkyManager::setLightningEnabled(bool enabled) -{ - /// \todo -} - -void SkyManager::setLightningDirection(const Ogre::Vector3& dir) -{ - if (!mCreated) return; - mLightning->setDirection (dir); -} void SkyManager::setMasserFade(const float fade) { diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 7c31150f3d..e40eb1dcec 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -103,7 +103,6 @@ namespace MWRender void setPhase(const Phase& phase); void setType(const Type& type); - Phase getPhase() const; unsigned int getPhaseInt() const; private: @@ -169,8 +168,6 @@ namespace MWRender void secundaDisable(); void setLightningStrength(const float factor); - void setLightningDirection(const Ogre::Vector3& dir); - void setLightningEnabled(bool enabled); ///< disable prior to map render void setGlare(const float glare); void setGlareEnabled(bool enabled); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 2558c95c59..cbd9e24443 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,21 +1,11 @@ #include "terrainstorage.hpp" -#include -#include -#include -#include -#include -#include -#include - #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" -#include - namespace MWRender { @@ -59,515 +49,4 @@ namespace MWRender return esmStore.get().find(index, plugin); } - bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) - { - assert (size <= 1 && "TerrainStorage::getMinMaxHeights, chunk size should be <= 1 cell"); - - /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int cellX = origin.x; - int cellY = origin.y; - - const ESM::Land* land = getLand(cellX, cellY); - if (!land) - return false; - - min = std::numeric_limits().max(); - max = -std::numeric_limits().max(); - for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; - } - } - return true; - } - - void TerrainStorage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mHasData) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - } - - void TerrainStorage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) - { - Ogre::Vector3 n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row); - fixNormal(n2, cellX, cellY, col-1, row); - fixNormal(n3, cellX, cellY, col, row+1); - fixNormal(n4, cellX, cellY, col, row-1); - normal = (n1+n2+n3+n4); - normal.normalise(); - } - - void TerrainStorage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mLandData->mUsingColours) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - } - - void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours) - { - // LOD level n means every 2^n-th vertex is kept - size_t increment = 1 << lodLevel; - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int startX = origin.x; - int startY = origin.y; - - size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; - - colours.resize(numVerts*numVerts*4); - positions.resize(numVerts*numVerts*3); - normals.resize(numVerts*numVerts*3); - - Ogre::Vector3 normal; - Ogre::ColourValue color; - - float vertY; - float vertX; - - float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) - { - float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) - { - ESM::Land* land = getLand(cellX, cellY); - if (land && !land->mHasData) - land = NULL; - bool hasColors = land && land->mLandData->mUsingColours; - - int rowStart = 0; - int colStart = 0; - // Skip the first row / column unless we're at a chunk edge, - // since this row / column is already contained in a previous cell - if (colStart == 0 && vertY_ != 0) - colStart += increment; - if (rowStart == 0 && vertX_ != 0) - rowStart += increment; - - vertY = vertY_; - for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - else - positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; - - if (land) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - - // Normals apparently don't connect seamlessly between cells - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixNormal(normal, cellX, cellY, col, row); - - // some corner normals appear to be complete garbage (z < 0) - if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) - averageNormal(normal, cellX, cellY, col, row); - - assert(normal.z > 0); - - normals[vertX*numVerts*3 + vertY*3] = normal.x; - normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; - normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; - - if (hasColors) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - // Unlike normals, colors mostly connect seamlessly between cells, but not always... - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixColour(color, cellX, cellY, col, row); - - color.a = 1; - Ogre::uint32 rsColor; - Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); - - ++vertX; - } - ++vertY; - } - vertX_ = vertX; - } - vertY_ = vertY; - - assert(vertX_ == numVerts); // Ensure we covered whole area - } - assert(vertY_ == numVerts); // Ensure we covered whole area - } - - TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY, - int x, int y) - { - // For the first/last row/column, we need to get the texture from the neighbour cell - // to get consistent blending at the borders - --x; - if (x < 0) - { - --cellX; - x += ESM::Land::LAND_TEXTURE_SIZE; - } - if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? - { - ++cellY; - y -= ESM::Land::LAND_TEXTURE_SIZE; - } - - assert(xmLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; - if (tex == 0) - return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); - } - else - return std::make_pair(0,0); - } - - std::string TerrainStorage::getTextureName(UniqueTextureId id) - { - if (id.first == 0) - return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded? - - // NB: All vtex ids are +1 compared to the ltex ids - const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); - - std::string texture = ltex->mTexture; - //TODO this is needed due to MWs messed up texture handling - texture = texture.substr(0, texture.rfind(".")) + ".dds"; - - return texture; - } - - void TerrainStorage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) - { - for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) - { - out.push_back(Terrain::LayerCollection()); - out.back().mTarget = *it; - getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); - } - } - - void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); - } - - void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - // TODO - blending isn't completely right yet; the blending radius appears to be - // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap - // and interpolate the rest of the cell by hand? :/ - - Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = origin.x; - int cellY = origin.y; - - // Save the used texture indices so we know the total number of textures - // and number of required blend maps - std::set textureIndices; - // Due to the way the blending works, the base layer will always shine through in between - // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). - // To get a consistent look, we need to make sure to use the same base layer in all cells. - // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. - textureIndices.insert(std::make_pair(0,0)); - - for (int y=0; y textureIndicesMap; - for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) - { - int size = textureIndicesMap.size(); - textureIndicesMap[*it] = size; - layerList.push_back(getLayerInfo(getTextureName(*it))); - } - - int numTextures = textureIndices.size(); - // numTextures-1 since the base layer doesn't need blending - int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); - - int channels = pack ? 4 : 1; - - // Second iteration - create and fill in the blend maps - const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - - for (int i=0; isecond; - int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); - int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - - if (blendIndex == i) - pData[y*blendmapSize*channels + x*channels + channel] = 255; - else - pData[y*blendmapSize*channels + x*channels + channel] = 0; - } - } - blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); - } - } - - float TerrainStorage::getHeightAt(const Ogre::Vector3 &worldPos) - { - int cellX = std::floor(worldPos.x / 8192.f); - int cellY = std::floor(worldPos.y / 8192.f); - - ESM::Land* land = getLand(cellX, cellY); - if (!land) - return -2048; - - // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition - - // Normalized position in the cell - float nX = (worldPos.x - (cellX * 8192))/8192.f; - float nY = (worldPos.y - (cellY * 8192))/8192.f; - - // get left / bottom points (rounded down) - float factor = ESM::Land::LAND_SIZE - 1.0f; - float invFactor = 1.0f / factor; - - int startX = static_cast(nX * factor); - int startY = static_cast(nY * factor); - int endX = startX + 1; - int endY = startY + 1; - - assert(endX < ESM::Land::LAND_SIZE); - assert(endY < ESM::Land::LAND_SIZE); - - // now get points in terrain space (effectively rounding them to boundaries) - float startXTS = startX * invFactor; - float startYTS = startY * invFactor; - float endXTS = endX * invFactor; - float endYTS = endY * invFactor; - - // get parametric from start coord to next point - float xParam = (nX - startXTS) * factor; - float yParam = (nY - startYTS) * factor; - - /* For even / odd tri strip rows, triangles are this shape: - even odd - 3---2 3---2 - | / | | \ | - 0---1 0---1 - */ - - // Build all 4 positions in normalized cell space, using point-sampled height - Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); - Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); - Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); - Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); - // define this plane in terrain space - Ogre::Plane plane; - // (At the moment, all rows have the same triangle alignment) - if (true) - { - // odd row - bool secondTri = ((1.0 - yParam) > xParam); - if (secondTri) - plane.redefine(v0, v1, v3); - else - plane.redefine(v1, v2, v3); - } - else - { - // even row - bool secondTri = (yParam > xParam); - if (secondTri) - plane.redefine(v0, v2, v3); - else - plane.redefine(v0, v1, v2); - } - - // Solve plane equation for z - return (-plane.normal.x * nX - -plane.normal.y * nY - - plane.d) / plane.normal.z * 8192; - - } - - float TerrainStorage::getVertexHeight(const ESM::Land *land, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; - } - - Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture) - { - // Already have this cached? - if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) - return mLayerInfoMap[texture]; - - Terrain::LayerInfo info; - info.mParallax = false; - info.mSpecular = false; - info.mDiffuseMap = "textures\\" + texture; - std::string texture_ = texture; - boost::replace_last(texture_, ".", "_nh."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - { - info.mNormalMap = "textures\\" + texture_; - info.mParallax = true; - } - else - { - texture_ = texture; - boost::replace_last(texture_, ".", "_n."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - info.mNormalMap = "textures\\" + texture_; - } - - texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - { - info.mDiffuseMap = "textures\\" + texture_; - info.mSpecular = true; - } - - // This wasn't cached, so the textures are probably not loaded either. - // Background load them so they are hopefully already loaded once we need them! - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); - if (!info.mNormalMap.empty()) - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); - - mLayerInfoMap[texture] = info; - - return info; - } - - Terrain::LayerInfo TerrainStorage::getDefaultLayer() - { - Terrain::LayerInfo info; - info.mDiffuseMap = "textures\\_land_default.dds"; - info.mParallax = false; - info.mSpecular = false; - return info; - } - - float TerrainStorage::getCellWorldSize() - { - return ESM::Land::REAL_SIZE; - } - - int TerrainStorage::getCellVertices() - { - return ESM::Land::LAND_SIZE; - } - } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index c1fb744451..30a2a61ac3 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -1,15 +1,13 @@ #ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H -#include -#include - -#include +#include namespace MWRender { - class TerrainStorage : public Terrain::Storage + /// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage. + class TerrainStorage : public ESMTerrain::Storage { private: virtual ESM::Land* getLand (int cellX, int cellY); @@ -18,92 +16,6 @@ namespace MWRender /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); - - /// Get the minimum and maximum heights of a terrain region. - /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. - /// Larger chunks can simply merge AABB of children. - /// @param size size of the chunk in cell units - /// @param center center of the chunk in cell units - /// @param min min height will be stored here - /// @param max max height will be stored here - /// @return true if there was data available for this terrain chunk - virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); - - /// Fill vertex buffers for a terrain chunk. - /// @note May be called from background threads. Make sure to only call thread-safe functions from here! - /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. - /// @param lodLevel LOD level, 0 = most detailed - /// @param size size of the terrain chunk in cell units - /// @param center center of the chunk in cell units - /// @param positions buffer to write vertices - /// @param normals buffer to write vertex normals - /// @param colours buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours); - - /// Create textures holding layer blend values for a terrain chunk. - /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param chunkSize size of the terrain chunk in cell units - /// @param chunkCenter center of the chunk in cell units - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - /// @param blendmaps created blendmaps will be written here - /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); - - /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. - /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. - /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param nodes A collection of nodes for which to retrieve the aforementioned data - /// @param out Output vector - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); - - virtual float getHeightAt (const Ogre::Vector3& worldPos); - - virtual Terrain::LayerInfo getDefaultLayer(); - - /// Get the transformation factor for mapping cell units to world units. - virtual float getCellWorldSize(); - - /// Get the number of vertices on one side for each cell. Should be (power of two)+1 - virtual int getCellVertices(); - - private: - void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); - void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - - float getVertexHeight (const ESM::Land* land, int x, int y); - - // Since plugins can define new texture palettes, we need to know the plugin index too - // in order to retrieve the correct texture name. - // pair - typedef std::pair UniqueTextureId; - - UniqueTextureId getVtexIndexAt(int cellX, int cellY, - int x, int y); - std::string getTextureName (UniqueTextureId id); - - std::map mLayerInfoMap; - - Terrain::LayerInfo getLayerInfo(const std::string& texture); - - // Non-virtual - void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); }; } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 409e273887..072653898c 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -26,18 +26,31 @@ typedef SSIZE_T ssize_t; namespace MWRender { -#ifdef OPENMW_USE_FFMPEG - extern "C" { #include #include #include - // From libavformat version 55.0.100 and onward the declaration of av_gettime() is removed from libavformat/avformat.h and moved - // to libavutil/time.h + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) +#define IS_NOTEQ_NOPTS_VAL(x) ((uint64_t)x != AV_NOPTS_VALUE) +#define IS_NOTEQ_NOPTS_VAL_PTR(x) (*(uint64_t*)x != AV_NOPTS_VALUE) +#else +#define IS_NOTEQ_NOPTS_VAL(x) ((int64_t)x != AV_NOPTS_VALUE) +#define IS_NOTEQ_NOPTS_VAL_PTR(x) (*(int64_t*)x != AV_NOPTS_VALUE) +#endif /* LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) */ + + + #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) + #define av_frame_alloc avcodec_alloc_frame + #endif + + // From libavformat version 55.0.100 and onward the declaration of av_gettime() is + // removed from libavformat/avformat.h and moved to libavutil/time.h // https://github.com/FFmpeg/FFmpeg/commit/06a83505992d5f49846c18507a6c3eb8a47c650e - #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO) + #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ + LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO) #include #endif @@ -48,30 +61,25 @@ extern "C" LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) #include #endif -} -#ifdef _WIN32 - // Decide whether to play binkaudio. - #include - // libavcodec versions 54.10.100 (or maybe earlier) to 54.54.100 potentially crashes Windows 64bit. - // From version 54.56 or higher, there's no sound due to the encoding format changing from S16 to FLTP - // (see https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d and - // http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=3049d5b9b32845c86aa5588bb3352bdeb2edfdb2;hp=43c6b45a53a186a187f7266e4d6bd3c2620519f1), - // but does not crash (or at least no known crash). - #if (LIBAVCODEC_VERSION_MAJOR > 54) - #define FFMPEG_PLAY_BINKAUDIO + // WARNING: avcodec versions up to 54.54.100 potentially crashes on Windows 64bit. + + // From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: + // https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d + // http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 + #ifdef HAVE_LIBSWRESAMPLE + #include #else - #ifdef _WIN64 - #if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 55)) - #define FFMPEG_PLAY_BINKAUDIO - #endif - #else - #if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 10)) - #define FFMPEG_PLAY_BINKAUDIO - #endif - #endif + /* FIXME: remove this section once libswresample is available on all platforms */ + #include + #include + #define SwrContext AVAudioResampleContext + int swr_init(AVAudioResampleContext *avr); + void swr_free(AVAudioResampleContext **avr); + int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); + AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); #endif -#endif +} #define MAX_AUDIOQ_SIZE (5 * 16 * 1024) #define MAX_VIDEOQ_SIZE (5 * 256 * 1024) @@ -221,8 +229,11 @@ void PacketQueue::put(AVPacket *pkt) if(!pkt1) throw std::bad_alloc(); pkt1->pkt = *pkt; pkt1->next = NULL; - +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) if(pkt1->pkt.destruct == NULL) +#else + if(pkt1->pkt.buf == NULL) +#endif /* LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) */ { if(av_dup_packet(&pkt1->pkt) < 0) { @@ -319,6 +330,12 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder VideoState *mVideoState; AVStream *mAVStream; + SwrContext *mSwr; + enum AVSampleFormat mOutputSampleFormat; + uint8_t *mDataBuf; + uint8_t **mFrameData; + int mDataBufLen; + AutoAVPacket mPacket; AVFrame *mFrame; ssize_t mFramePos; @@ -385,6 +402,28 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder if(!got_frame || frame->nb_samples <= 0) continue; + if(mSwr) + { + if(!mDataBuf || mDataBufLen < frame->nb_samples) + { + av_freep(&mDataBuf); + if(av_samples_alloc(&mDataBuf, NULL, mAVStream->codec->channels, + frame->nb_samples, mOutputSampleFormat, 0) < 0) + break; + else + mDataBufLen = frame->nb_samples; + } + + if(swr_convert(mSwr, (uint8_t**)&mDataBuf, frame->nb_samples, + (const uint8_t**)frame->extended_data, frame->nb_samples) < 0) + { + break; + } + mFrameData = &mDataBuf; + } + else + mFrameData = &frame->data[0]; + mAudioClock += (double)frame->nb_samples / (double)mAVStream->codec->sample_rate; @@ -399,7 +438,7 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder return -1; /* if update, update the audio clock w/pts */ - if((uint64_t)pkt->pts != AV_NOPTS_VALUE) + if(IS_NOTEQ_NOPTS_VAL(pkt->pts)) mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts; } } @@ -422,7 +461,7 @@ public: MovieAudioDecoder(VideoState *is) : mVideoState(is) , mAVStream(*is->audio_st) - , mFrame(avcodec_alloc_frame()) + , mFrame(av_frame_alloc()) , mFramePos(0) , mFrameSize(0) , mAudioClock(0.0) @@ -431,10 +470,17 @@ public: /* Correct audio only if larger error than this */ , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */) , mAudioDiffAvgCount(0) + , mSwr(0) + , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) + , mDataBuf(NULL) + , mFrameData(NULL) + , mDataBufLen(0) { } virtual ~MovieAudioDecoder() { av_freep(&mFrame); + swr_free(&mSwr); + av_freep(&mDataBuf); } void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type) @@ -445,10 +491,18 @@ public: *type = MWSound::SampleType_Int16; else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT) *type = MWSound::SampleType_Float32; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + *type = MWSound::SampleType_UInt8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + *type = MWSound::SampleType_Int16; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + *type = MWSound::SampleType_Float32; else fail(std::string("Unsupported sample format: ")+ av_get_sample_fmt_name(mAVStream->codec->sample_fmt)); + int64_t ch_layout = mAVStream->codec->channel_layout; + if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = MWSound::ChannelConfig_Mono; else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO) @@ -463,9 +517,15 @@ public: { /* Unknown channel layout. Try to guess. */ if(mAVStream->codec->channels == 1) + { *chans = MWSound::ChannelConfig_Mono; + ch_layout = AV_CH_LAYOUT_MONO; + } else if(mAVStream->codec->channels == 2) + { *chans = MWSound::ChannelConfig_Stereo; + ch_layout = AV_CH_LAYOUT_STEREO; + } else { std::stringstream sstr("Unsupported raw channel count: "); @@ -482,6 +542,30 @@ public: } *samplerate = mAVStream->codec->sample_rate; + + if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + mOutputSampleFormat = AV_SAMPLE_FMT_FLT; + + if(mOutputSampleFormat != AV_SAMPLE_FMT_NONE) + { + mSwr = swr_alloc_set_opts(mSwr, // SwrContext + ch_layout, // output ch layout + mOutputSampleFormat, // output sample format + mAVStream->codec->sample_rate, // output sample rate + ch_layout, // input ch layout + mAVStream->codec->sample_fmt, // input sample format + mAVStream->codec->sample_rate, // input sample rate + 0, // logging level offset + NULL); // log context + if(!mSwr) + fail(std::string("Couldn't allocate SwrContext")); + if(swr_init(mSwr) < 0) + fail(std::string("Couldn't initialize SwrContext")); + } } size_t read(char *stream, size_t len) @@ -502,7 +586,8 @@ public: } mFramePos = std::min(mFrameSize, sample_skip); - sample_skip -= mFramePos; + if(sample_skip > 0 || mFrameSize > -sample_skip) + sample_skip -= mFramePos; continue; } @@ -510,7 +595,7 @@ public: if(mFramePos >= 0) { len1 = std::min(len1, mFrameSize-mFramePos); - memcpy(stream, mFrame->data[0]+mFramePos, len1); + memcpy(stream, mFrameData[0]+mFramePos, len1); } else { @@ -521,29 +606,29 @@ public: /* add samples by copying the first sample*/ if(n == 1) - memset(stream, *mFrame->data[0], len1); + memset(stream, *mFrameData[0], len1); else if(n == 2) { - const int16_t val = *((int16_t*)mFrame->data[0]); + const int16_t val = *((int16_t*)mFrameData[0]); for(size_t nb = 0;nb < len1;nb += n) *((int16_t*)(stream+nb)) = val; } else if(n == 4) { - const int32_t val = *((int32_t*)mFrame->data[0]); + const int32_t val = *((int32_t*)mFrameData[0]); for(size_t nb = 0;nb < len1;nb += n) *((int32_t*)(stream+nb)) = val; } else if(n == 8) { - const int64_t val = *((int64_t*)mFrame->data[0]); + const int64_t val = *((int64_t*)mFrameData[0]); for(size_t nb = 0;nb < len1;nb += n) *((int64_t*)(stream+nb)) = val; } else { for(size_t nb = 0;nb < len1;nb += n) - memcpy(stream+nb, mFrame->data[0], n); + memcpy(stream+nb, mFrameData[0], n); } } @@ -748,6 +833,8 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) * a frame at the time it is allocated. */ static uint64_t global_video_pkt_pts = static_cast(AV_NOPTS_VALUE); + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) { int ret = avcodec_default_get_buffer(c, pic); @@ -761,18 +848,26 @@ static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) if(pic) av_freep(&pic->opaque); avcodec_default_release_buffer(c, pic); } - +#else +static int our_get_buffer2(struct AVCodecContext *c, AVFrame *pic, int flags = AV_GET_BUFFER_FLAG_REF) +{ + int ret = avcodec_default_get_buffer2(c, pic, flags); + uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t)); + *pts = global_video_pkt_pts; + pic->opaque = pts; + return ret; +} +#endif /* LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) */ void VideoState::video_thread_loop(VideoState *self) { AVPacket pkt1, *packet = &pkt1; int frameFinished; AVFrame *pFrame; - double pts; - pFrame = avcodec_alloc_frame(); + pFrame = av_frame_alloc(); - self->rgbaFrame = avcodec_alloc_frame(); + self->rgbaFrame = av_frame_alloc(); avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); while(self->videoq.get(packet, self) >= 0) @@ -783,10 +878,10 @@ void VideoState::video_thread_loop(VideoState *self) if(avcodec_decode_video2((*self->video_st)->codec, pFrame, &frameFinished, packet) < 0) throw std::runtime_error("Error decoding video frame"); - pts = 0; - if((uint64_t)packet->dts != AV_NOPTS_VALUE) + double pts = 0; + if(IS_NOTEQ_NOPTS_VAL(packet->dts)) pts = packet->dts; - else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) + else if(pFrame->opaque && IS_NOTEQ_NOPTS_VAL_PTR(pFrame->opaque)) pts = *(uint64_t*)pFrame->opaque; pts *= av_q2d((*self->video_st)->time_base); @@ -887,6 +982,9 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) // Get a pointer to the codec context for the video stream codecCtx = pFormatCtx->streams[stream_index]->codec; codec = avcodec_find_decoder(codecCtx->codec_id); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56,1,0) + codecCtx->refcounted_frames = 1; +#endif /* LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56,1,0) */ if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) { fprintf(stderr, "Unsupported codec!\n"); @@ -912,9 +1010,12 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) this->video_st = pFormatCtx->streams + stream_index; this->frame_last_delay = 40e-3; - +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) codecCtx->get_buffer = our_get_buffer; codecCtx->release_buffer = our_release_buffer; +#else + codecCtx->get_buffer2 = our_get_buffer2; +#endif /* LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,1,0) */ this->video_thread = boost::thread(video_thread_loop, this); this->refresh_thread = boost::thread(video_refresh, this); break; @@ -994,12 +1095,8 @@ void VideoState::init(const std::string& resourceName) this->external_clock_base = av_gettime(); -#if !defined(_WIN32) || defined(FFMPEG_PLAY_BINKAUDIO) if(audio_index >= 0) this->stream_open(audio_index, this->format_ctx); -#else - std::cout<<"FFmpeg sound disabled for \""+resourceName+"\""<= 0) { @@ -1067,33 +1164,12 @@ void VideoState::deinit() this->format_ctx->pb->buffer = NULL; av_free(this->format_ctx->pb); - this->format_ctx->pb = NULL;; + this->format_ctx->pb = NULL; } avformat_close_input(&this->format_ctx); } } -#else // defined OPENMW_USE_FFMPEG - -class VideoState -{ -public: - VideoState() { } - - void init(const std::string& resourceName) - { - throw std::runtime_error("FFmpeg not supported, cannot play \""+resourceName+"\""); - } - void deinit() { } - - void close() { } - - bool update() - { return false; } -}; - -#endif // defined OPENMW_USE_FFMPEG - VideoPlayer::VideoPlayer() : mState(NULL) @@ -1155,11 +1231,6 @@ int VideoPlayer::getVideoHeight() return height; } -void VideoPlayer::stopVideo () -{ - close(); -} - void VideoPlayer::close() { if(mState) diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp index 47e252cc15..69b2148709 100644 --- a/apps/openmw/mwrender/videoplayer.hpp +++ b/apps/openmw/mwrender/videoplayer.hpp @@ -21,7 +21,6 @@ namespace MWRender void update(); void close(); - void stopVideo(); bool isPlaying(); @@ -32,9 +31,6 @@ namespace MWRender private: VideoState* mState; - - int mWidth; - int mHeight; }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 2cbc4462c3..752da30df8 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -384,10 +384,11 @@ void Water::update(float dt, Ogre::Vector3 player) void Water::frameStarted(float dt) { - mSimulation->update(dt, mPlayer); - if (mReflection) + { + mSimulation->update(dt, mPlayer); mReflection->update(); + } } void Water::applyRTT() diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index d844138d69..a3f9354874 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -420,7 +420,6 @@ namespace MWScript runtime.pop(); const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - std::string currentTargetId; bool targetsAreEqual = false; MWWorld::Ptr targetPtr; @@ -457,7 +456,6 @@ namespace MWScript MWWorld::Ptr actor = R()(runtime); MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); creatureStats.getAiSequence().stopCombat(); - creatureStats.setHostile(false); } }; diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index d2e7748598..fb6b73be63 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -121,6 +121,34 @@ namespace MWScript } }; + template + class OpGetForceJump : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + } + }; + + template + class OpGetForceMoveJump : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + } + }; + template class OpGetForceSneak : public Interpreter::Opcode0 { @@ -169,27 +197,54 @@ namespace MWScript interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision); + //Force Run interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeForceRun, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); + interpreter.installSegment5 (Compiler::Control::opcodeForceRun, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); + + //Force Jump + interpreter.installSegment5 (Compiler::Control::opcodeClearForceJump, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5 (Compiler::Control::opcodeClearForceJumpExplicit, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceJump, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceJumpExplicit, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + + //Force MoveJump + interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJump, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJumpExplicit, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJump, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJumpExplicit, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + + //Force Sneak + interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); + interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); + interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning); interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRunExplicit, new OpGetForceRun); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceJump, new OpGetForceJump); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceJumpExplicit, new OpGetForceJump); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJump, new OpGetForceMoveJump); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJumpExplicit, new OpGetForceMoveJump); interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneak, new OpGetForceSneak); interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneakExplicit, new OpGetForceSneak); } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 8c9d6e480e..6263325648 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -405,5 +405,32 @@ op 0x200024c: Face op 0x200024d: Face, explicit op 0x200024e: GetStat (dummy function) op 0x200024f: GetStat (dummy function), explicit +op 0x2000250: GetCollidingPC +op 0x2000251: GetCollidingPC, explicit +op 0x2000252: GetCollidingActor +op 0x2000253: GetCollidingActor, explicit +op 0x2000254: HurtStandingActor +op 0x2000255: HurtStandingActor, explicit +op 0x2000256: HurtCollidingActor +op 0x2000257: HurtCollidingActor, explicit +op 0x2000258: ClearForceJump +op 0x2000259: ClearForceJump, explicit reference +op 0x200025a: ForceJump +op 0x200025b: ForceJump, explicit reference +op 0x200025c: ClearForceMoveJump +op 0x200025d: ClearForceMoveJump, explicit reference +op 0x200025e: ForceMoveJump +op 0x200025f: ForceMoveJump, explicit reference +op 0x2000260: GetForceJump +op 0x2000261: GetForceJump, explicit reference +op 0x2000262: GetForceMoveJump +op 0x2000263: GetForceMoveJump, explicit reference +op 0x2000264-0x200027b: GetMagicEffect +op 0x200027c-0x2000293: GetMagicEffect, explicit +op 0x2000294-0x20002ab: SetMagicEffect +op 0x20002ac-0x20002c3: SetMagicEffect, explicit +op 0x20002c4-0x20002db: ModMagicEffect +op 0x20002dc-0x20002f3: ModMagicEffect, explicit +op 0x20002f4: ResetActors -opcodes 0x2000250-0x3ffffff unused +opcodes 0x20002f5-0x3ffffff unused diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 8e118e2f8c..8270c4e6b2 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -15,61 +15,69 @@ namespace MWScript { + GlobalScriptDesc::GlobalScriptDesc() : mRunning (false) {} + + GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) - { - addStartup(); - } + {} - void GlobalScripts::addScript (const std::string& name) + void GlobalScripts::addScript (const std::string& name, const std::string& targetId) { - std::map >::iterator iter = + std::map::iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) { if (const ESM::Script *script = mStore.get().find (name)) { - Locals locals; + GlobalScriptDesc desc; + desc.mRunning = true; + desc.mLocals.configure (*script); + desc.mId = targetId; - locals.configure (*script); - - mScripts.insert (std::make_pair (name, std::make_pair (true, locals))); + mScripts.insert (std::make_pair (name, desc)); } } - else - iter->second.first = true; + else if (!iter->second.mRunning) + { + iter->second.mRunning = true; + iter->second.mId = targetId; + } } void GlobalScripts::removeScript (const std::string& name) { - std::map >::iterator iter = + std::map::iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) - iter->second.first = false; + iter->second.mRunning = false; } bool GlobalScripts::isRunning (const std::string& name) const { - std::map >::const_iterator iter = + std::map::const_iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) return false; - return iter->second.first; + return iter->second.mRunning; } void GlobalScripts::run() { - for (std::map >::iterator iter (mScripts.begin()); + for (std::map::iterator iter (mScripts.begin()); iter!=mScripts.end(); ++iter) { - if (iter->second.first) + if (iter->second.mRunning) { + MWWorld::Ptr ptr; + MWScript::InterpreterContext interpreterContext ( - &iter->second.second, MWWorld::Ptr()); + &iter->second.mLocals, MWWorld::Ptr(), iter->second.mId); + MWBase::Environment::get().getScriptManager()->run (iter->first, interpreterContext); } } @@ -99,16 +107,18 @@ namespace MWScript void GlobalScripts::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { - for (std::map >::const_iterator iter (mScripts.begin()); + for (std::map::const_iterator iter (mScripts.begin()); iter!=mScripts.end(); ++iter) { ESM::GlobalScript script; script.mId = iter->first; - iter->second.second.write (script.mLocals, iter->first); + iter->second.mLocals.write (script.mLocals, iter->first); - script.mRunning = iter->second.first ? 1 : 0; + script.mRunning = iter->second.mRunning ? 1 : 0; + + script.mTargetId = iter->second.mId; writer.startRecord (ESM::REC_GSCR); script.save (writer); @@ -124,25 +134,25 @@ namespace MWScript ESM::GlobalScript script; script.load (reader); - std::map >::iterator iter = + std::map::iterator iter = mScripts.find (script.mId); if (iter==mScripts.end()) { if (const ESM::Script *scriptRecord = mStore.get().search (script.mId)) { - std::pair data (false, Locals()); + GlobalScriptDesc desc; + desc.mLocals.configure (*scriptRecord); - data.second.configure (*scriptRecord); - - iter = mScripts.insert (std::make_pair (script.mId, data)).first; + iter = mScripts.insert (std::make_pair (script.mId, desc)).first; } else // script does not exist anymore return true; } - iter->second.first = script.mRunning!=0; - iter->second.second.read (script.mLocals, script.mId); + iter->second.mRunning = script.mRunning!=0; + iter->second.mLocals.read (script.mLocals, script.mId); + iter->second.mId = script.mTargetId; return true; } @@ -153,21 +163,19 @@ namespace MWScript Locals& GlobalScripts::getLocals (const std::string& name) { std::string name2 = ::Misc::StringUtils::lowerCase (name); - std::map >::iterator iter = - mScripts.find (name2); + std::map::iterator iter = mScripts.find (name2); if (iter==mScripts.end()) { if (const ESM::Script *script = mStore.get().find (name)) { - Locals locals; + GlobalScriptDesc desc; + desc.mLocals.configure (*script); - locals.configure (*script); - - iter = mScripts.insert (std::make_pair (name, std::make_pair (false, locals))).first; + iter = mScripts.insert (std::make_pair (name, desc)).first; } } - return iter->second.second; + return iter->second.mLocals; } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 97584a5b88..55c2e93217 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -26,16 +26,25 @@ namespace MWWorld namespace MWScript { + struct GlobalScriptDesc + { + bool mRunning; + Locals mLocals; + std::string mId; // ID used to start targeted script (empty if not a targeted script) + + GlobalScriptDesc(); + }; + class GlobalScripts { const MWWorld::ESMStore& mStore; - std::map > mScripts; // running, local variables + std::map mScripts; public: GlobalScripts (const MWWorld::ESMStore& store); - void addScript (const std::string& name); + void addScript (const std::string& name, const std::string& targetId = ""); void removeScript (const std::string& name); diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 028f83a61a..21fee5f571 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -3,8 +3,14 @@ #include #include +#include #include + +#include + +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -14,6 +20,7 @@ #include "../mwbase/inputmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwmechanics/npcstats.hpp" @@ -22,7 +29,7 @@ namespace MWScript { - MWWorld::Ptr InterpreterContext::getReference ( + MWWorld::Ptr InterpreterContext::getReferenceImp ( const std::string& id, bool activeOnly, bool doThrow) { if (!id.empty()) @@ -31,6 +38,10 @@ namespace MWScript } else { + if (mReference.isEmpty() && !mTargetId.empty()) + mReference = + MWBase::Environment::get().getWorld()->searchPtr (mTargetId, false); + if (mReference.isEmpty() && doThrow) throw std::runtime_error ("no implicit reference"); @@ -38,7 +49,7 @@ namespace MWScript } } - const MWWorld::Ptr InterpreterContext::getReference ( + const MWWorld::Ptr InterpreterContext::getReferenceImp ( const std::string& id, bool activeOnly, bool doThrow) const { if (!id.empty()) @@ -47,6 +58,10 @@ namespace MWScript } else { + if (mReference.isEmpty() && !mTargetId.empty()) + mReference = + MWBase::Environment::get().getWorld()->searchPtr (mTargetId, false); + if (mReference.isEmpty() && doThrow) throw std::runtime_error ("no implicit reference"); @@ -64,7 +79,7 @@ namespace MWScript } else { - const MWWorld::Ptr ptr = getReference (id, false); + const MWWorld::Ptr ptr = getReferenceImp (id, false); id = ptr.getClass().getScript (ptr); @@ -84,7 +99,7 @@ namespace MWScript } else { - const MWWorld::Ptr ptr = getReference (id, false); + const MWWorld::Ptr ptr = getReferenceImp (id, false); id = ptr.getClass().getScript (ptr); @@ -95,11 +110,43 @@ namespace MWScript } } + int InterpreterContext::findLocalVariableIndex (const std::string& scriptId, + const std::string& name, char type) const + { + int index = MWBase::Environment::get().getScriptManager()->getLocals (scriptId). + searchIndex (type, name); + + if (index!=-1) + return index; + + std::ostringstream stream; + + stream << "Failed to access "; + + switch (type) + { + case 's': stream << "short"; break; + case 'l': stream << "long"; break; + case 'f': stream << "float"; break; + } + + stream << " member variable " << name << " in script " << scriptId; + + throw std::runtime_error (stream.str().c_str()); + } + + InterpreterContext::InterpreterContext ( - MWScript::Locals *locals, MWWorld::Ptr reference) + MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) : mLocals (locals), mReference (reference), - mActivationHandled (false) - {} + mActivationHandled (false), mTargetId (targetId) + { + // If we run on a reference (local script, dialogue script or console with object + // selected), store the ID of that reference store it so it can be inherited by + // targeted scripts started from this one. + if (targetId.empty() && !reference.isEmpty()) + mTargetId = reference.getClass().getId (reference); + } int InterpreterContext::getLocalShort (int index) const { @@ -236,34 +283,34 @@ namespace MWScript std::string InterpreterContext::getNPCName() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; return npc.mName; } std::string InterpreterContext::getNPCRace() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mRace); return race->mName; } std::string InterpreterContext::getNPCClass() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mClass); return class_->mName; } std::string InterpreterContext::getNPCFaction() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mFaction); return faction->mName; } std::string InterpreterContext::getNPCRank() const { - std::map ranks = mReference.getClass().getNpcStats (mReference).getFactionRanks(); + std::map ranks = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks(); std::map::const_iterator it = ranks.begin(); MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -299,7 +346,7 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; + std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first; std::map ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map::const_iterator it = ranks.find(factionId); @@ -326,7 +373,7 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; + std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first; std::map ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map::const_iterator it = ranks.find(factionId); @@ -366,9 +413,9 @@ namespace MWScript return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name); } - void InterpreterContext::startScript (const std::string& name) + void InterpreterContext::startScript (const std::string& name, const std::string& targetId) { - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name, targetId); } void InterpreterContext::stopScript (const std::string& name) @@ -383,16 +430,14 @@ namespace MWScript MWWorld::Ptr ref2; if (id.empty()) - ref2 = getReference("", true, true); + ref2 = getReferenceImp(); else - ref2 = MWBase::Environment::get().getWorld()->searchPtr(id, true); + ref2 = MWBase::Environment::get().getWorld()->getPtr(id, false); - // If either actor is in a non-active cell, return a large value (just like vanilla) - if (ref2.isEmpty()) - return std::numeric_limits().max(); + const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr(name, false); - const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->searchPtr(name, true); - if (ref.isEmpty()) + // If the objects are in different worldspaces, return a large value (just like vanilla) + if (ref.getCell()->getCell()->getCellId().mWorldspace != ref2.getCell()->getCell()->getCellId().mWorldspace) return std::numeric_limits().max(); double diff[3]; @@ -435,12 +480,6 @@ namespace MWScript mActivationHandled = true; } - void InterpreterContext::clearActivation() - { - mActivated = MWWorld::Ptr(); - mActivationHandled = false; - } - float InterpreterContext::getSecondsPassed() const { return MWBase::Environment::get().getFrameDuration(); @@ -448,19 +487,19 @@ namespace MWScript bool InterpreterContext::isDisabled (const std::string& id) const { - const MWWorld::Ptr ref = getReference (id, false); + const MWWorld::Ptr ref = getReferenceImp (id, false); return !ref.getRefData().isEnabled(); } void InterpreterContext::enable (const std::string& id) { - MWWorld::Ptr ref = getReference (id, false); + MWWorld::Ptr ref = getReferenceImp (id, false); MWBase::Environment::get().getWorld()->enable (ref); } void InterpreterContext::disable (const std::string& id) { - MWWorld::Ptr ref = getReference (id, false); + MWWorld::Ptr ref = getReferenceImp (id, false); MWBase::Environment::get().getWorld()->disable (ref); } @@ -471,10 +510,7 @@ namespace MWScript const Locals& locals = getMemberLocals (scriptId, global); - int index = MWBase::Environment::get().getScriptManager()->getLocalIndex ( - scriptId, name, 's'); - - return locals.mShorts[index]; + return locals.mShorts[findLocalVariableIndex (scriptId, name, 's')]; } int InterpreterContext::getMemberLong (const std::string& id, const std::string& name, @@ -484,10 +520,7 @@ namespace MWScript const Locals& locals = getMemberLocals (scriptId, global); - int index = MWBase::Environment::get().getScriptManager()->getLocalIndex ( - scriptId, name, 'l'); - - return locals.mLongs[index]; + return locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')]; } float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name, @@ -497,10 +530,7 @@ namespace MWScript const Locals& locals = getMemberLocals (scriptId, global); - int index = MWBase::Environment::get().getScriptManager()->getLocalIndex ( - scriptId, name, 'f'); - - return locals.mFloats[index]; + return locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')]; } void InterpreterContext::setMemberShort (const std::string& id, const std::string& name, @@ -510,10 +540,7 @@ namespace MWScript Locals& locals = getMemberLocals (scriptId, global); - int index = - MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 's'); - - locals.mShorts[index] = value; + locals.mShorts[findLocalVariableIndex (scriptId, name, 's')] = value; } void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value, bool global) @@ -522,10 +549,7 @@ namespace MWScript Locals& locals = getMemberLocals (scriptId, global); - int index = - MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'l'); - - locals.mLongs[index] = value; + locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')] = value; } void InterpreterContext::setMemberFloat (const std::string& id, const std::string& name, float value, bool global) @@ -534,14 +558,16 @@ namespace MWScript Locals& locals = getMemberLocals (scriptId, global); - int index = - MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'f'); - - locals.mFloats[index] = value; + locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')] = value; } MWWorld::Ptr InterpreterContext::getReference(bool required) { - return getReference ("", true, required); + return getReferenceImp ("", true, required); + } + + std::string InterpreterContext::getTargetId() const + { + return mTargetId; } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 99026392ed..b543399656 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -27,14 +27,22 @@ namespace MWScript class InterpreterContext : public Interpreter::Context { Locals *mLocals; - MWWorld::Ptr mReference; + mutable MWWorld::Ptr mReference; MWWorld::Ptr mActivated; bool mActivationHandled; - MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true); + std::string mTargetId; - const MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true) const; + /// If \a id is empty, a reference the script is run from is returned or in case + /// of a non-local script the reference derived from the target ID. + MWWorld::Ptr getReferenceImp (const std::string& id = "", bool activeOnly = false, + bool doThrow=true); + + /// If \a id is empty, a reference the script is run from is returned or in case + /// of a non-local script the reference derived from the target ID. + const MWWorld::Ptr getReferenceImp (const std::string& id = "", + bool activeOnly = false, bool doThrow=true) const; const Locals& getMemberLocals (std::string& id, bool global) const; ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before @@ -42,9 +50,14 @@ namespace MWScript Locals& getMemberLocals (std::string& id, bool global); ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before + /// Throws an exception if local variable can't be found. + int findLocalVariableIndex (const std::string& scriptId, const std::string& name, + char type) const; + public: - InterpreterContext (MWScript::Locals *locals, MWWorld::Ptr reference); + InterpreterContext (MWScript::Locals *locals, MWWorld::Ptr reference, + const std::string& targetId = ""); ///< The ownership of \a locals is not transferred. 0-pointer allowed. virtual int getLocalShort (int index) const; @@ -113,7 +126,7 @@ namespace MWScript virtual bool isScriptRunning (const std::string& name) const; - virtual void startScript (const std::string& name); + virtual void startScript (const std::string& name, const std::string& targetId = ""); virtual void stopScript (const std::string& name); @@ -133,9 +146,6 @@ namespace MWScript void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. - void clearActivation(); - ///< Discard the action defined by the last activate call. - virtual float getSecondsPassed() const; virtual bool isDisabled (const std::string& id = "") const; @@ -158,6 +168,8 @@ namespace MWScript MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) + + virtual std::string getTargetId() const; }; } diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 57584ac306..a1ee48ae68 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -14,12 +14,15 @@ namespace MWScript { void Locals::configure (const ESM::Script& script) { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals (script.mId); + mShorts.clear(); - mShorts.resize (script.mData.mNumShorts, 0); + mShorts.resize (locals.get ('s').size(), 0); mLongs.clear(); - mLongs.resize (script.mData.mNumLongs, 0); + mLongs.resize (locals.get ('l').size(), 0); mFloats.clear(); - mFloats.resize (script.mData.mNumFloats, 0); + mFloats.resize (locals.get ('f').size(), 0); } bool Locals::isEmpty() const @@ -29,7 +32,7 @@ namespace MWScript int Locals::getIntVar(const std::string &script, const std::string &var) { - Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) @@ -53,7 +56,7 @@ namespace MWScript bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { - Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index 1e8c6e12a4..9fa4214acc 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -24,8 +24,11 @@ namespace MWScript bool isEmpty() const; void configure (const ESM::Script& script); + /// @note var needs to be in lowercase bool setVarByInt(const std::string& script, const std::string& var, int val); - int getIntVar (const std::string& script, const std::string& var); ///< if var does not exist, returns 0 + int getIntVar (const std::string& script, const std::string& var); + ///< if var does not exist, returns 0 + /// @note var needs to be in lowercase void write (ESM::Locals& locals, const std::string& script) const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index a4c74be6b2..be8c85bbf9 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -3,8 +3,6 @@ #include -#include - #include #include #include @@ -147,6 +145,14 @@ namespace MWScript } ptr.getClass().lock (ptr, lockLevel); + + // Instantly reset door to closed state + // This is done when using Lock in scripts, but not when using Lock spells. + if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport()) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, 0); + MWBase::Environment::get().getWorld()->localRotateObject(ptr, 0, 0, 0); + } } }; @@ -240,7 +246,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWorld()->getFader()->fadeIn(time); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(time); } }; @@ -253,7 +259,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWorld()->getFader()->fadeOut(time); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(time); } }; @@ -269,7 +275,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWorld()->getFader()->fadeTo(alpha, time); + MWBase::Environment::get().getWindowManager()->fadeScreenTo(alpha, time); } }; @@ -350,7 +356,7 @@ namespace MWScript key = ESM::MagicEffect::effectStringToId(effect); runtime.push(ptr.getClass().getCreatureStats(ptr).getMagicEffects().get( - MWMechanics::EffectKey(key)).mMagnitude > 0); + MWMechanics::EffectKey(key)).getMagnitude() > 0); } }; @@ -603,6 +609,60 @@ namespace MWScript } }; + template + class OpGetCollidingPc : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (MWBase::Environment::get().getWorld()->getPlayerCollidingWith(ptr)); + } + }; + + template + class OpGetCollidingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (MWBase::Environment::get().getWorld()->getActorCollidingWith(ptr)); + } + }; + + template + class OpHurtStandingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); + + MWBase::Environment::get().getWorld()->hurtStandingActors(ptr, healthDiffPerSecond); + } + }; + + template + class OpHurtCollidingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); + + MWBase::Environment::get().getWorld()->hurtCollidingActors(ptr, healthDiffPerSecond); + } + }; + class OpGetWindSpeed : public Interpreter::Opcode0 { public: @@ -811,7 +871,6 @@ namespace MWScript { MWBase::World* world = MWBase::Environment::get().getWorld(); world->goToJail(); - MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; @@ -959,6 +1018,14 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPc, new OpGetCollidingPc); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPcExplicit, new OpGetCollidingPc); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActor, new OpGetCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActorExplicit, new OpGetCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActor, new OpHurtStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActorExplicit, new OpHurtStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActor, new OpHurtCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActorExplicit, new OpHurtCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 7b858dacf5..8110803613 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -22,12 +23,19 @@ namespace MWScript { ScriptManager::ScriptManager (const MWWorld::ESMStore& store, bool verbose, - Compiler::Context& compilerContext, int warningsMode) + Compiler::Context& compilerContext, int warningsMode, + const std::vector& scriptBlacklist) : mErrorHandler (std::cerr), mStore (store), mVerbose (verbose), mCompilerContext (compilerContext), mParser (mErrorHandler, mCompilerContext), mOpcodesInstalled (false), mGlobalScripts (store) { mErrorHandler.setWarningsMode (warningsMode); + + mScriptBlacklist.resize (scriptBlacklist.size()); + + std::transform (scriptBlacklist.begin(), scriptBlacklist.end(), + mScriptBlacklist.begin(), Misc::StringUtils::lowerCase); + std::sort (mScriptBlacklist.begin(), mScriptBlacklist.end()); } bool ScriptManager::compile (const std::string& name) @@ -35,13 +43,12 @@ namespace MWScript mParser.reset(); mErrorHandler.reset(); - bool Success = true; - if (const ESM::Script *script = mStore.get().find (name)) { if (mVerbose) std::cout << "compiling script: " << name << std::endl; + bool Success = true; try { std::istringstream input (script->mScriptText); @@ -133,16 +140,22 @@ namespace MWScript int success = 0; const MWWorld::Store& scripts = mStore.get(); - MWWorld::Store::iterator it = scripts.begin(); - for (; it != scripts.end(); ++it, ++count) - if (compile (it->mId)) - ++success; + for (MWWorld::Store::iterator iter = scripts.begin(); + iter != scripts.end(); ++iter) + if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(), + Misc::StringUtils::lowerCase (iter->mId))) + { + ++count; + + if (compile (iter->mId)) + ++success; + } return std::make_pair (count, success); } - Compiler::Locals& ScriptManager::getLocals (const std::string& name) + const Compiler::Locals& ScriptManager::getLocals (const std::string& name) { std::string name2 = Misc::StringUtils::lowerCase (name); @@ -182,46 +195,4 @@ namespace MWScript { return mGlobalScripts; } - - int ScriptManager::getLocalIndex (const std::string& scriptId, const std::string& variable, - char type) - { - const ESM::Script *script = mStore.get().find (scriptId); - - int offset = 0; - int size = 0; - - switch (type) - { - case 's': - - offset = 0; - size = script->mData.mNumShorts; - break; - - case 'l': - - offset = script->mData.mNumShorts; - size = script->mData.mNumLongs; - break; - - case 'f': - - offset = script->mData.mNumShorts+script->mData.mNumLongs; - size = script->mData.mNumFloats; - break; - - default: - - throw std::runtime_error ("invalid variable type"); - } - - std::string variable2 = Misc::StringUtils::lowerCase (variable); - - for (int i=0; imVarNames.at (i+offset))==variable2) - return i; - - throw std::runtime_error ("unable to access local variable " + variable + " of " + scriptId); - } } diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index da3abc60b5..6026f6aba2 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -48,11 +48,13 @@ namespace MWScript ScriptCollection mScripts; GlobalScripts mGlobalScripts; std::map mOtherLocals; + std::vector mScriptBlacklist; public: ScriptManager (const MWWorld::ESMStore& store, bool verbose, - Compiler::Context& compilerContext, int warningsMode); + Compiler::Context& compilerContext, int warningsMode, + const std::vector& scriptBlacklist); virtual void run (const std::string& name, Interpreter::Context& interpreterContext); ///< Run the script with the given name (compile first, if not compiled yet) @@ -65,15 +67,10 @@ namespace MWScript ///< Compile all scripts /// \return count, success - virtual Compiler::Locals& getLocals (const std::string& name); + virtual const Compiler::Locals& getLocals (const std::string& name); ///< Return locals for script \a name. virtual GlobalScripts& getGlobalScripts(); - - virtual int getLocalIndex (const std::string& scriptId, const std::string& variable, - char type); - ///< Return index of the variable of the given name and type in the given script. Will - /// throw an exception, if there is no such script or variable or the type does not match. }; } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 07d137ce2a..c012cb12ee 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -528,11 +528,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr actor = R()(runtime, false); + std::string factionID = ""; if(arg0==0) { - MWWorld::Ptr actor = R()(runtime); factionID = getDialogueActorFaction(actor); } else @@ -562,11 +563,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr actor = R()(runtime, false); + std::string factionID = ""; if(arg0==0) { - MWWorld::Ptr actor = R()(runtime); factionID = getDialogueActorFaction(actor); } else @@ -602,11 +604,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr actor = R()(runtime, false); + std::string factionID = ""; if(arg0==0) { - MWWorld::Ptr actor = R()(runtime); factionID = getDialogueActorFaction(actor); } else @@ -637,7 +640,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::Ptr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0) @@ -750,6 +753,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr ptr = R()(runtime, false); + std::string factionId; if (arg0==1) @@ -759,8 +764,6 @@ namespace MWScript } else { - MWWorld::Ptr ptr = R()(runtime); - if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; } @@ -783,6 +786,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr ptr = R()(runtime, false); + Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -795,8 +800,6 @@ namespace MWScript } else { - MWWorld::Ptr ptr = R()(runtime); - if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; } @@ -818,6 +821,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr ptr = R()(runtime, false); + Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -830,8 +835,6 @@ namespace MWScript } else { - MWWorld::Ptr ptr = R()(runtime); - if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; } @@ -913,7 +916,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::Ptr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -952,6 +955,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr ptr = R()(runtime, false); + std::string factionID = ""; if(arg0 >0 ) { @@ -960,7 +965,6 @@ namespace MWScript } else { - MWWorld::Ptr ptr = R()(runtime); if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; @@ -985,6 +989,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { + MWWorld::Ptr ptr = R()(runtime, false); + std::string factionID = ""; if(arg0 >0 ) { @@ -993,7 +999,6 @@ namespace MWScript } else { - MWWorld::Ptr ptr = R()(runtime); if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; @@ -1176,6 +1181,91 @@ namespace MWScript } }; + template + class OpGetMagicEffect : public Interpreter::Opcode0 + { + int mPositiveEffect; + int mNegativeEffect; + + public: + OpGetMagicEffect (int positiveEffect, int negativeEffect) + : mPositiveEffect(positiveEffect) + , mNegativeEffect(negativeEffect) + { + } + + 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(); + if (mNegativeEffect != -1) + currentValue -= stats.getMagicEffects().get(mNegativeEffect).getMagnitude(); + + int ret = static_cast(currentValue); + runtime.push(ret); + } + }; + + template + class OpSetMagicEffect : public Interpreter::Opcode0 + { + int mPositiveEffect; + int mNegativeEffect; + + public: + OpSetMagicEffect (int positiveEffect, int negativeEffect) + : mPositiveEffect(positiveEffect) + , mNegativeEffect(negativeEffect) + { + } + + 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(); + if (mNegativeEffect != -1) + currentValue -= stats.getMagicEffects().get(mNegativeEffect).getMagnitude(); + currentValue = int(currentValue); + + int arg = runtime[0].mInteger; + runtime.pop(); + stats.getMagicEffects().modifyBase(mPositiveEffect, (arg - currentValue)); + } + }; + + template + class OpModMagicEffect : public Interpreter::Opcode0 + { + int mPositiveEffect; + int mNegativeEffect; + + public: + OpModMagicEffect (int positiveEffect, int negativeEffect) + : mPositiveEffect(positiveEffect) + , mNegativeEffect(negativeEffect) + { + } + + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + int arg = runtime[0].mInteger; + runtime.pop(); + stats.getMagicEffects().modifyBase(mPositiveEffect, arg); + } + }; + + struct MagicEffect + { + int mPositiveEffect; + int mNegativeEffect; + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { for (int i=0; i); interpreter.installSegment5 (Compiler::Stats::opcodeGetStat, new OpGetStat); interpreter.installSegment5 (Compiler::Stats::opcodeGetStatExplicit, new OpGetStat); + + static const MagicEffect sMagicEffects[] = { + { ESM::MagicEffect::ResistMagicka, ESM::MagicEffect::WeaknessToMagicka }, + { ESM::MagicEffect::ResistFire, ESM::MagicEffect::WeaknessToFire }, + { ESM::MagicEffect::ResistFrost, ESM::MagicEffect::WeaknessToFrost }, + { ESM::MagicEffect::ResistShock, ESM::MagicEffect::WeaknessToShock }, + { ESM::MagicEffect::ResistCommonDisease, ESM::MagicEffect::WeaknessToCommonDisease }, + { ESM::MagicEffect::ResistBlightDisease, ESM::MagicEffect::WeaknessToBlightDisease }, + { ESM::MagicEffect::ResistCorprusDisease, ESM::MagicEffect::WeaknessToCorprusDisease }, + { ESM::MagicEffect::ResistPoison, ESM::MagicEffect::WeaknessToPoison }, + { ESM::MagicEffect::ResistParalysis, -1 }, + { ESM::MagicEffect::ResistNormalWeapons, ESM::MagicEffect::WeaknessToNormalWeapons }, + { ESM::MagicEffect::WaterBreathing, -1 }, + { ESM::MagicEffect::Chameleon, -1 }, + { ESM::MagicEffect::WaterWalking, -1 }, + { ESM::MagicEffect::SwiftSwim, -1 }, + { ESM::MagicEffect::Jump, -1 }, + { ESM::MagicEffect::Levitate, -1 }, + { ESM::MagicEffect::Shield, -1 }, + { ESM::MagicEffect::Sound, -1 }, + { ESM::MagicEffect::Silence, -1 }, + { ESM::MagicEffect::Blind, -1 }, + { ESM::MagicEffect::Paralyze, -1 }, + { ESM::MagicEffect::Invisibility, -1 }, + { ESM::MagicEffect::FortifyAttack, -1 }, + { ESM::MagicEffect::Sanctuary, -1 }, + }; + + for (int i=0; i<24; ++i) + { + int positive = sMagicEffects[i].mPositiveEffect; + int negative = sMagicEffects[i].mNegativeEffect; + + interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffect+i, new OpGetMagicEffect (positive, negative)); + interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffectExplicit+i, new OpGetMagicEffect (positive, negative)); + + interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffect+i, new OpSetMagicEffect (positive, negative)); + interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffectExplicit+i, new OpSetMagicEffect (positive, negative)); + + interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffect+i, new OpModMagicEffect (positive, negative)); + interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffectExplicit+i, new OpModMagicEffect (positive, negative)); + } } } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a041049ca4..e74388effc 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -327,7 +327,7 @@ namespace MWScript } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); - ptr.getClass().adjustPosition(ptr); + ptr.getClass().adjustPosition(ptr, false); } else { @@ -363,8 +363,19 @@ namespace MWScript runtime.pop(); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); - MWBase::Environment::get().getWorld()->moveObject(ptr, - MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + + // 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.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->moveObject(ptr, + MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + } + else + { + MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); + } + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -374,7 +385,7 @@ namespace MWScript zRot = zRot/60.; } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); - ptr.getClass().adjustPosition(ptr); + ptr.getClass().adjustPosition(ptr, false); } }; @@ -452,25 +463,26 @@ namespace MWScript Interpreter::Type_Float zRot = runtime[0].mFloat; runtime.pop(); - int cx,cy; - MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); - MWWorld::CellStore* store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); - if(store) + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::CellStore* store = NULL; + if (player.getCell()->isExterior()) { - ESM::Position pos; - pos.pos[0] = x; - pos.pos[1] = y; - pos.pos[2] = z; - pos.rot[0] = pos.rot[1] = 0; - pos.rot[2] = zRot; - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); - ref.getPtr().getCellRef().setPosition(pos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + int cx,cy; + MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); + store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); } else - { - throw std::runtime_error ("unknown cell"); - } + store = player.getCell(); + + ESM::Position pos; + pos.pos[0] = x; + pos.pos[1] = y; + pos.pos[2] = z; + pos.rot[0] = pos.rot[1] = 0; + pos.rot[2] = zRot; + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); + ref.getPtr().getCellRef().setPosition(pos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } }; @@ -708,6 +720,15 @@ namespace MWScript } }; + class OpResetActors : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getWorld()->resetActors(); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -748,6 +769,7 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorldExplicit,new OpMoveWorld); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); + interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); } } } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 10a782b964..414e31d704 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -1,6 +1,3 @@ -#ifdef OPENMW_USE_FFMPEG - - #include "ffmpeg_decoder.hpp" // auto_ptr @@ -8,6 +5,16 @@ #include +extern "C" { +#ifndef HAVE_LIBSWRESAMPLE +/* FIXME: remove this section once libswresample is available on all platforms */ +int swr_init(AVAudioResampleContext *avr); +void swr_free(AVAudioResampleContext **avr); +int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); +AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); +#endif +} + namespace MWSound { @@ -76,7 +83,7 @@ bool FFmpeg_Decoder::getNextPacket() bool FFmpeg_Decoder::getAVAudioData() { - int got_frame, len; + int got_frame; if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO) return false; @@ -86,6 +93,7 @@ bool FFmpeg_Decoder::getAVAudioData() return false; /* Decode some data, and check for errors */ + int len = 0; if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0) return false; @@ -98,6 +106,29 @@ bool FFmpeg_Decoder::getAVAudioData() memmove(mPacket.data, &mPacket.data[len], remaining); av_shrink_packet(&mPacket, remaining); } + + if(mSwr) + { + if(!mDataBuf || mDataBufLen < mFrame->nb_samples) + { + av_freep(&mDataBuf); + if(av_samples_alloc(&mDataBuf, NULL, (*mStream)->codec->channels, + mFrame->nb_samples, mOutputSampleFormat, 0) < 0) + break; + else + mDataBufLen = mFrame->nb_samples; + } + + if(swr_convert(mSwr, (uint8_t**)&mDataBuf, mFrame->nb_samples, + (const uint8_t**)mFrame->extended_data, mFrame->nb_samples) < 0) + { + break; + } + mFrameData = &mDataBuf; + } + else + mFrameData = &mFrame->data[0]; + } while(got_frame == 0 || mFrame->nb_samples == 0); mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate; @@ -125,7 +156,7 @@ size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) size_t rem = std::min(length-dec, mFrameSize-mFramePos); /* Copy the data to the app's buffer and increment */ - memcpy(data, mFrame->data[0]+mFramePos, rem); + memcpy(data, mFrameData[0]+mFramePos, rem); data = (char*)data + rem; dec += rem; mFramePos += rem; @@ -135,19 +166,6 @@ size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) return dec; } -static AVSampleFormat ffmpegNonPlanarSampleFormat (AVSampleFormat format) -{ - switch (format) - { - case AV_SAMPLE_FMT_U8P: return AV_SAMPLE_FMT_U8; - case AV_SAMPLE_FMT_S16P: return AV_SAMPLE_FMT_S16; - case AV_SAMPLE_FMT_S32P: return AV_SAMPLE_FMT_S32; - case AV_SAMPLE_FMT_FLTP: return AV_SAMPLE_FMT_FLT; - case AV_SAMPLE_FMT_DBLP: return AV_SAMPLE_FMT_DBL; - default:return format; - } -} - void FFmpeg_Decoder::open(const std::string &fname) { close(); @@ -194,7 +212,7 @@ void FFmpeg_Decoder::open(const std::string &fname) if(!mStream) fail("No audio streams in "+fname); - (*mStream)->codec->request_sample_fmt = ffmpegNonPlanarSampleFormat ((*mStream)->codec->sample_fmt); + (*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt; AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); if(!codec) @@ -206,7 +224,7 @@ void FFmpeg_Decoder::open(const std::string &fname) if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) fail("Failed to open audio codec " + std::string(codec->long_name)); - mFrame = avcodec_alloc_frame(); + mFrame = av_frame_alloc(); } catch(std::exception&) { @@ -231,6 +249,8 @@ void FFmpeg_Decoder::close() av_free_packet(&mPacket); av_freep(&mFrame); + swr_free(&mSwr); + av_freep(&mDataBuf); if(mFormatCtx) { @@ -271,10 +291,18 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * *type = SampleType_Int16; else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT) *type = SampleType_Float32; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + *type = SampleType_UInt8; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + *type = SampleType_Int16; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + *type = SampleType_Float32; else fail(std::string("Unsupported sample format: ")+ av_get_sample_fmt_name((*mStream)->codec->sample_fmt)); + int64_t ch_layout = (*mStream)->codec->channel_layout; + if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO) @@ -289,9 +317,15 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * { /* Unknown channel layout. Try to guess. */ if((*mStream)->codec->channels == 1) + { *chans = ChannelConfig_Mono; + ch_layout = AV_CH_LAYOUT_MONO; + } else if((*mStream)->codec->channels == 2) + { *chans = ChannelConfig_Stereo; + ch_layout = AV_CH_LAYOUT_STEREO; + } else { std::stringstream sstr("Unsupported raw channel count: "); @@ -308,6 +342,31 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * } *samplerate = (*mStream)->codec->sample_rate; + + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + mOutputSampleFormat = AV_SAMPLE_FMT_FLT; + + if(mOutputSampleFormat != AV_SAMPLE_FMT_NONE) + { + mSwr = swr_alloc_set_opts(mSwr, // SwrContext + ch_layout, // output ch layout + mOutputSampleFormat, // output sample format + (*mStream)->codec->sample_rate, // output sample rate + ch_layout, // input ch layout + (*mStream)->codec->sample_fmt, // input sample format + (*mStream)->codec->sample_rate, // input sample rate + 0, // logging level offset + NULL); // log context + if(!mSwr) + fail(std::string("Couldn't allocate SwrContext")); + if(swr_init(mSwr) < 0) + fail(std::string("Couldn't initialize SwrContext")); + + } } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) @@ -326,7 +385,7 @@ void FFmpeg_Decoder::readAll(std::vector &output) { size_t got = mFrame->nb_samples * (*mStream)->codec->channels * av_get_bytes_per_sample((*mStream)->codec->sample_fmt); - const char *inbuf = reinterpret_cast(mFrame->data[0]); + const char *inbuf = reinterpret_cast(mFrameData[0]); output.insert(output.end(), inbuf, inbuf+got); } } @@ -355,6 +414,11 @@ FFmpeg_Decoder::FFmpeg_Decoder() , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) + , mSwr(0) + , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) + , mDataBuf(NULL) + , mFrameData(NULL) + , mDataBufLen(0) { memset(&mPacket, 0, sizeof(mPacket)); @@ -375,5 +439,3 @@ FFmpeg_Decoder::~FFmpeg_Decoder() } } - -#endif diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 8276b45c77..dc9937e814 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -18,6 +18,21 @@ extern "C" LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) #include #endif + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) +#define av_frame_alloc avcodec_alloc_frame +#endif + +// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: +// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d +// http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 +#ifdef HAVE_LIBSWRESAMPLE +#include +#else +#include +#include +#define SwrContext AVAudioResampleContext +#endif } #include @@ -40,6 +55,12 @@ namespace MWSound double mNextPts; + SwrContext *mSwr; + enum AVSampleFormat mOutputSampleFormat; + uint8_t *mDataBuf; + uint8_t **mFrameData; + int mDataBufLen; + bool getNextPacket(); Ogre::DataStreamPtr mDataStream; diff --git a/apps/openmw/mwsound/libavwrapper.cpp b/apps/openmw/mwsound/libavwrapper.cpp new file mode 100644 index 0000000000..a12f412d12 --- /dev/null +++ b/apps/openmw/mwsound/libavwrapper.cpp @@ -0,0 +1,109 @@ +#ifndef HAVE_LIBSWRESAMPLE +extern "C" +{ +#define __STDC_CONSTANT_MACROS +#include + +#include +#include +// From libavutil version 52.2.0 and onward the declaration of +// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to +// libavutil/channel_layout.h +#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) + #include +#endif +#include +#include + +/* FIXME: delete this file once libswresample is available on all platforms */ + +int swr_init(AVAudioResampleContext *avr) { return 1; } + +void swr_free(AVAudioResampleContext **avr) { avresample_free(avr); } + +int swr_convert( + AVAudioResampleContext *avr, + uint8_t** output, + int out_samples, + const uint8_t** input, + int in_samples) +{ + // FIXME: potential performance hit + int out_plane_size = 0; + int in_plane_size = 0; + return avresample_convert(avr, output, out_plane_size, out_samples, + (uint8_t **)input, in_plane_size, in_samples); +} + +AVAudioResampleContext * swr_alloc_set_opts( + AVAudioResampleContext *avr, + int64_t out_ch_layout, + AVSampleFormat out_fmt, + int out_rate, + int64_t in_ch_layout, + AVSampleFormat in_fmt, + int in_rate, + int o, + void* l) +{ + avr = avresample_alloc_context(); + if(!avr) + return 0; + + int res; + res = av_opt_set_int(avr, "out_channel_layout", out_ch_layout, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_ch_layout = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "out_sample_fmt", out_fmt, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_fmt = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "out_sample_rate", out_rate, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_rate = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_channel_layout", in_ch_layout, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_ch_layout = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_sample_fmt", in_fmt, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_fmt = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_sample_rate", in_rate, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_rate = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: internal_sample_fmt\n"); + return 0; + } + + + if(avresample_open(avr) < 0) + { + av_log(avr, AV_LOG_ERROR, "Error opening context\n"); + return 0; + } + else + return avr; +} + +} +#endif diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp new file mode 100644 index 0000000000..ebe3bf1ec5 --- /dev/null +++ b/apps/openmw/mwsound/loudness.cpp @@ -0,0 +1,53 @@ +#include "loudness.hpp" + +#include "soundmanagerimp.hpp" + +namespace MWSound +{ + + void analyzeLoudness(const std::vector &data, int sampleRate, ChannelConfig chans, + SampleType type, std::vector &out, float valuesPerSecond) + { + int samplesPerSegment = sampleRate / valuesPerSecond; + int numSamples = bytesToFrames(data.size(), chans, type); + int advance = framesToBytes(1, chans, type); + + out.reserve(numSamples/samplesPerSegment); + + int segment=0; + int sample=0; + while (segment < numSamples/samplesPerSegment) + { + float sum=0; + int samplesAdded = 0; + while (sample < numSamples && sample < (segment+1)*samplesPerSegment) + { + // get sample on a scale from -1 to 1 + float value = 0; + if (type == SampleType_UInt8) + value = ((char)(data[sample*advance]^0x80))/128.f; + else if (type == SampleType_Int16) + { + value = *reinterpret_cast(&data[sample*advance]); + value /= float(std::numeric_limits().max()); + } + else if (type == SampleType_Float32) + { + value = *reinterpret_cast(&data[sample*advance]); + value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. + } + + sum += value*value; + ++samplesAdded; + ++sample; + } + + float rms = 0; // root mean square + if (samplesAdded > 0) + rms = std::sqrt(sum / samplesAdded); + out.push_back(rms); + ++segment; + } + } + +} diff --git a/apps/openmw/mwsound/loudness.hpp b/apps/openmw/mwsound/loudness.hpp new file mode 100644 index 0000000000..df727bd0b2 --- /dev/null +++ b/apps/openmw/mwsound/loudness.hpp @@ -0,0 +1,20 @@ +#include "sound_decoder.hpp" + +namespace MWSound +{ + +/** + * Analyzes the energy (closely related to loudness) of a sound buffer. + * The buffer will be divided into segments according to \a valuesPerSecond, + * and for each segment a loudness value in the range of [0,1] will be computed. + * @param data the sound buffer to analyze, containing raw samples + * @param sampleRate the sample rate of the sound buffer + * @param chans channel layout of the buffer + * @param type sample type of the buffer + * @param out Will contain the output loudness values. + * @param valuesPerSecond How many loudness values per second of audio to compute. + */ +void analyzeLoudness (const std::vector& data, int sampleRate, ChannelConfig chans, SampleType type, + std::vector& out, float valuesPerSecond); + +} diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index b245b92414..3d2795ce10 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -11,11 +11,16 @@ #include "sound_decoder.hpp" #include "sound.hpp" #include "soundmanagerimp.hpp" +#include "loudness.hpp" #ifndef ALC_ALL_DEVICES_SPECIFIER #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif +namespace +{ + const int loudnessFPS = 20; // loudness values per second of audio +} namespace MWSound { @@ -734,7 +739,7 @@ void OpenAL_Output::deinit() mUnusedBuffers.clear(); while(!mBufferCache.empty()) { - alDeleteBuffers(1, &mBufferCache.begin()->second); + alDeleteBuffers(1, &mBufferCache.begin()->second.mALBuffer); mBufferCache.erase(mBufferCache.begin()); } @@ -750,14 +755,14 @@ void OpenAL_Output::deinit() } -ALuint OpenAL_Output::getBuffer(const std::string &fname) +const CachedSound& OpenAL_Output::getBuffer(const std::string &fname) { ALuint buf = 0; NameMap::iterator iditer = mBufferCache.find(fname); if(iditer != mBufferCache.end()) { - buf = iditer->second; + buf = iditer->second.mALBuffer; if(mBufferRefs[buf]++ == 0) { IDDq::iterator iter = std::find(mUnusedBuffers.begin(), @@ -766,7 +771,7 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) mUnusedBuffers.erase(iter); } - return buf; + return iditer->second; } throwALerror(); @@ -795,12 +800,16 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) decoder->readAll(data); decoder->close(); + CachedSound cached; + analyzeLoudness(data, srate, chans, type, cached.mLoudnessVector, loudnessFPS); + alGenBuffers(1, &buf); throwALerror(); alBufferData(buf, format, &data[0], data.size(), srate); - mBufferCache[fname] = buf; mBufferRefs[buf] = 1; + cached.mALBuffer = buf; + mBufferCache[fname] = cached; ALint bufsize = 0; alGetBufferi(buf, AL_SIZE, &bufsize); @@ -821,7 +830,7 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) NameMap::iterator nameiter = mBufferCache.begin(); while(nameiter != mBufferCache.end()) { - if(nameiter->second == oldbuf) + if(nameiter->second.mALBuffer == oldbuf) mBufferCache.erase(nameiter++); else ++nameiter; @@ -832,7 +841,8 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) alDeleteBuffers(1, &oldbuf); mBufferCacheMemSize -= bufsize; } - return buf; + + return mBufferCache[fname]; } void OpenAL_Output::bufferFinished(ALuint buf) @@ -856,7 +866,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, f try { - buf = getBuffer(fname); + buf = getBuffer(fname).mALBuffer; sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); } catch(std::exception&) @@ -883,7 +893,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, f } MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch, - float min, float max, int flags, float offset) + float min, float max, int flags, float offset, bool extractLoudness) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -895,8 +905,12 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre try { - buf = getBuffer(fname); + const CachedSound& cached = getBuffer(fname); + buf = cached.mALBuffer; + sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); + if (extractLoudness) + sound->setLoudnessVector(cached.mLoudnessVector, loudnessFPS); } catch(std::exception&) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 31edf73599..be12bfbecb 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -16,6 +16,12 @@ namespace MWSound class SoundManager; class Sound; + struct CachedSound + { + ALuint mALBuffer; + std::vector mLoudnessVector; + }; + class OpenAL_Output : public Sound_Output { ALCdevice *mDevice; @@ -25,7 +31,7 @@ namespace MWSound IDDq mFreeSources; IDDq mUnusedBuffers; - typedef std::map NameMap; + typedef std::map NameMap; NameMap mBufferCache; typedef std::map IDRefMap; @@ -36,7 +42,7 @@ namespace MWSound typedef std::vector SoundVec; SoundVec mActiveSounds; - ALuint getBuffer(const std::string &fname); + const CachedSound& getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); Environment mLastEnvironment; @@ -49,7 +55,7 @@ namespace MWSound virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset); /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float vol, float basevol, float pitch, float min, float max, int flags, float offset); + float vol, float basevol, float pitch, float min, float max, int flags, float offset, bool extractLoudness=false); virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); diff --git a/apps/openmw/mwsound/sound.cpp b/apps/openmw/mwsound/sound.cpp new file mode 100644 index 0000000000..b3105a82c2 --- /dev/null +++ b/apps/openmw/mwsound/sound.cpp @@ -0,0 +1,23 @@ +#include "sound.hpp" + +namespace MWSound +{ + + float Sound::getCurrentLoudness() + { + if (mLoudnessVector.empty()) + return 0.f; + int index = getTimeOffset() * mLoudnessFPS; + + index = std::max(0, std::min(index, int(mLoudnessVector.size()-1))); + + return mLoudnessVector[index]; + } + + void Sound::setLoudnessVector(const std::vector &loudnessVector, float loudnessFPS) + { + mLoudnessVector = loudnessVector; + mLoudnessFPS = loudnessFPS; + } + +} diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 670002a30f..1b5c001966 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -24,6 +24,9 @@ namespace MWSound int mFlags; float mFadeOutTime; + std::vector mLoudnessVector; + float mLoudnessFPS; + public: virtual void stop() = 0; virtual bool isPlaying() = 0; @@ -31,6 +34,12 @@ namespace MWSound void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } void setFadeout(float duration) { mFadeOutTime=duration; } + void setLoudnessVector(const std::vector& loudnessVector, float loudnessFPS); + + /// Get loudness at the current time position on a [0,1] scale. + /// Requires that loudnessVector was filled in by the user. + float getCurrentLoudness(); + MWBase::SoundManager::PlayType getPlayType() const { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } @@ -44,6 +53,7 @@ namespace MWSound , mMaxDistance(maxdist) , mFlags(flags) , mFadeOutTime(0) + , mLoudnessFPS(20) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 91e25db521..a9a999a5cb 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -28,7 +28,7 @@ namespace MWSound virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset) = 0; /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float vol, float basevol, float pitch, float min, float max, int flags, float offset) = 0; + float vol, float basevol, float pitch, float min, float max, int flags, float offset, bool extractLoudness=false) = 0; virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index ba7b4f3bab..812e49a64f 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -17,15 +17,10 @@ #include "openal_output.hpp" #define SOUND_OUT "OpenAL" -/* Set up the sound manager to use FFMPEG for input. - * The OPENMW_USE_x macros are set in CMakeLists.txt. -*/ -#ifdef OPENMW_USE_FFMPEG #include "ffmpeg_decoder.hpp" #ifndef SOUND_IN #define SOUND_IN "FFmpeg" #endif -#endif namespace MWSound @@ -256,7 +251,7 @@ namespace MWSound const Ogre::Vector3 objpos(pos.pos); MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0); + 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0, true); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -265,6 +260,21 @@ namespace MWSound } } + float SoundManager::getSaySoundLoudness(const MWWorld::Ptr &ptr) const + { + SoundMap::const_iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == ptr && snditer->second.second == "_say_sound") + break; + ++snditer; + } + if (snditer == mActiveSounds.end()) + return 0.f; + + return snditer->first->getCurrentLoudness(); + } + void SoundManager::say(const std::string& filename) { if(!mOutput->isInitialized()) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 380cfe2552..250cb0d51c 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -110,6 +110,11 @@ namespace MWSound virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()); ///< Stop an actor speaking + virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const; + ///< Check the currently playing say sound for this actor + /// and get an average loudness value (scale [0,1]) at the current time position. + /// If the actor is not saying anything, returns 0. + virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type); ///< Play a 2D audio track, using a custom decoder diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 6f569f078c..f8fcfceec6 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -14,9 +14,6 @@ #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - bool MWState::operator< (const Slot& left, const Slot& right) { return left.mTimeStampstartNewGame (bypass); - if (!bypass) MWBase::Environment::get().getWindowManager()->setNewGame (true); - else - MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + + MWBase::Environment::get().getWorld()->startNewGame (bypass); mState = State_Running; } @@ -161,7 +161,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot profile.mContentFiles = world.getContentFiles(); - profile.mPlayerName = player.getClass().getName (player); + profile.mPlayerName = player.get()->mBase->mName; profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); std::string classId = player.get()->mBase->mClass; @@ -353,6 +353,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl case ESM::REC_ACTC: case ESM::REC_PROJ: case ESM::REC_MPRJ: + case ESM::REC_ENAB: MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); break; @@ -365,6 +366,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl case ESM::REC_GMAP: case ESM::REC_KEYS: case ESM::REC_ASPL: + case ESM::REC_MARK: MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); break; @@ -377,7 +379,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl default: // ignore invalid records - /// \todo log error + std::cerr << "Ignoring unknown record: " << n.name << std::endl; reader.skipRecord(); } listener.increaseProgress(); @@ -403,6 +405,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + // Do not trigger erroneous cellChanged events MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } @@ -437,7 +441,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(); - std::string name = player.getClass().getName(player); + std::string name = player.get()->mBase->mName; return mCharacterManager.getCurrentCharacter (create, name); } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 4378e179d7..3368aa1c0e 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -1,4 +1,3 @@ - #include "actionteleport.hpp" #include "../mwbase/environment.hpp" @@ -16,15 +15,19 @@ namespace MWWorld void ActionTeleport::executeImp (const Ptr& actor) { - MWBase::World* world = MWBase::Environment::get().getWorld(); - //find any NPC that is following the actor and teleport him too std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); for(std::list::iterator it = followers.begin();it != followers.end();++it) { - executeImp(*it); + teleport(*it); } + teleport(actor); + } + + void ActionTeleport::teleport(const Ptr &actor) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); if(actor == world->getPlayerPtr()) { world->getPlayer().setTeleported(true); diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index a13cb61b21..9ca664de84 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -14,12 +14,16 @@ namespace MWWorld std::string mCellName; ESM::Position mPosition; + /// Teleports this actor and also teleports anyone following that actor. virtual void executeImp (const Ptr& actor); + /// Teleports only the given actor (internal use). + void teleport(const Ptr &actor); + public: ActionTeleport (const std::string& cellName, const ESM::Position& position); - ///< If cellName is empty, an exterior cell is asumed. + ///< If cellName is empty, an exterior cell is assumed. }; } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index f16d8e3d17..c0b3bb6afc 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -10,6 +10,12 @@ namespace MWWorld return mCellRef.mRefNum; } + void CellRef::unsetRefNum() + { + mCellRef.mRefNum.mContentFile = -1; + mCellRef.mRefNum.mIndex = 0; + } + std::string CellRef::getRefId() const { return mCellRef.mRefID; @@ -88,6 +94,16 @@ namespace MWWorld return mCellRef.mOwner; } + std::string CellRef::getGlobalVariable() const + { + return mCellRef.mGlobalVariable; + } + + int CellRef::getFactionRank() const + { + return mCellRef.mFactionRank; + } + void CellRef::setOwner(const std::string &owner) { if (owner != mCellRef.mOwner) diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 689671c025..d2e4fdf1ce 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -25,6 +25,9 @@ namespace MWWorld // Note: Currently unused for items in containers ESM::RefNum getRefNum() const; + // Set RefNum to its default state. + void unsetRefNum(); + // Id of object being referenced std::string getRefId() const; @@ -61,6 +64,11 @@ namespace MWWorld std::string getOwner() const; void setOwner(const std::string& owner); + // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed + // even if it has an Owner field. + // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. + std::string getGlobalVariable() const; + // ID of creature trapped in this soul gem std::string getSoul() const; void setSoul(const std::string& soul); @@ -70,6 +78,9 @@ namespace MWWorld std::string getFaction() const; void setFaction (const std::string& faction); + // PC faction rank required to use the item. Sometimes is -1, which means "any rank". + int getFactionRank() const; + // Lock level for doors and containers // Positive for a locked door. 0 for a door that was never locked. // For an unlocked door, it is set to -(previous locklevel) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 446519b877..ebe08dc44c 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -52,7 +52,7 @@ namespace MWWorld return false; } - void Class::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const + void Class::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const { throw std::runtime_error ("class does not represent an actor"); } @@ -303,10 +303,6 @@ namespace MWWorld { } - void Class::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const - { - } - std::string Class::getModel(const MWWorld::Ptr &ptr) const { return ""; @@ -322,7 +318,7 @@ namespace MWWorld return std::make_pair (1, ""); } - void Class::adjustPosition(const MWWorld::Ptr& ptr) const + void Class::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { } @@ -358,7 +354,7 @@ namespace MWWorld Class::copyToCell(const Ptr &ptr, CellStore &cell) const { Ptr newPtr = copyToCellImpl(ptr, cell); - + newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference return newPtr; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index c3f94d7f10..8ac39eaa9b 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -72,12 +72,6 @@ namespace MWWorld public: - /// NPC-stances. - enum Stance - { - Run, Sneak - }; - virtual ~Class(); const std::string& getTypeName() const { @@ -96,8 +90,9 @@ namespace MWWorld ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual void adjustPosition(const MWWorld::Ptr& ptr) 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 MWMechanics::CreatureStats& getCreatureStats (const Ptr& ptr) const; ///< Return creature stats or throw an exception, if class does not have creature stats @@ -236,7 +231,7 @@ namespace MWWorld /// /// (default implementation: ignore and return false) - virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const; + virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const; ///< Inform actor \a ptr that a skill use has succeeded. /// /// (default implementations: throws an exception) @@ -272,8 +267,6 @@ namespace MWWorld virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; - virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 7c7470bd12..8382311aa8 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -142,6 +142,34 @@ void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) remove(ptr, ptr.getRefData().getCount()-1, container); } +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) +{ + MWWorld::ContainerStoreIterator retval = end(); + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (item == *iter) + { + retval = iter; + break; + } + } + + if (retval == end()) + throw std::runtime_error("item is not from this container"); + + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, item)) + { + iter->getRefData().setCount(iter->getRefData().getCount() + item.getRefData().getCount()); + item.getRefData().setCount(0); + retval = iter; + break; + } + } + return retval; +} + bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { const MWWorld::Class& cls1 = ptr1.getClass(); @@ -228,6 +256,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr pos.pos[1] = 0; pos.pos[2] = 0; item.getCellRef().setPosition(pos); + item.getCellRef().unsetRefNum(); // destroy link to content file std::string script = item.getClass().getScript(item); if(script != "") @@ -673,7 +702,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) for (std::vector >::const_iterator iter (state.mLights.begin()); iter!=state.mLights.end(); ++iter) { - getState (lights, iter->first); + int slot = iter->second; + setSlot (getState (lights, iter->first), slot); } mLevelledItemMap = state.mLevelledItemMap; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 7c81bdb6e7..6d9d7a6bb1 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -134,6 +134,11 @@ namespace MWWorld void unstack (const Ptr& ptr, const Ptr& container); ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). + MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item); + ///< Attempt to re-stack an item in this container. + /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. + /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. + /// @return How many items with refID \a id are in this container? int count (const std::string& id); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 12831e7dc1..1b5d3d1e95 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -81,7 +81,13 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) mMagicEffects.load (esm); } else if (n.val == ESM::REC_SKIL) { mSkills.load (esm); - } else { + } + else if (n.val==ESM::REC_FILT || ESM::REC_DBGP) + { + // ignore project file only records + esm.skipRecord(); + } + else { std::stringstream error; error << "Unknown record: " << n.toString(); throw std::runtime_error(error.str()); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 2eb8aeb465..f515f1efba 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -73,15 +73,14 @@ MWWorld::InventoryStore::InventoryStore() } MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) -: ContainerStore (store) + : ContainerStore (store) , mSelectedEnchantItem(end()) + , mMagicEffects(store.mMagicEffects) + , mFirstAutoEquip(store.mFirstAutoEquip) + , mListener(store.mListener) + , mUpdatesEnabled(store.mUpdatesEnabled) + , mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes) { - mMagicEffects = store.mMagicEffects; - mFirstAutoEquip = store.mFirstAutoEquip; - mListener = store.mListener; - mUpdatesEnabled = store.mUpdatesEnabled; - - mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; copySlots (store); } @@ -198,11 +197,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) continue; } - // Don't auto-equip probes or lockpicks. NPCs can't use them (yet). And AiCombat would attempt to "attack" with them. - // NOTE: In the future AiCombat should handle equipping appropriate weapons - if (test.getTypeName() == typeid(ESM::Lockpick).name() || test.getTypeName() == typeid(ESM::Probe).name()) - continue; - // Only autoEquip if we are the original owner of the item. // This stops merchants from auto equipping anything you sell to them. // ...unless this is a companion, he should always equip items given to him. @@ -219,30 +213,27 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) for (std::vector::const_iterator iter2 (itemsSlots.first.begin()); iter2!=itemsSlots.first.end(); ++iter2) { - bool use = false; + if (*iter2 == Slot_CarriedRight) // Items in right hand are situational use, so don't equip them. + // Equipping weapons is handled by AiCombat. Anything else (lockpicks, probes) can't be used by NPCs anyway (yet) + continue; - if (slots_.at (*iter2)==end()) - use = true; // slot was empty before -> skip all further checks - else + if (slots_.at (*iter2)!=end()) { Ptr old = *slots_.at (*iter2); - if (!use) + // check skill + int oldSkill = old.getClass().getEquipmentSkill (old); + + bool use = false; + if (testSkill!=-1 && oldSkill==-1) + use = true; + else if (testSkill!=-1 && oldSkill!=-1 && testSkill!=oldSkill) { - // check skill - int oldSkill = - old.getClass().getEquipmentSkill (old); + if (actor.getClass().getSkill(actor, oldSkill) > actor.getClass().getSkill (actor, testSkill)) + continue; // rejected, because old item better matched the NPC's skills. - if (testSkill!=-1 && oldSkill==-1) + if (actor.getClass().getSkill(actor, oldSkill) < actor.getClass().getSkill (actor, testSkill)) use = true; - else if (testSkill!=-1 && oldSkill!=-1 && testSkill!=oldSkill) - { - if (actor.getClass().getSkill(actor, oldSkill) > actor.getClass().getSkill (actor, testSkill)) - continue; // rejected, because old item better matched the NPC's skills. - - if (actor.getClass().getSkill(actor, oldSkill) < actor.getClass().getSkill (actor, testSkill)) - use = true; - } } if (!use) @@ -253,8 +244,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { continue; } - - use = true; } } @@ -491,7 +480,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor } if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() - && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player") + && *mSelectedEnchantItem == item) { mSelectedEnchantItem = end(); } @@ -512,24 +501,17 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c // empty this slot mSlots[slot] = end(); - // restack the previously equipped item with other (non-equipped) items - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + if (it->getRefData().getCount()) { - if (stacks(*iter, *it)) - { - iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); - it->getRefData().setCount(0); - retval = iter; - break; - } - } + retval = restack(*it); - if (actor.getRefData().getHandle() == "player") - { - // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared - const std::string& script = it->getClass().getScript(*it); - if (script != "") - (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); + if (actor.getRefData().getHandle() == "player") + { + // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared + const std::string& script = it->getClass().getScript(*it); + if (script != "") + (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); + } if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) { @@ -633,14 +615,21 @@ void MWWorld::InventoryStore::rechargeItems(float duration) static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( "fMagicItemRechargePerSecond")->getFloat(); - it->first->getCellRef().setEnchantmentCharge(std::min (it->first->getCellRef().getEnchantmentCharge() + fMagicItemRechargePerSecond * duration, - it->second)); + if (it->first->getCellRef().getEnchantmentCharge() <= it->second) + { + it->first->getCellRef().setEnchantmentCharge(std::min (it->first->getCellRef().getEnchantmentCharge() + fMagicItemRechargePerSecond * duration, + it->second)); + + // attempt to restack when fully recharged + if (it->first->getCellRef().getEnchantmentCharge() == it->second) + it->first = restack(*it->first); + } } } void MWWorld::InventoryStore::purgeEffect(short effectId) { - mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude); + mMagicEffects.remove(MWMechanics::EffectKey(effectId)); } void MWWorld::InventoryStore::clear() diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 95b956907c..41caae4e52 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -141,7 +141,7 @@ namespace MWWorld /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); - ///< \note \a iterator can be an end-iterator + ///< \warning \a iterator can not be an end()-iterator, use unequip function instead void setSelectedEnchantItem(const ContainerStoreIterator& iterator); ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 0921d3a1b5..dd313632b8 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -30,8 +30,18 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) { if (const ESM::Script* script = MWBase::Environment::get().getWorld()->getStore().get().search (scriptId)) { - mData.setLocals (*script); - mData.getLocals().read (state.mLocals, scriptId); + try + { + mData.setLocals (*script); + mData.getLocals().read (state.mLocals, scriptId); + } + catch (const std::exception& exception) + { + std::cerr + << "failed to load state for local script " << scriptId + << " because an exception has been thrown: " << exception.what() + << std::endl; + } } } } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 8a671cea8c..f3a6471249 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -93,9 +93,18 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) { if (const ESM::Script *script = mStore.get().find (scriptName)) { - ptr.getRefData().setLocals (*script); + try + { + ptr.getRefData().setLocals (*script); - mScripts.push_back (std::make_pair (scriptName, ptr)); + mScripts.push_back (std::make_pair (scriptName, ptr)); + } + catch (const std::exception& exception) + { + std::cerr + << "failed to add local script " << scriptName + << " because an exception has been thrown: " << exception.what() << std::endl; + } } } diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index b77257e47e..0becd75242 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -28,15 +28,13 @@ namespace MWWorld cellRef.mRefNum.mContentFile = -1; cellRef.mRefID = name; cellRef.mScale = 1; - cellRef.mFactIndex = 0; + cellRef.mFactionRank = 0; cellRef.mCharge = -1; cellRef.mGoldValue = 1; cellRef.mEnchantmentCharge = -1; cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; - cellRef.mFltv = 0; - cellRef.mNam0 = 0; LiveCellRef ref(cellRef, base); diff --git a/apps/openmw/mwworld/omwloader.cpp b/apps/openmw/mwworld/omwloader.cpp deleted file mode 100644 index 8562a4fe04..0000000000 --- a/apps/openmw/mwworld/omwloader.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "omwloader.hpp" - -namespace MWWorld -{ - -OmwLoader::OmwLoader(Loading::Listener& listener) - : ContentLoader(listener) -{ -} - -void OmwLoader::load(const boost::filesystem::path& filepath, int& index) -{ - ContentLoader::load(filepath.filename(), index); -} - -} /* namespace MWWorld */ - diff --git a/apps/openmw/mwworld/omwloader.hpp b/apps/openmw/mwworld/omwloader.hpp deleted file mode 100644 index cb9faa4303..0000000000 --- a/apps/openmw/mwworld/omwloader.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef OMWLOADER_HPP -#define OMWLOADER_HPP - -#include "contentloader.hpp" - -namespace MWWorld -{ - -/** - * @brief Placeholder for real OpenMW content loader - */ -struct OmwLoader : public ContentLoader -{ - OmwLoader(Loading::Listener& listener); - - void load(const boost::filesystem::path& filepath, int& index); -}; - -} /* namespace MWWorld */ - -#endif /* OMWLOADER_HPP */ diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index daad5b0e69..31d52e39db 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -23,6 +23,7 @@ #include "../mwbase/environment.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/movement.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -241,7 +242,9 @@ namespace MWWorld } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, - bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine) + bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine + , std::map& collisionTracker + , std::map& standingCollisionTracker) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -294,21 +297,24 @@ namespace MWWorld else { velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; + // not in water nor can fly, so need to deal with gravity if(!physicActor->getOnGround()) // if current OnGround status is false, must be falling or jumping { - // If falling, add part of the incoming velocity with the current inertia - // TODO: but we could be jumping up? - velocity = velocity * time + physicActor->getInertialForce(); - - // avoid getting infinite inertia in air + // If falling or jumping up, add part of the incoming velocity with the current inertia, + // but don't allow increasing inertia beyond actor's speed (except on the initial jump impulse) float actorSpeed = ptr.getClass().getSpeed(ptr); - float speedXY = Ogre::Vector2(velocity.x, velocity.y).length(); - if (speedXY > actorSpeed) + float cap = std::max(actorSpeed, Ogre::Vector2(physicActor->getInertialForce().x, physicActor->getInertialForce().y).length()); + Ogre::Vector3 newVelocity = velocity + physicActor->getInertialForce(); + if (Ogre::Vector2(newVelocity.x, newVelocity.y).squaredLength() > cap*cap) { - velocity.x *= actorSpeed / speedXY; - velocity.y *= actorSpeed / speedXY; + velocity = newVelocity; + float speedXY = Ogre::Vector2(velocity.x, velocity.y).length(); + velocity.x *= cap / speedXY; + velocity.y *= cap / speedXY; } + else + velocity = newVelocity; } inertia = velocity; // NOTE: velocity is for z axis only in this code block @@ -318,12 +324,18 @@ namespace MWWorld tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); // check if down 2 possible if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) { + const btCollisionObject* standingOn = tracer.mHitObject; + if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) + { + standingCollisionTracker[ptr.getRefData().getHandle()] = body->mName; + } isOnGround = true; // if we're on the ground, don't try to fall any more velocity.z = std::max(0.0f, velocity.z); } } } + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; // Now that we have the effective movement vector, apply wind forces to it if (MWBase::Environment::get().getWorld()->isInStorm()) @@ -376,6 +388,14 @@ namespace MWWorld remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? break; } + else + { + const btCollisionObject* standingOn = tracer.mHitObject; + if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) + { + collisionTracker[ptr.getRefData().getHandle()] = body->mName; + } + } } else { @@ -597,9 +617,9 @@ namespace MWWorld } } - std::vector PhysicsSystem::getCollisions(const Ptr &ptr) + std::vector PhysicsSystem::getCollisions(const Ptr &ptr, int collisionGroup, int collisionMask) { - return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName()); + return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName(), collisionGroup, collisionMask); } Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) @@ -704,7 +724,10 @@ namespace MWWorld } if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) - act->setScale(node->getScale().x); + { + // NOTE: Ignoring Npc::adjustScale (race height) on purpose. This is a bug in MW and must be replicated for compatibility reasons + act->setScale(ptr.getCellRef().getScale()); + } } bool PhysicsSystem::toggleCollisionMode() @@ -769,8 +792,19 @@ namespace MWWorld mMovementQueue.push_back(std::make_pair(ptr, movement)); } + void PhysicsSystem::clearQueuedMovement() + { + mMovementQueue.clear(); + mCollisions.clear(); + mStandingCollisions.clear(); + } + const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) { + // Collision events are only tracked for a single frame, so reset first + mCollisions.clear(); + mStandingCollisions.clear(); + mMovementResults.clear(); mTimeAccum += dt; @@ -790,7 +824,7 @@ namespace MWWorld const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); bool waterCollision = false; - if (effects.get(ESM::MagicEffect::WaterWalking).mMagnitude + if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() && cell->hasWater() && !world->isUnderwater(iter->first.getCell(), Ogre::Vector3(iter->first.getRefData().getPosition().pos))) @@ -806,11 +840,11 @@ namespace MWWorld 0xff, OEngine::Physic::CollisionType_Actor); // 100 points of slowfall reduce gravity by 90% (this is just a guess) - float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f); + float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).getMagnitude() / 100.f) * 0.9f), 0.9f); Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, world->isFlying(iter->first), - waterlevel, slowFall, mEngine); + waterlevel, slowFall, mEngine, mCollisions, mStandingCollisions); if (waterCollision) mEngine->mDynamicsWorld->removeCollisionObject(&object); @@ -837,4 +871,57 @@ namespace MWWorld mEngine->stepSimulation(dt); } + + bool PhysicsSystem::isActorStandingOn(const Ptr &actor, const Ptr &object) const + { + const std::string& actorHandle = actor.getRefData().getHandle(); + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mStandingCollisions.begin(); + it != mStandingCollisions.end(); ++it) + { + if (it->first == actorHandle && it->second == objectHandle) + return true; + } + return false; + } + + void PhysicsSystem::getActorsStandingOn(const Ptr &object, std::vector &out) const + { + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mStandingCollisions.begin(); + it != mStandingCollisions.end(); ++it) + { + if (it->second == objectHandle) + out.push_back(it->first); + } + } + + bool PhysicsSystem::isActorCollidingWith(const Ptr &actor, const Ptr &object) const + { + const std::string& actorHandle = actor.getRefData().getHandle(); + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mCollisions.begin(); + it != mCollisions.end(); ++it) + { + if (it->first == actorHandle && it->second == objectHandle) + return true; + } + return false; + } + + void PhysicsSystem::getActorsCollidingWith(const Ptr &object, std::vector &out) const + { + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mCollisions.begin(); + it != mCollisions.end(); ++it) + { + if (it->second == objectHandle) + out.push_back(it->first); + } + } + } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 8e0be95d57..e66c179b02 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -55,7 +55,7 @@ namespace MWWorld void stepSimulation(float dt); - std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with + std::vector getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); std::pair getFacedHandle(float queryDistance); @@ -85,14 +85,40 @@ namespace MWWorld /// be overwritten. Valid until the next call to applyQueuedMovement. void queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &velocity); + /// Apply all queued movements, then clear the list. const PtrVelocityList& applyQueuedMovement(float dt); + /// Clear the queued movements list without applying. + void clearQueuedMovement(); + + /// Return true if \a actor has been standing on \a object in this frame + /// This will trigger whenever the object is directly below the actor. + /// It doesn't matter if the actor is stationary or moving. + bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; + + /// Get the handle of all actors standing on \a object in this frame. + void getActorsStandingOn(const MWWorld::Ptr& object, std::vector& out) const; + + /// Return true if \a actor has collided with \a object in this frame. + /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. + bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; + + /// Get the handle of all actors colliding with \a object in this frame. + void getActorsCollidingWith(const MWWorld::Ptr& object, std::vector& out) const; + private: OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; std::map handleToMesh; + // Tracks all movement collisions happening during a single frame. + // This will detect e.g. running against a vertical wall. It will not detect climbing up stairs, + // stepping up small objects, etc. + std::map mCollisions; + + std::map mStandingCollisions; + PtrVelocityList mMovementQueue; PtrVelocityList mMovementResults; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 12908ca9da..b3996f7566 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -244,6 +244,8 @@ namespace MWWorld mPlayer.load (player.mObject); + getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear(); + MWBase::World& world = *MWBase::Environment::get().getWorld(); try @@ -253,6 +255,10 @@ namespace MWWorld catch (...) { // 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); + pos.pos[2] = 0; + mPlayer.mData.setPosition(pos); mCellStore = world.getExterior(0,0); } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index fb376bb930..b04f67b001 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -20,6 +20,8 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwrender/effectmanager.hpp" +#include "../mwrender/animation.hpp" +#include "../mwrender/renderconst.hpp" #include "../mwsound/sound.hpp" @@ -41,6 +43,9 @@ namespace MWWorld if(state.mObject->mControllers[i].getSource().isNull()) state.mObject->mControllers[i].setSource(Ogre::SharedPtr (new MWRender::EffectAnimationTime())); } + + MWRender::Animation::setRenderProperties(state.mObject, MWRender::RV_Misc, + MWRender::RQG_Main, MWRender::RQG_Alpha, 0.f, false, NULL); } void ProjectileManager::update(NifOgre::ObjectScenePtr object, float duration) @@ -247,29 +252,23 @@ namespace MWWorld if (obstacle == caster) continue; - if (obstacle.isEmpty()) + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty()) { - // Terrain + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; } - else if (obstacle.getClass().isActor()) - { - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty()) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) - bow = *invIt; - } + if (caster.isEmpty()) + caster = obstacle; - if (caster.isEmpty()) - caster = obstacle; + MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); - MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); - } hit = true; } if (hit) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b2faa1a010..a316f0bb23 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -4,8 +4,6 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" /// FIXME #include "../mwbase/soundmanager.hpp" @@ -86,7 +84,7 @@ namespace updateObjectLocalRotation(ptr, mPhysics, mRendering); MWBase::Environment::get().getWorld()->scaleObject (ptr, ptr.getCellRef().getScale()); - ptr.getClass().adjustPosition (ptr); + ptr.getClass().adjustPosition (ptr, false); } catch (const std::exception& e) { @@ -232,7 +230,7 @@ namespace MWWorld float z = Ogre::Radian(pos.rot[2]).valueDegrees(); world->rotateObject(player, x, y, z); - player.getClass().adjustPosition(player); + player.getClass().adjustPosition(player, true); } MWBase::MechanicsManager *mechMgr = @@ -261,8 +259,6 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { - Nif::NIFFile::CacheLock cachelock; - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -272,26 +268,6 @@ namespace MWWorld loadingListener->setLabel(loadingExteriorText); CellStoreCollection::iterator active = mActiveCells.begin(); - - // get the number of cells to unload - int numUnload = 0; - while (active!=mActiveCells.end()) - { - if ((*active)->getCell()->isExterior()) - { - if (std::abs (X-(*active)->getCell()->getGridX())<=1 && - std::abs (Y-(*active)->getCell()->getGridY())<=1) - { - // keep cells within the new 3x3 grid - ++active; - continue; - } - } - ++active; - ++numUnload; - } - - active = mActiveCells.begin(); while (active!=mActiveCells.end()) { if ((*active)->getCell()->isExterior()) @@ -405,22 +381,20 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { - Nif::NIFFile::CacheLock lock; - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); - - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(loadingListener); - - mRendering.enableTerrain(false); - - std::string loadingInteriorText = "#{sLoadingMessage2}"; - loadingListener->setLabel(loadingInteriorText); - CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); if(!loadcell) loadcell = *mCurrentCell != *cell; + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + std::string loadingInteriorText = "#{sLoadingMessage2}"; + loadingListener->setLabel(loadingInteriorText); + Loading::ScopedLoad load(loadingListener); + + mRendering.enableTerrain(false); + if(!loadcell) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -431,27 +405,16 @@ namespace MWWorld float z = Ogre::Radian(position.rot[2]).valueDegrees(); world->rotateObject(world->getPlayerPtr(), x, y, z); - world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr()); - world->getFader()->fadeIn(0.5f); + world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); return; } std::cout << "Changing to interior\n"; - // remove active - CellStoreCollection::iterator active = mActiveCells.begin(); - - // count number of cells to unload - int numUnload = 0; - while (active!=mActiveCells.end()) - { - ++active; - ++numUnload; - } - // unload int current = 0; - active = mActiveCells.begin(); + CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { unloadCell (active++); @@ -480,7 +443,7 @@ namespace MWWorld MWBase::Environment::get().getWorld()->adjustSky(); mCellChanged = true; - MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.5); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); } void Scene::changeToExteriorCell (const ESM::Position& position) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 7ef06e841a..caf4083fe0 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -58,6 +58,7 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // merge new cell into old cell // push the new references on the list of references to manage (saveContext = true) oldcell->mData = cell.mData; + oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) oldcell->loadCell(esm, true); } else { @@ -74,6 +75,7 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) if (oldcell) { // merge new cell into old cell oldcell->mData = cell.mData; + oldcell->mName = cell.mName; oldcell->loadCell(esm, false); // handle moved ref (MVRF) subrecords diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index fb45cb0343..613fd712fd 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -73,11 +73,6 @@ Rain Height Max=700 ? Rain Threshold=0.6 ? Max Raindrops=650 ? */ - - size_t offset = weather.mCloudTexture.find(".tga"); - if (offset != std::string::npos) - weather.mCloudTexture.replace(offset, weather.mCloudTexture.length() - offset, ".dds"); - weather.mIsStorm = (name == "ashstorm" || name == "blight"); mWeatherSettings[name] = weather; @@ -707,9 +702,13 @@ void WeatherManager::changeWeather(const std::string& region, const unsigned int mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather; - std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell()->mRegion; - if (Misc::StringUtils::ciEqual(region, playerRegion)) - setWeather(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) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 93316f8765..c49f1f4835 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -51,7 +51,6 @@ #include "contentloader.hpp" #include "esmloader.hpp" -#include "omwloader.hpp" using namespace Ogre; @@ -148,8 +147,8 @@ namespace MWWorld mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (activationDistanceOverride), mFallback(fallbackMap), mTeleportEnabled(true), mLevitationEnabled(true), - mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), - mGoToJail(false), + mGodMode(false), mContentFiles (contentFiles), + mGoToJail(false), mDaysInPrison(0), mStartCell (startCell), mStartupScript(startupScript) { mPhysics = new PhysicsSystem(renderer); @@ -163,19 +162,18 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - // NOTE: We might need to reserve one more for the running game / save. mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); GameContentLoader gameContentLoader(*listener); EsmLoader esmLoader(mStore, mEsm, encoder, *listener); - OmwLoader omwLoader(*listener); gameContentLoader.addLoader(".esm", &esmLoader); gameContentLoader.addLoader(".esp", &esmLoader); - gameContentLoader.addLoader(".omwgame", &omwLoader); - gameContentLoader.addLoader(".omwaddon", &omwLoader); + gameContentLoader.addLoader(".omwgame", &esmLoader); + gameContentLoader.addLoader(".omwaddon", &esmLoader); + gameContentLoader.addLoader(".project", &esmLoader); loadContentFiles(fileCollections, contentFiles, gameContentLoader); @@ -211,9 +209,9 @@ namespace MWWorld // set new game mark mGlobalVariables["chargenstate"].setInteger (1); mGlobalVariables["pcrace"].setInteger (3); - - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); } + else + mGlobalVariables["chargenstate"].setInteger (-1); if (bypass && !mStartCell.empty()) { @@ -259,7 +257,8 @@ namespace MWWorld mWeatherManager = 0; mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); + if (!mStartupScript.empty()) + MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); } void World::clear() @@ -290,7 +289,7 @@ namespace MWWorld mGodMode = false; mSky = true; mTeleportEnabled = true; - mFacedDistance = FLT_MAX; + mLevitationEnabled = true; mGlobalVariables.fill (mStore); } @@ -304,7 +303,8 @@ namespace MWWorld +mProjectileManager->countSavedGameRecords() +1 // player record +1 // weather record - +1; // actorId counter + +1 // actorId counter + +1; // levitation/teleport enabled state } void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -320,66 +320,111 @@ namespace MWWorld MWMechanics::CreatureStats::writeActorIdCounter(writer); progress.increaseProgress(); + mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that + // references to custom made records will be recognized mCells.write (writer, progress); - mStore.write (writer, progress); mGlobalVariables.write (writer, progress); mPlayer->write (writer, progress); mWeatherManager->write (writer, progress); mProjectileManager->write (writer, progress); + + writer.startRecord(ESM::REC_ENAB); + writer.writeHNT("TELE", mTeleportEnabled); + writer.writeHNT("LEVT", mLevitationEnabled); + writer.endRecord(ESM::REC_ENAB); + progress.increaseProgress(); } void World::readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap) { - if (type == ESM::REC_ACTC) + switch (type) { - MWMechanics::CreatureStats::readActorIdCounter(reader); - return; - } - - if (!mStore.readRecord (reader, type) && - !mGlobalVariables.readRecord (reader, type) && - !mPlayer->readRecord (reader, type) && - !mWeatherManager->readRecord (reader, type) && - !mCells.readRecord (reader, type, contentFileMap) && - !mProjectileManager->readRecord (reader, type)) - { - throw std::runtime_error ("unknown record in saved game"); + case ESM::REC_ACTC: + MWMechanics::CreatureStats::readActorIdCounter(reader); + return; + case ESM::REC_ENAB: + reader.getHNT(mTeleportEnabled, "TELE"); + reader.getHNT(mLevitationEnabled, "LEVT"); + return; + default: + if (!mStore.readRecord (reader, type) && + !mGlobalVariables.readRecord (reader, type) && + !mPlayer->readRecord (reader, type) && + !mWeatherManager->readRecord (reader, type) && + !mCells.readRecord (reader, type, contentFileMap) && + !mProjectileManager->readRecord (reader, type)) + { + throw std::runtime_error ("unknown record in saved game"); + } + break; } } void World::ensureNeededRecords() { - if (!mStore.get().search("sCompanionShare")) + std::map gmst; + // Companion (tribunal) + gmst["sCompanionShare"] = ESM::Variant("Companion Share"); + gmst["sCompanionWarningMessage"] = ESM::Variant("Warning message"); + gmst["sCompanionWarningButtonOne"] = ESM::Variant("Button 1"); + gmst["sCompanionWarningButtonTwo"] = ESM::Variant("Button 2"); + gmst["sCompanionShare"] = ESM::Variant("Companion Share"); + gmst["sProfitValue"] = ESM::Variant("Profit Value"); + gmst["sTeleportDisabled"] = ESM::Variant("Teleport disabled"); + gmst["sLevitateDisabled"] = ESM::Variant("Levitate disabled"); + + // Missing in unpatched MW 1.0 + gmst["sDifficulty"] = ESM::Variant("Difficulty"); + gmst["fDifficultyMult"] = ESM::Variant(5.f); + gmst["sAuto_Run"] = ESM::Variant("Auto Run"); + gmst["sServiceRefusal"] = ESM::Variant("Service Refusal"); + gmst["sNeedOneSkill"] = ESM::Variant("Need one skill"); + gmst["sNeedTwoSkills"] = ESM::Variant("Need two skills"); + gmst["sEasy"] = ESM::Variant("Easy"); + gmst["sHard"] = ESM::Variant("Hard"); + gmst["sDeleteNote"] = ESM::Variant("Delete Note"); + gmst["sEditNote"] = ESM::Variant("Edit Note"); + gmst["sAdmireSuccess"] = ESM::Variant("Admire Success"); + gmst["sAdmireFail"] = ESM::Variant("Admire Fail"); + gmst["sIntimidateSuccess"] = ESM::Variant("Intimidate Success"); + gmst["sIntimidateFail"] = ESM::Variant("Intimidate Fail"); + gmst["sTauntSuccess"] = ESM::Variant("Taunt Success"); + gmst["sTauntFail"] = ESM::Variant("Taunt Fail"); + gmst["sBribeSuccess"] = ESM::Variant("Bribe Success"); + gmst["sBribeFail"] = ESM::Variant("Bribe Fail"); + + // Werewolf (BM) + gmst["fWereWolfRunMult"] = ESM::Variant(1.f); + gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(1.f); + + + std::map globals; + // vanilla Morrowind does not define dayspassed. + globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :( + globals["werewolfclawmult"] = ESM::Variant(25.f); + globals["pcknownwerewolf"] = ESM::Variant(0); + + for (std::map::iterator it = gmst.begin(); it != gmst.end(); ++it) { - ESM::GameSetting sCompanionShare; - sCompanionShare.mId = "sCompanionShare"; - ESM::Variant value; - value.setType(ESM::VT_String); - value.setString("Companion Share"); - sCompanionShare.mValue = value; - mStore.insertStatic(sCompanionShare); + if (!mStore.get().search(it->first)) + { + ESM::GameSetting setting; + setting.mId = it->first; + setting.mValue = it->second; + mStore.insertStatic(setting); + } } - if (!mStore.get().search("dayspassed")) + + for (std::map::iterator it = globals.begin(); it != globals.end(); ++it) { - // vanilla Morrowind does not define dayspassed. - ESM::Global dayspassed; - dayspassed.mId = "dayspassed"; - ESM::Variant value; - value.setType(ESM::VT_Long); - value.setInteger(1); // but the addons start counting at 1 :( - dayspassed.mValue = value; - mStore.insertStatic(dayspassed); - } - if (!mStore.get().search("fWereWolfRunMult")) - { - ESM::GameSetting fWereWolfRunMult; - fWereWolfRunMult.mId = "fWereWolfRunMult"; - ESM::Variant value; - value.setType(ESM::VT_Float); - value.setFloat(1.f); - fWereWolfRunMult.mValue = value; - mStore.insertStatic(fWereWolfRunMult); + if (!mStore.get().search(it->first)) + { + ESM::Global setting; + setting.mId = it->first; + setting.mValue = it->second; + mStore.insertStatic(setting); + } } } @@ -546,13 +591,29 @@ namespace MWWorld std::string lowerCaseName = Misc::StringUtils::lowerCase(name); - // active cells + for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); + iter!=mWorldScene->getActiveCells().end(); ++iter) + { + // TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in) + CellStore* cellstore = *iter; + Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false); + + if (!ptr.isEmpty()) + return ptr; + } + + if (!activeOnly) + { + ret = mCells.getPtr (lowerCaseName); + if (!ret.isEmpty()) + return ret; + } + for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); iter!=mWorldScene->getActiveCells().end(); ++iter) { CellStore* cellstore = *iter; - Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, true); - + Ptr ptr = cellstore->searchInContainer(lowerCaseName); if (!ptr.isEmpty()) return ptr; } @@ -560,14 +621,7 @@ namespace MWWorld Ptr ptr = mPlayer->getPlayer().getClass() .getContainerStore(mPlayer->getPlayer()).search(lowerCaseName); - if (!ptr.isEmpty()) - return ptr; - - if (!activeOnly) - { - ret = mCells.getPtr (lowerCaseName); - } - return ret; + return ptr; } Ptr World::getPtr (const std::string& name, bool activeOnly) @@ -840,6 +894,8 @@ namespace MWWorld void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + mPhysics->clearQueuedMovement(); + if (mCurrentWorldSpace != cellName) { // changed worldspace @@ -855,6 +911,8 @@ namespace MWWorld void World::changeToExteriorCell (const ESM::Position& position) { + mPhysics->clearQueuedMovement(); + if (mCurrentWorldSpace != "sys::default") // FIXME { // changed worldspace @@ -908,9 +966,27 @@ namespace MWWorld MWWorld::Ptr World::getFacedObject() { - if (mFacedHandle.empty()) + std::string facedHandle; + + if (MWBase::Environment::get().getWindowManager()->isGuiMode() && + MWBase::Environment::get().getWindowManager()->isConsoleMode()) + getFacedHandle(facedHandle, getMaxActivationDistance() * 50, false); + else + { + float telekinesisRangeBonus = + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() + .get(ESM::MagicEffect::Telekinesis).getMagnitude(); + telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + + float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; + + getFacedHandle(facedHandle, activationDistance); + } + + if (facedHandle.empty()) return MWWorld::Ptr(); - return searchPtrViaHandle(mFacedHandle); + + return getPtrViaHandle(facedHandle); } std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) @@ -1123,6 +1199,9 @@ namespace MWWorld ptr.getRefData().setPosition(pos); + if(ptr.getRefData().getBaseNode() == 0) + return; + if (ptr.getClass().isActor()) mWorldScene->updateObjectRotation(ptr); else @@ -1131,24 +1210,24 @@ namespace MWWorld void World::localRotateObject (const Ptr& ptr, float x, float y, float z) { + LocalRotation rot = ptr.getRefData().getLocalRotation(); + rot.rot[0]=Ogre::Degree(x).valueRadians(); + rot.rot[1]=Ogre::Degree(y).valueRadians(); + rot.rot[2]=Ogre::Degree(z).valueRadians(); + + wrap(rot.rot[0]); + wrap(rot.rot[1]); + wrap(rot.rot[2]); + + ptr.getRefData().setLocalRotation(rot); + if (ptr.getRefData().getBaseNode() != 0) { - LocalRotation rot = ptr.getRefData().getLocalRotation(); - rot.rot[0]=Ogre::Degree(x).valueRadians(); - rot.rot[1]=Ogre::Degree(y).valueRadians(); - rot.rot[2]=Ogre::Degree(z).valueRadians(); - - wrap(rot.rot[0]); - wrap(rot.rot[1]); - wrap(rot.rot[2]); - - ptr.getRefData().setLocalRotation(rot); - mWorldScene->updateObjectLocalRotation(ptr); } } - void World::adjustPosition(const Ptr &ptr) + void World::adjustPosition(const Ptr &ptr, bool force) { ESM::Position pos (ptr.getRefData().getPosition()); @@ -1167,7 +1246,7 @@ namespace MWWorld ptr.getRefData().setPosition(pos); - if (!isFlying(ptr)) + if (force || !isFlying(ptr)) { Ogre::Vector3 traced = mPhysics->traceDown(ptr, 200); if (traced.z < pos.pos[2]) @@ -1236,7 +1315,7 @@ namespace MWWorld const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); PtrVelocityList::const_iterator player(results.end()); - for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++) + for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter) { if(iter->first.getRefData().getHandle() == "player") { @@ -1281,7 +1360,8 @@ namespace MWWorld bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; /// \todo should use convexSweepTest here - std::vector collisions = mPhysics->getCollisions(it->first); + std::vector collisions = mPhysics->getCollisions(it->first, OEngine::Physic::CollisionType_Actor + , OEngine::Physic::CollisionType_Actor); for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { MWWorld::Ptr ptr = getPtrViaHandle(*cit); @@ -1297,7 +1377,6 @@ namespace MWWorld // we need to undo the rotation localRotateObject(it->first, 0, 0, oldRot); reached = false; - //break; //Removed in case multiple actors are touching } } @@ -1455,65 +1534,33 @@ namespace MWWorld Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); } - - updateFacedHandle (); } - void World::updateFacedHandle () + void World::getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer) { - float telekinesisRangeBonus = - mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() - .get(ESM::MagicEffect::Telekinesis).mMagnitude; - telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + maxDistance += mRendering->getCameraDistance(); - float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; - activationDistance += mRendering->getCameraDistance(); - - // send new query - // figure out which object we want to test against std::vector < std::pair < float, std::string > > results; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedHandles(x, y, activationDistance); - if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) - results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50); + results = mPhysics->getFacedHandles(x, y, maxDistance); } else { - results = mPhysics->getFacedHandles(activationDistance); + results = mPhysics->getFacedHandles(maxDistance); } - // ignore the player and other things we're not interested in - std::vector < std::pair < float, std::string > >::iterator it = results.begin(); - while (it != results.end()) - { - if ((*it).second.find("HeightField") != std::string::npos) // Don't attempt to getPtrViaHandle on terrain - { - ++it; - continue; - } - - if (getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) - { - it = results.erase(it); - } - else - ++it; - } + if (ignorePlayer && + !results.empty() && results.front().second == "player") + results.erase(results.begin()); if (results.empty() || results.front().second.find("HeightField") != std::string::npos) // Blocked by terrain - { - mFacedHandle = ""; - mFacedDistance = FLT_MAX; - } + facedHandle = ""; else - { - mFacedHandle = results.front().second; - mFacedDistance = results.front().first; - } + facedHandle = results.front().second; } bool World::isCellExterior() const @@ -1554,11 +1601,6 @@ namespace MWWorld mWeatherManager->modRegion(regionid, chances); } - OEngine::Render::Fader* World::getFader() - { - return mRendering->getFader(); - } - Ogre::Vector2 World::getNorthVector (CellStore* cell) { MWWorld::CellRefList& statics = cell->get(); @@ -1596,9 +1638,14 @@ namespace MWWorld } } - void World::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) + void World::worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) { - mRendering->getInteriorMapPosition(position, nX, nY, x, y); + mRendering->worldToInteriorMapPosition(position, nX, nY, x, y); + } + + Ogre::Vector2 World::interiorMapToWorldPosition(float nX, float nY, int x, int y) + { + return mRendering->interiorMapToWorldPosition(nX, nY, x, y); } bool World::isPositionExplored (float nX, float nY, int x, int y, bool interior) @@ -1717,6 +1764,7 @@ namespace MWWorld localRotation.rot[2] = 0; dropped.getRefData().setLocalRotation(localRotation); dropped.getCellRef().setPosition(pos); + dropped.getCellRef().unsetRefNum(); if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { @@ -1779,7 +1827,7 @@ namespace MWWorld World::isFlying(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); + bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0); if(!ptr.getClass().isActor()) return false; @@ -1790,7 +1838,7 @@ namespace MWWorld if (ptr.getClass().canFly(ptr)) return !isParalyzed; - if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0 + if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && isLevitationEnabled()) return true; @@ -1808,7 +1856,7 @@ namespace MWWorld return false; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).mMagnitude > 0) + if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).getMagnitude() > 0) return true; return false; @@ -1983,27 +2031,72 @@ namespace MWWorld mDoorStates[door] = state; } - void World::activateDoor(const Ptr &door, bool open) + void World::activateDoor(const Ptr &door, int state) { - int state = open ? 1 : 2; door.getClass().setDoorState(door, state); mDoorStates[door] = state; + if (state == 0) + mDoorStates.erase(door); } bool World::getPlayerStandingOn (const MWWorld::Ptr& object) { - MWWorld::Ptr player = mPlayer->getPlayer(); - if (!mPhysEngine->getCharacter("player")->getOnGround()) - return false; - btVector3 from (player.getRefData().getPosition().pos[0], player.getRefData().getPosition().pos[1], player.getRefData().getPosition().pos[2]); - btVector3 to = from - btVector3(0,0,5); - std::pair result = mPhysEngine->rayTest(from, to); - return result.first == object.getRefData().getBaseNode()->getName(); + MWWorld::Ptr player = getPlayerPtr(); + return mPhysics->isActorStandingOn(player, object); } bool World::getActorStandingOn (const MWWorld::Ptr& object) { - return mPhysEngine->isAnyActorStandingOn(object.getRefData().getBaseNode()->getName()); + std::vector actors; + mPhysics->getActorsStandingOn(object, actors); + return !actors.empty(); + } + + bool World::getPlayerCollidingWith (const MWWorld::Ptr& object) + { + MWWorld::Ptr player = getPlayerPtr(); + return mPhysics->isActorCollidingWith(player, object); + } + + bool World::getActorCollidingWith (const MWWorld::Ptr& object) + { + std::vector actors; + mPhysics->getActorsCollidingWith(object, actors); + return !actors.empty(); + } + + void World::hurtStandingActors(const Ptr &object, float healthPerSecond) + { + std::vector actors; + mPhysics->getActorsStandingOn(object, actors); + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist + if (actor.isEmpty()) + continue; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); + stats.setHealth(health); + } + } + + void World::hurtCollidingActors(const Ptr &object, float healthPerSecond) + { + std::vector actors; + mPhysics->getActorsCollidingWith(object, actors); + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist + if (actor.isEmpty()) + continue; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); + stats.setHealth(health); + } } float World::getWindSpeed() @@ -2110,8 +2203,8 @@ namespace MWWorld void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); - - physicActor->enableCollisionBody(enable); + if (physicActor) + physicActor->enableCollisionBody(enable); } bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) @@ -2248,6 +2341,34 @@ namespace MWWorld windowManager->unsetForceHide(MWGui::GW_Inventory); windowManager->unsetForceHide(MWGui::GW_Magic); } + + // Witnesses of the player's transformation will make them a globally known werewolf + std::vector closeActors; + MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(actor.getRefData().getPosition().pos), + getStore().get().search("fAlarmRadius")->getFloat(), + closeActors); + + bool detected = false; + for (std::vector::const_iterator it = closeActors.begin(); it != closeActors.end(); ++it) + { + if (*it == actor) + continue; + + if (!it->getClass().isNpc()) + continue; + + if (getLOS(*it, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, *it)) + { + detected = true; + break; + } + } + + if (detected) + { + windowManager->messageBox("#{sWerewolfAlarmMessage}"); + setGlobalInt("pcknownwerewolf", 1); + } } } @@ -2340,8 +2461,49 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // TODO: this only works for the player - MWWorld::Ptr target = getFacedObject(); + // Get the target to use for "on touch" effects + MWWorld::Ptr target; + float distance = 192.f; // ?? + + if (actor == getPlayerPtr()) + { + // For the player, use camera to aim + std::string facedHandle; + getFacedHandle(facedHandle, distance); + if (!facedHandle.empty()) + target = getPtrViaHandle(facedHandle); + } + else + { + // For NPCs use facing direction from Head node + Ogre::Vector3 origin(actor.getRefData().getPosition().pos); + MWRender::Animation *anim = mRendering->getAnimation(actor); + if(anim != NULL) + { + Ogre::Node *node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); + if(node != NULL) + origin += node->_getDerivedPosition(); + } + Ogre::Quaternion orient; + orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Vector3 direction = orient.yAxis(); + Ogre::Vector3 dest = origin + direction * distance; + + + std::vector > collisions = mPhysEngine->rayTest2(btVector3(origin.x, origin.y, origin.z), btVector3(dest.x, dest.y, dest.z)); + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + { + MWWorld::Ptr collided = getPtrViaHandle(cIt->second); + if (collided != actor) + { + target = collided; + break; + } + } + } std::string selectedSpell = stats.getSpells().getSelectedSpell(); @@ -2352,10 +2514,10 @@ namespace MWWorld if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().search(selectedSpell); - - // A power can be used once per 24h - if (spell->mData.mType == ESM::Spell::ST_Power) - stats.getSpells().usePower(spell->mId); + + // A power can be used once per 24h + if (spell->mData.mType == ESM::Spell::ST_Power) + stats.getSpells().usePower(spell->mId); cast.cast(spell); } @@ -2543,11 +2705,11 @@ namespace MWWorld const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float dist=0; if (type == World::Detect_Creature) - dist = effects.get(ESM::MagicEffect::DetectAnimal).mMagnitude; + dist = effects.get(ESM::MagicEffect::DetectAnimal).getMagnitude(); else if (type == World::Detect_Key) - dist = effects.get(ESM::MagicEffect::DetectKey).mMagnitude; + dist = effects.get(ESM::MagicEffect::DetectKey).getMagnitude(); else if (type == World::Detect_Enchantment) - dist = effects.get(ESM::MagicEffect::DetectEnchantment).mMagnitude; + dist = effects.get(ESM::MagicEffect::DetectEnchantment).getMagnitude(); if (!dist) return; @@ -2585,9 +2747,15 @@ namespace MWWorld float fCrimeGoldDiscountMult = getStore().get().find("fCrimeGoldDiscountMult")->getFloat(); float fCrimeGoldTurnInMult = getStore().get().find("fCrimeGoldTurnInMult")->getFloat(); - int discount = bounty*fCrimeGoldDiscountMult; + int discount = bounty * fCrimeGoldDiscountMult; int turnIn = bounty * fCrimeGoldTurnInMult; + if (bounty > 0) + { + discount = std::max(1, discount); + turnIn = std::max(1, turnIn); + } + mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0); mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0); @@ -2643,8 +2811,19 @@ namespace MWWorld { if (!mGoToJail) { - // Save for next update, since the player should be able to read the dialog text first + // Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first) mGoToJail = true; + + MWWorld::Ptr player = getPlayerPtr(); + + int bounty = player.getClass().getNpcStats(player).getBounty(); + player.getClass().getNpcStats(player).setBounty(0); + mPlayer->recordCrimeId(); + confiscateStolenItems(player); + + int iDaysinPrisonMod = getStore().get().find("iDaysinPrisonMod")->getInt(); + mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod); + return; } else @@ -2655,13 +2834,8 @@ namespace MWWorld MWWorld::Ptr player = getPlayerPtr(); teleportToClosestMarker(player, "prisonmarker"); - int bounty = player.getClass().getNpcStats(player).getBounty(); - player.getClass().getNpcStats(player).setBounty(0); - confiscateStolenItems(player); - - int iDaysinPrisonMod = getStore().get().find("iDaysinPrisonMod")->getInt(); - int days = std::max(1, bounty / iDaysinPrisonMod); + int days = mDaysInPrison; advanceTime(days * 24); for (int i=0; irest (true); @@ -2860,4 +3034,31 @@ namespace MWWorld if (!interpreterContext.hasActivationBeenHandled()) interpreterContext.executeActivation(object, actor); } + + struct ResetActorsFunctor + { + bool operator() (Ptr ptr) + { + // Can't reset actors that were moved to a different cell, because we don't know what cell they came from. + // This could be fixed once we properly track actor cell changes, but may not be desirable behaviour anyhow. + if (ptr.getClass().isActor() && ptr.getCellRef().getRefNum().mContentFile != -1) + { + const ESM::Position& origPos = ptr.getCellRef().getPosition(); + MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]); + MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]); + ptr.getClass().adjustPosition(ptr, false); + } + return true; + } + }; + void World::resetActors() + { + for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); + iter!=mWorldScene->getActiveCells().end(); ++iter) + { + CellStore* cellstore = *iter; + ResetActorsFunctor functor; + cellstore->forEach(functor); + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 08d7eb42dd..d4f11f5b67 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -91,8 +91,6 @@ namespace MWWorld Ptr getPtrViaHandle (const std::string& handle, CellStore& cellStore); int mActivationDistanceOverride; - std::string mFacedHandle; - float mFacedDistance; std::string mStartupScript; @@ -113,7 +111,7 @@ namespace MWWorld void updateWindowManager (); void performUpdateSceneQueries (); - void updateFacedHandle (); + void getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer=true); float getMaxActivationDistance (); float getNpcActivationDistance (); @@ -143,6 +141,7 @@ namespace MWWorld bool mTeleportEnabled; bool mLevitationEnabled; bool mGoToJail; + int mDaysInPrison; float feetToGameUnits(float feet); @@ -169,9 +168,6 @@ namespace MWWorld virtual void readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap); - virtual OEngine::Render::Fader* getFader(); - ///< \todo remove this function. Rendering details should not be exposed. - virtual CellStore *getExterior (int x, int y); virtual CellStore *getInterior (const std::string& name); @@ -208,13 +204,16 @@ namespace MWWorld virtual bool isCellQuasiExterior() const; virtual Ogre::Vector2 getNorthVector (CellStore* cell); - ///< get north vector (OGRE coordinates) for given interior cell + ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); ///< get a list of teleport door markers for a given cell, to be displayed on the local map - virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); - ///< see MWRender::LocalMap::getInteriorMapPosition + virtual void worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); + ///< see MWRender::LocalMap::worldToInteriorMapPosition + + virtual Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y); + ///< see MWRender::LocalMap::interiorMapToWorldPosition virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior); ///< see MWRender::LocalMap::isPositionExplored @@ -260,8 +259,9 @@ namespace MWWorld virtual Ptr searchPtrViaActorId (int actorId); ///< Search is limited to the active cells. - virtual void adjustPosition (const Ptr& ptr); + virtual void adjustPosition (const Ptr& ptr, bool force); ///< Adjust position after load to be on ground. Must be called after model load. + /// @param force do this even if the ptr is flying virtual void fixPosition (const Ptr& actor); ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. @@ -473,11 +473,22 @@ namespace MWWorld /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door); - /// open or close a non-teleport door as specified - virtual void activateDoor(const MWWorld::Ptr& door, bool open); + /// update movement state of a non-teleport door as specified + /// @param state see MWClass::setDoorState + /// @note throws an exception when invoked on a teleport door + virtual void activateDoor(const MWWorld::Ptr& door, int state); virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object); ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::Ptr& object); ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond); + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond); + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual float getWindSpeed(); virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out); @@ -600,6 +611,9 @@ namespace MWWorld /// @see MWWorld::WeatherManager::getStormDirection virtual Ogre::Vector3 getStormDirection() const; + + /// Resets all actors in the current active cells to their original location within that cell. + virtual void resetActors(); }; } diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake index a3509597b2..74584bf318 100644 --- a/cmake/FindFFmpeg.cmake +++ b/cmake/FindFFmpeg.cmake @@ -14,6 +14,7 @@ # - AVUTIL # - POSTPROCESS # - SWSCALE +# - SWRESAMPLE # the following variables will be defined # _FOUND - System has # _INCLUDE_DIRS - Include directory necessary for using the headers @@ -112,6 +113,8 @@ if (NOT FFMPEG_LIBRARIES) find_component(AVUTIL libavutil avutil libavutil/avutil.h) find_component(SWSCALE libswscale swscale libswscale/swscale.h) find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + find_component(AVRESAMPLE libavresample avresample libavresample/avresample.h) # Check if the required components were found and add their stuff to the FFMPEG_* vars. foreach (_component ${FFmpeg_FIND_COMPONENTS}) @@ -142,7 +145,7 @@ if (NOT FFMPEG_LIBRARIES) endif () # Now set the noncached _FOUND vars for the components. -foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE SWRESAMPLE AVRESAMPLE) set_component_found(${_component}) endforeach () diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index ebf0694046..40fa2373f0 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -12,6 +12,7 @@ # For details see the accompanying COPYING-CMAKE-SCRIPTS file. CMAKE_POLICY(PUSH) include(FindPkgMacros) +include(PreprocessorUtils) # IF (MYGUI_LIBRARIES AND MYGUI_INCLUDE_DIRS) # SET(MYGUI_FIND_QUIETLY TRUE) @@ -131,6 +132,18 @@ IF (MYGUI_FOUND) MESSAGE(STATUS " libraries : ${MYGUI_LIBRARIES} from ${MYGUI_LIB_DIR}") MESSAGE(STATUS " includes : ${MYGUI_INCLUDE_DIRS}") ENDIF (NOT MYGUI_FIND_QUIETLY) + + find_file(MYGUI_PREQUEST_FILE NAMES MyGUI_Prerequest.h PATHS ${MYGUI_INCLUDE_DIRS}) + file(READ ${MYGUI_PREQUEST_FILE} MYGUI_TEMP_VERSION_CONTENT) + get_preprocessor_entry(MYGUI_TEMP_VERSION_CONTENT MYGUI_VERSION_MAJOR MYGUI_VERSION_MAJOR) + get_preprocessor_entry(MYGUI_TEMP_VERSION_CONTENT MYGUI_VERSION_MINOR MYGUI_VERSION_MINOR) + get_preprocessor_entry(MYGUI_TEMP_VERSION_CONTENT MYGUI_VERSION_PATCH MYGUI_VERSION_PATCH) + set(MYGUI_VERSION "${MYGUI_VERSION_MAJOR}.${MYGUI_VERSION_MINOR}.${MYGUI_VERSION_PATCH}") + + 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") diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index 81b52b1b77..5608c528cd 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -127,7 +127,7 @@ endif () set(OGRE_COMPONENTS Paging Terrain Plugin_BSPSceneManager Plugin_CgProgramManager Plugin_OctreeSceneManager Plugin_OctreeZone Plugin_PCZSceneManager Plugin_ParticleFX - RenderSystem_Direct3D10 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES) + RenderSystem_Direct3D10 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES2) set(OGRE_RESET_VARS OGRE_CONFIG_INCLUDE_DIR OGRE_INCLUDE_DIR OGRE_LIBRARY_FWK OGRE_LIBRARY_REL OGRE_LIBRARY_DBG @@ -234,10 +234,10 @@ if (OGRE_STATIC) find_package(FreeImage QUIET) find_package(Freetype QUIET) find_package(OpenGL QUIET) - find_package(OpenGLES QUIET) + find_package(OpenGLES2 QUIET) find_package(ZLIB QUIET) find_package(ZZip QUIET) - if (UNIX AND NOT APPLE) + if (UNIX AND (NOT APPLE AND NOT ANDROID)) find_package(X11 QUIET) find_library(XAW_LIBRARY NAMES Xaw Xaw7 PATHS ${DEP_LIB_SEARCH_DIR} ${X11_LIB_SEARCH_PATH}) if (NOT XAW_LIBRARY OR NOT X11_Xt_FOUND) @@ -258,10 +258,16 @@ if (OGRE_STATIC) endif () endif () - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} +if (ANDROID) + set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} - ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES}) +else () + set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} + ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} + ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} + ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES}) +endif() if (NOT ZLIB_FOUND OR NOT ZZip_FOUND) set(OGRE_DEPS_FOUND FALSE) @@ -272,7 +278,7 @@ if (OGRE_STATIC) if (NOT FREETYPE_FOUND) set(OGRE_DEPS_FOUND FALSE) endif () - if (UNIX AND NOT APPLE) + if (UNIX AND NOT APPLE AND NOT ANDROID) if (NOT X11_FOUND) set(OGRE_DEPS_FOUND FALSE) endif () @@ -486,7 +492,7 @@ ogre_find_plugin(Plugin_CgProgramManager OgreCgProgram.h PlugIns/CgProgramManage ogre_find_plugin(Plugin_OctreeSceneManager OgreOctreeSceneManager.h PlugIns/OctreeSceneManager/include) ogre_find_plugin(Plugin_ParticleFX OgreParticleFXPrerequisites.h PlugIns/ParticleFX/include) ogre_find_plugin(RenderSystem_GL OgreGLRenderSystem.h RenderSystems/GL/include) -ogre_find_plugin(RenderSystem_GLES OgreGLESRenderSystem.h RenderSystems/GLES/include) +ogre_find_plugin(RenderSystem_GLES2 OgreGLES2RenderSystem.h RenderSystems/GLES2/include) ogre_find_plugin(RenderSystem_Direct3D9 OgreD3D9RenderSystem.h RenderSystems/Direct3D9/include) ogre_find_plugin(RenderSystem_Direct3D10 OgreD3D10RenderSystem.h RenderSystems/Direct3D10/include) ogre_find_plugin(RenderSystem_Direct3D11 OgreD3D11RenderSystem.h RenderSystems/Direct3D11/include) @@ -528,8 +534,8 @@ if (OGRE_STATIC) set(OGRE_RenderSystem_GL_LIBRARIES ${OGRE_RenderSystem_GL_LIBRARIES} ${OPENGL_LIBRARIES} ) - set(OGRE_RenderSystem_GLES_LIBRARIES ${OGRE_RenderSystem_GLES_LIBRARIES} - ${OPENGLES_LIBRARIES} + set(OGRE_RenderSystem_GLES2_LIBRARIES ${OGRE_RenderSystem_GLES2_LIBRARIES} + ${OPENGLES2_LIBRARIES} ) set(OGRE_Plugin_CgProgramManager_LIBRARIES ${OGRE_Plugin_CgProgramManager_LIBRARIES} ${Cg_LIBRARIES} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b8ebb84b19..d0508e9d4c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -19,7 +19,11 @@ add_component_dir (bsa ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property + controlled effect niftypes record controller extra node record_ptr data niffile property nifkey data node + ) + +add_component_dir (nifcache + nifcache ) add_component_dir (nifogre @@ -41,16 +45,24 @@ add_component_dir (esm loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate - npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate - aisequence + npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile + aisequence magiceffects + ) + +add_component_dir (esmterrain + storage ) add_component_dir (misc - utf8stream stringops + utf8stream stringops resourcehelpers ) +IF(NOT WIN32 AND NOT APPLE) + add_definitions(-DGLOBAL_DATA_PATH="${GLOBAL_DATA_PATH}") + add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}") +ENDIF() add_component_dir (files - linuxpath windowspath macospath fixedpath multidircollection collections configurationmanager + linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager constrainedfiledatastream lowlevelfile ) @@ -58,7 +70,7 @@ add_component_dir (compiler context controlparser errorhandler exception exprparser extensions fileparser generator lineparser literals locals output parser scanner scriptparser skipparser streamerrorhandler stringparser tokenloc nullerrorhandler opcodes extensions0 declarationparser - quickfileparser + quickfileparser discardparser ) add_component_dir (interpreter @@ -72,7 +84,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world defaultworld storage material buffercache defs backgroundloader + quadtreenode chunk world defaultworld terraingrid storage material buffercache defs ) add_component_dir (loadinglistener @@ -83,6 +95,14 @@ add_component_dir (ogreinit ogreinit ogreplugin ) +add_component_dir (widgets + box imagebutton tags list numericeditbox widgets + ) + +add_component_dir (fontloader + fontloader + ) + add_component_dir (version version ) @@ -104,6 +124,12 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) +if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" AND NOT APPLE) + add_definitions(-fPIC) + endif() +endif () + include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) diff --git a/components/bsa/tests/.gitignore b/components/bsa/tests/.gitignore deleted file mode 100644 index e2f2f332df..0000000000 --- a/components/bsa/tests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*_test -bsatool -*.bsa diff --git a/components/bsa/tests/Makefile b/components/bsa/tests/Makefile deleted file mode 100644 index 73e20d7b3a..0000000000 --- a/components/bsa/tests/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -GCC=g++ - -all: bsa_file_test ogre_archive_test - -I_OGRE=$(shell pkg-config --cflags OGRE) -L_OGRE=$(shell pkg-config --libs OGRE) - -bsa_file_test: bsa_file_test.cpp ../bsa_file.cpp - $(GCC) $^ -o $@ - -ogre_archive_test: ogre_archive_test.cpp ../bsa_file.cpp ../bsa_archive.cpp - $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) - -clean: - rm *_test diff --git a/components/bsa/tests/bsa_file_test.cpp b/components/bsa/tests/bsa_file_test.cpp deleted file mode 100644 index 07ee73d17e..0000000000 --- a/components/bsa/tests/bsa_file_test.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "../bsa_file.hpp" - -/* - Test of the BSAFile class - - This test requires that data/Morrowind.bsa exists in the root - directory of OpenMW. - - */ - -#include - -using namespace std; -using namespace Bsa; - -BSAFile bsa; - -void find(const char* file) -{ - cout << "Does file '" << file << "' exist?\n "; - if(bsa.exists(file)) - { - cout << "Yes.\n "; - cout << bsa.getFile(file)->size() << " bytes\n"; - } - else cout << "No.\n"; -} - -int main() -{ - cout << "Reading Morrowind.bsa\n"; - bsa.open("../../data/Morrowind.bsa"); - - const BSAFile::FileList &files = bsa.getList(); - - cout << "First 10 files in archive:\n"; - for(int i=0; i<10; i++) - cout << " " << files[i].name - << " (" << files[i].fileSize << " bytes @" - << files[i].offset << ")\n"; - - find("meshes\\r\\xnetch_betty.nif"); - find("humdrum"); -} diff --git a/components/bsa/tests/ogre_archive_test.cpp b/components/bsa/tests/ogre_archive_test.cpp deleted file mode 100644 index 9cd57240f8..0000000000 --- a/components/bsa/tests/ogre_archive_test.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include - -// This is a test of the BSA archive handler for OGRE. - -#include "../bsa_archive.hpp" - -using namespace Ogre; -using namespace std; - -int main() -{ - // Disable Ogre logging - new LogManager; - Log *log = LogManager::getSingleton().createLog(""); - log->setDebugOutputEnabled(false); - - // Set up Root - Root *root = new Root("","",""); - - // Add the BSA - Bsa::addBSA("../../data/Morrowind.bsa"); - - // Pick a sample file - String tex = "textures\\tx_natural_cavern_wall13.dds"; - cout << "Opening file: " << tex << endl; - - // Get it from the resource system - DataStreamPtr data = ResourceGroupManager::getSingleton().openResource(tex, "General"); - - cout << "Size: " << data->size() << endl; - - return 0; -} diff --git a/components/bsa/tests/output/bsa_file_test.out b/components/bsa/tests/output/bsa_file_test.out deleted file mode 100644 index 0d8e76cdd8..0000000000 --- a/components/bsa/tests/output/bsa_file_test.out +++ /dev/null @@ -1,17 +0,0 @@ -Reading Morrowind.bsa -First 10 files in archive: - meshes\m\probe_journeyman_01.nif (6276 bytes @126646052) - textures\menu_rightbuttonup_top.dds (256 bytes @218530052) - textures\menu_rightbuttonup_right.dds (256 bytes @218529796) - textures\menu_rightbuttonup_left.dds (256 bytes @218529540) - textures\menu_rightbuttondown_top.dds (256 bytes @218528196) - meshes\b\b_n_redguard_f_skins.nif (41766 bytes @17809778) - meshes\b\b_n_redguard_m_skins.nif (41950 bytes @18103107) - meshes\b\b_n_redguard_f_wrist.nif (2355 bytes @17858132) - meshes\b\b_n_redguard_m_foot.nif (4141 bytes @17862081) - meshes\b\b_n_redguard_m_knee.nif (2085 bytes @18098101) -Does file 'meshes\r\xnetch_betty.nif' exist? - Yes. - 53714 bytes -Does file 'humdrum' exist? - No. diff --git a/components/bsa/tests/output/ogre_archive_test.out b/components/bsa/tests/output/ogre_archive_test.out deleted file mode 100644 index 748e4b1e7f..0000000000 --- a/components/bsa/tests/output/ogre_archive_test.out +++ /dev/null @@ -1,2 +0,0 @@ -Opening file: textures\tx_natural_cavern_wall13.dds -Size: 43808 diff --git a/components/bsa/tests/test.sh b/components/bsa/tests/test.sh deleted file mode 100755 index 2d07708adc..0000000000 --- a/components/bsa/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp new file mode 100644 index 0000000000..6028968bb2 --- /dev/null +++ b/components/compiler/discardparser.cpp @@ -0,0 +1,70 @@ + +#include "discardparser.hpp" + +#include "scanner.hpp" + +namespace Compiler +{ + DiscardParser::DiscardParser (ErrorHandler& errorHandler, const Context& context) + : Parser (errorHandler, context), mState (StartState) + { + + } + + bool DiscardParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) + { + if (mState==StartState || mState==CommaState || mState==MinusState) + { + start(); + return false; + } + + return Parser::parseInt (value, loc, scanner); + } + + bool DiscardParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) + { + if (mState==StartState || mState==CommaState || mState==MinusState) + { + start(); + return false; + } + + return Parser::parseFloat (value, loc, scanner); + } + + bool DiscardParser::parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner) + { + if (mState==StartState || mState==CommaState) + { + start(); + return false; + } + + return Parser::parseName (name, loc, scanner); + } + + bool DiscardParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) + { + if (code==Scanner::S_comma && mState==StartState) + { + mState = CommaState; + return true; + } + + if (code==Scanner::S_minus && (mState==StartState || mState==CommaState)) + { + mState = MinusState; + return true; + } + + return Parser::parseSpecial (code, loc, scanner); + } + + void DiscardParser::reset() + { + mState = StartState; + Parser::reset(); + } +} diff --git a/components/compiler/discardparser.hpp b/components/compiler/discardparser.hpp new file mode 100644 index 0000000000..bee8a87bb2 --- /dev/null +++ b/components/compiler/discardparser.hpp @@ -0,0 +1,45 @@ +#ifndef COMPILER_DISCARDPARSER_H_INCLUDED +#define COMPILER_DISCARDPARSER_H_INCLUDED + +#include "parser.hpp" + +namespace Compiler +{ + /// \brief Parse a single optional numeric value or string and discard it + class DiscardParser : public Parser + { + enum State + { + StartState, CommaState, MinusState + }; + + State mState; + + public: + + DiscardParser (ErrorHandler& errorHandler, const Context& context); + + virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner); + ///< Handle an int token. + /// \return fetch another token? + + virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner); + ///< Handle a float token. + /// \return fetch another token? + + virtual bool parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner); + ///< Handle a name token. + /// \return fetch another token? + + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); + ///< Handle a special character token. + /// \return fetch another token? + + virtual void reset(); + ///< Reset parser to clean state. + }; +} + +#endif + diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index fe58836cca..bcd30ef2d5 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -3,11 +3,8 @@ namespace Compiler { - // constructor - - ErrorHandler::ErrorHandler() : mWarnings (0), mErrors (0), mWarningsMode (1) {} - - // destructor + ErrorHandler::ErrorHandler() + : mWarnings (0), mErrors (0), mWarningsMode (1), mDowngradeErrors (false) {} ErrorHandler::~ErrorHandler() {} @@ -49,6 +46,12 @@ namespace Compiler void ErrorHandler::error (const std::string& message, const TokenLoc& loc) { + if (mDowngradeErrors) + { + warning (message, loc); + return; + } + ++mErrors; report (message, loc, ErrorMessage); } @@ -72,4 +75,21 @@ namespace Compiler { mWarningsMode = mode; } + + void ErrorHandler::downgradeErrors (bool downgrade) + { + mDowngradeErrors = downgrade; + } + + + ErrorDowngrade::ErrorDowngrade (ErrorHandler& handler) : mHandler (handler) + { + mHandler.downgradeErrors (true); + } + + ErrorDowngrade::~ErrorDowngrade() + { + mHandler.downgradeErrors (false); + } + } diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp index e5922a6be5..c92e7bb8d9 100644 --- a/components/compiler/errorhandler.hpp +++ b/components/compiler/errorhandler.hpp @@ -17,6 +17,7 @@ namespace Compiler int mWarnings; int mErrors; int mWarningsMode; + bool mDowngradeErrors; protected: @@ -66,6 +67,26 @@ namespace Compiler void setWarningsMode (int mode); ///< // 0 ignore, 1 rate as warning, 2 rate as error + + /// Treat errors as warnings. + void downgradeErrors (bool downgrade); + }; + + class ErrorDowngrade + { + ErrorHandler& mHandler; + + /// not implemented + ErrorDowngrade (const ErrorDowngrade&); + + /// not implemented + ErrorDowngrade& operator= (const ErrorDowngrade&); + + public: + + ErrorDowngrade (ErrorHandler& handler); + + ~ErrorDowngrade(); }; } diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index e54b2e2a8f..6dcca08df9 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -16,6 +16,7 @@ #include "stringparser.hpp" #include "extensions.hpp" #include "context.hpp" +#include "discardparser.hpp" namespace Compiler { @@ -386,6 +387,9 @@ namespace Compiler mExplicit.clear(); mRefOp = false; + std::vector ignore; + parseArguments ("x", scanner, ignore); + mNextOperand = false; return true; } @@ -404,6 +408,21 @@ namespace Compiler mNextOperand = false; return true; } + else if (keyword==Scanner::K_scriptrunning) + { + start(); + + mTokenLoc = loc; + parseArguments ("c", scanner); + + Generator::scriptRunning (mCode); + mOperands.push_back ('l'); + + mExplicit.clear(); + mRefOp = false; + mNextOperand = false; + return true; + } // check for custom extensions if (const Extensions *extensions = getContext().getExtensions()) @@ -527,6 +546,9 @@ namespace Compiler Generator::getDisabled (mCode, mLiterals, ""); mOperands.push_back ('l'); + std::vector ignore; + parseArguments ("x", scanner, ignore); + mNextOperand = false; return true; } @@ -737,6 +759,7 @@ namespace Compiler ExprParser parser (getErrorHandler(), getContext(), mLocals, mLiterals, true); StringParser stringParser (getErrorHandler(), getContext(), mLiterals); + DiscardParser discardParser (getErrorHandler(), getContext()); std::stack > stack; @@ -771,11 +794,32 @@ namespace Compiler ++optionalCount; } } + else if (*iter=='X') + { + parser.reset(); + + parser.setOptional (true); + + scanner.scan (parser); + + if (parser.isEmpty()) + break; + } + else if (*iter=='z') + { + discardParser.reset(); + discardParser.setOptional (true); + + scanner.scan (discardParser); + + if (discardParser.isEmpty()) + break; + } else { parser.reset(); - if (optional || *iter == 'X') + if (optional) parser.setOptional (true); scanner.scan (parser); @@ -783,20 +827,17 @@ namespace Compiler if (optional && parser.isEmpty()) break; - if (*iter != 'X') - { - std::vector tmp; + std::vector tmp; - char type = parser.append (tmp); + char type = parser.append (tmp); - if (type!=*iter) - Generator::convert (tmp, type, *iter); + if (type!=*iter) + Generator::convert (tmp, type, *iter); - stack.push (tmp); + stack.push (tmp); - if (optional) - ++optionalCount; - } + if (optional) + ++optionalCount; } } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index d229751dee..a15218d99f 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -21,7 +21,8 @@ namespace Compiler s - Short
    S - String, case preserved
    x - Optional, ignored string argument - X - Optional, ignored integer argument + X - Optional, ignored numeric expression + z - Optional, ignored string or numeric argument **/ typedef std::string ScriptArgs; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index ef4fe4fbda..e12ab65eb5 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -113,12 +113,12 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("additem", "cl", opcodeAddItem, opcodeAddItemExplicit); + extensions.registerInstruction ("additem", "clX", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, opcodeRemoveItemExplicit); - extensions.registerInstruction ("equip", "c", opcodeEquip, opcodeEquipExplicit); + extensions.registerInstruction ("equip", "cX", opcodeEquip, opcodeEquipExplicit); extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit); extensions.registerFunction ("hasitemequipped", 'l', "c", opcodeHasItemEquipped, opcodeHasItemEquippedExplicit); extensions.registerFunction ("hassoulgem", 'l', "c", opcodeHasSoulGem, opcodeHasSoulGemExplicit); @@ -148,6 +148,16 @@ namespace Compiler extensions.registerInstruction ("forcerun", "", opcodeForceRun, opcodeForceRunExplicit); + extensions.registerInstruction ("clearforcejump", "", opcodeClearForceJump, + opcodeClearForceJumpExplicit); + extensions.registerInstruction ("forcejump", "", opcodeForceJump, + opcodeForceJumpExplicit); + + extensions.registerInstruction ("clearforcemovejump", "", opcodeClearForceMoveJump, + opcodeClearForceMoveJumpExplicit); + extensions.registerInstruction ("forcemovejump", "", opcodeForceMoveJump, + opcodeForceMoveJumpExplicit); + extensions.registerInstruction ("clearforcesneak", "", opcodeClearForceSneak, opcodeClearForceSneakExplicit); extensions.registerInstruction ("forcesneak", "", opcodeForceSneak, @@ -155,6 +165,8 @@ namespace Compiler extensions.registerFunction ("getpcrunning", 'l', "", opcodeGetPcRunning); extensions.registerFunction ("getpcsneaking", 'l', "", opcodeGetPcSneaking); extensions.registerFunction ("getforcerun", 'l', "", opcodeGetForceRun, opcodeGetForceRunExplicit); + extensions.registerFunction ("getforcejump", 'l', "", opcodeGetForceJump, opcodeGetForceJumpExplicit); + extensions.registerFunction ("getforcemovejump", 'l', "", opcodeGetForceMoveJump, opcodeGetForceMoveJumpExplicit); extensions.registerFunction ("getforcesneak", 'l', "", opcodeGetForceSneak, opcodeGetForceSneakExplicit); } } @@ -272,6 +284,10 @@ namespace Compiler extensions.registerInstruction ("fall", "", opcodeFall, opcodeFallExplicit); extensions.registerFunction ("getstandingpc", 'l', "", opcodeGetStandingPc, opcodeGetStandingPcExplicit); extensions.registerFunction ("getstandingactor", 'l', "", opcodeGetStandingActor, opcodeGetStandingActorExplicit); + extensions.registerFunction ("getcollidingpc", 'l', "", opcodeGetCollidingPc, opcodeGetCollidingPcExplicit); + extensions.registerFunction ("getcollidingactor", 'l', "", opcodeGetCollidingActor, opcodeGetCollidingActorExplicit); + extensions.registerInstruction ("hurtstandingactor", "f", opcodeHurtStandingActor, opcodeHurtStandingActorExplicit); + extensions.registerInstruction ("hurtcollidingactor", "f", opcodeHurtCollidingActor, opcodeHurtCollidingActorExplicit); extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed); extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit); extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); @@ -354,6 +370,16 @@ namespace Compiler "mercantile", "speechcraft", "handtohand" }; + static const char *magicEffects[numberOfMagicEffects] = + { + "resistmagicka", "resistfire", "resistfrost", "resistshock", + "resistdisease", "resistblight", "resistcorprus", "resistpoison", + "resistparalysis", "resistnormalweapons", "waterbreathing", "chameleon", + "waterwalking", "swimspeed", "superjump", "flying", + "armorbonus", "castpenalty", "silence", "blindness", + "paralysis", "invisible", "attackbonus", "defendbonus" + }; + std::string get ("get"); std::string set ("set"); std::string mod ("mod"); @@ -402,11 +428,23 @@ namespace Compiler opcodeModSkill+i, opcodeModSkillExplicit+i); } + for (int i=0; i + #include #include "scanner.hpp" @@ -11,6 +13,7 @@ #include "generator.hpp" #include "extensions.hpp" #include "declarationparser.hpp" +#include "exception.hpp" namespace Compiler { @@ -54,7 +57,7 @@ namespace Compiler Literals& literals, std::vector& code, bool allowExpression) : Parser (errorHandler, context), mLocals (locals), mLiterals (literals), mCode (code), mState (BeginState), mExprParser (errorHandler, context, locals, literals), - mAllowExpression (allowExpression), mButtons(0), mType(0) + mAllowExpression (allowExpression), mButtons(0), mType(0), mReferenceMember(false) {} bool LineParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) @@ -119,7 +122,7 @@ namespace Compiler if (mState==SetMemberVarState) { - mMemberName = name; + mMemberName = Misc::StringUtils::lowerCase (name); std::pair type = getContext().getMemberType (mMemberName, mName); if (type.first!=' ') @@ -262,6 +265,20 @@ namespace Compiler Generator::disable (mCode, mLiterals, mExplicit); mState = PotentialEndState; return true; + + case Scanner::K_startscript: + + mExprParser.parseArguments ("c", scanner, mCode); + Generator::startScript (mCode, mLiterals, mExplicit); + mState = EndState; + return true; + + case Scanner::K_stopscript: + + mExprParser.parseArguments ("c", scanner, mCode); + Generator::stopScript (mCode); + mState = EndState; + return true; } // check for custom extensions @@ -278,9 +295,34 @@ namespace Compiler mExplicit.clear(); } - int optionals = mExprParser.parseArguments (argumentType, scanner, mCode); + try + { + // workaround for broken positioncell instructions. + /// \todo add option to disable this + std::auto_ptr errorDowngrade (0); + if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") + errorDowngrade.reset (new ErrorDowngrade (getErrorHandler())); + + std::vector code; + int optionals = mExprParser.parseArguments (argumentType, scanner, code); + mCode.insert (mCode.end(), code.begin(), code.end()); + extensions->generateInstructionCode (keyword, mCode, mLiterals, + mExplicit, optionals); + } + catch (const SourceException&) + { + // Ignore argument exceptions for positioncell. + /// \todo add option to disable this + if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") + { + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + return false; + } + + throw; + } - extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals); mState = EndState; return true; } @@ -349,7 +391,7 @@ namespace Compiler if (declaration.parseKeyword (keyword, loc, scanner)) scanner.scan (declaration); - return true; + return false; } case Scanner::K_set: mState = SetState; return true; @@ -361,13 +403,6 @@ namespace Compiler mState = EndState; return true; - case Scanner::K_startscript: - - mExprParser.parseArguments ("c", scanner, mCode); - Generator::startScript (mCode); - mState = EndState; - return true; - case Scanner::K_stopscript: mExprParser.parseArguments ("c", scanner, mCode); diff --git a/components/compiler/locals.hpp b/components/compiler/locals.hpp index d5bf052538..1b2ae60426 100644 --- a/components/compiler/locals.hpp +++ b/components/compiler/locals.hpp @@ -15,10 +15,6 @@ namespace Compiler std::vector mLongs; std::vector mFloats; - int searchIndex (char type, const std::string& name) const; - - bool search (char type, const std::string& name) const; - std::vector& get (char type); public: @@ -29,6 +25,12 @@ namespace Compiler int getIndex (const std::string& name) const; ///< return index for local variable \a name (-1: does not exist). + bool search (char type, const std::string& name) const; + + /// Return index for local variable \a name of type \a type (-1: variable does not + /// exit). + int searchIndex (char type, const std::string& name) const; + const std::vector& get (char type) const; void write (std::ostream& localFile) const; diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 9f3ed3574e..440475773d 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -123,6 +123,14 @@ namespace Compiler const int opcodeClearForceRunExplicit = 0x2000155; const int opcodeForceRun = 0x2000156; const int opcodeForceRunExplicit = 0x2000157; + const int opcodeClearForceJump = 0x2000258; + const int opcodeClearForceJumpExplicit = 0x2000259; + const int opcodeForceJump = 0x200025a; + const int opcodeForceJumpExplicit = 0x200025b; + const int opcodeClearForceMoveJump = 0x200025c; + const int opcodeClearForceMoveJumpExplicit = 0x200025d; + const int opcodeForceMoveJump = 0x200025e; + const int opcodeForceMoveJumpExplicit = 0x200025f; const int opcodeClearForceSneak = 0x2000158; const int opcodeClearForceSneakExplicit = 0x2000159; const int opcodeForceSneak = 0x200015a; @@ -134,6 +142,10 @@ namespace Compiler const int opcodeGetForceSneak = 0x20001cc; const int opcodeGetForceRunExplicit = 0x20001cd; const int opcodeGetForceSneakExplicit = 0x20001ce; + const int opcodeGetForceJump = 0x2000260; + const int opcodeGetForceMoveJump = 0x2000262; + const int opcodeGetForceJumpExplicit = 0x2000261; + const int opcodeGetForceMoveJumpExplicit = 0x2000263; } namespace Dialogue @@ -238,6 +250,14 @@ namespace Compiler const int opcodeGetStandingPcExplicit = 0x200020d; const int opcodeGetStandingActor = 0x200020e; const int opcodeGetStandingActorExplicit = 0x200020f; + const int opcodeGetCollidingPc = 0x2000250; + const int opcodeGetCollidingPcExplicit = 0x2000251; + const int opcodeGetCollidingActor = 0x2000252; + const int opcodeGetCollidingActorExplicit = 0x2000253; + const int opcodeHurtStandingActor = 0x2000254; + const int opcodeHurtStandingActorExplicit = 0x2000255; + const int opcodeHurtCollidingActor = 0x2000256; + const int opcodeHurtCollidingActorExplicit = 0x2000257; const int opcodeGetWindSpeed = 0x2000212; const int opcodePlayBink = 0x20001f7; const int opcodeGoToJail = 0x2000235; @@ -302,6 +322,8 @@ namespace Compiler const int numberOfDynamics = 3; const int numberOfSkills = 27; + const int numberOfMagicEffects = 24; + const int opcodeGetAttribute = 0x2000027; const int opcodeGetAttributeExplicit = 0x200002f; const int opcodeSetAttribute = 0x2000037; @@ -327,6 +349,13 @@ namespace Compiler const int opcodeModSkill = 0x20000fa; const int opcodeModSkillExplicit = 0x2000115; + const int opcodeGetMagicEffect = 0x2000264; + const int opcodeGetMagicEffectExplicit = 0x200027c; + const int opcodeSetMagicEffect = 0x2000294; + const int opcodeSetMagicEffectExplicit = 0x20002ac; + const int opcodeModMagicEffect = 0x20002c4; + const int opcodeModMagicEffectExplicit = 0x20002dc; + const int opcodeGetPCCrimeLevel = 0x20001ec; const int opcodeSetPCCrimeLevel = 0x20001ed; const int opcodeModPCCrimeLevel = 0x20001ee; @@ -455,6 +484,7 @@ namespace Compiler const int opcodeMoveExplicit = 0x2000207; const int opcodeMoveWorld = 0x2000208; const int opcodeMoveWorldExplicit = 0x2000209; + const int opcodeResetActors = 0x20002f4; } namespace User diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 781fbad8cb..0f442c3504 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -21,13 +21,6 @@ namespace Compiler throw SourceException(); } - // Report the error - - void Parser::reportError (const std::string& message, const TokenLoc& loc) - { - mErrorHandler.error (message, loc); - } - // Report the warning without throwing an exception. void Parser::reportWarning (const std::string& message, const TokenLoc& loc) diff --git a/components/compiler/parser.hpp b/components/compiler/parser.hpp index 54e913b202..2ef6e85b98 100644 --- a/components/compiler/parser.hpp +++ b/components/compiler/parser.hpp @@ -26,9 +26,6 @@ namespace Compiler void reportSeriousError (const std::string& message, const TokenLoc& loc); ///< Report the error and throw a exception. - void reportError (const std::string& message, const TokenLoc& loc); - ///< Report the error - void reportWarning (const std::string& message, const TokenLoc& loc); ///< Report the warning without throwing an exception. diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 46e50a2e9b..dd8fb431bf 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -343,17 +343,13 @@ namespace Compiler } else if (!(c=='"' && name.empty())) { - if (!(std::isalpha (c) || std::isdigit (c) || c=='_' || c=='`' || - /// \todo add an option to disable the following hack. Also, find out who is - /// responsible for allowing it in the first place and meet up with that person in - /// a dark alley. - (c=='-' && !name.empty() && std::isalpha (mStream.peek())))) + if (!isStringCharacter (c)) { putback (c); break; } - if (first && std::isdigit (c)) + if (first && (std::isdigit (c) || c=='`' || c=='-')) error = true; } @@ -499,6 +495,17 @@ namespace Compiler return true; } + bool Scanner::isStringCharacter (char c, bool lookAhead) + { + return std::isalpha (c) || std::isdigit (c) || c=='_' || + /// \todo disable this when doing more stricter compiling + c=='`' || + /// \todo disable this when doing more stricter compiling. Also, find out who is + /// responsible for allowing it in the first place and meet up with that person in + /// a dark alley. + (c=='-' && (!lookAhead || isStringCharacter (mStream.peek(), false))); + } + bool Scanner::isWhitespace (char c) { return c==' ' || c=='\t'; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 344ae05825..7f6609f761 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -92,6 +92,8 @@ namespace Compiler bool scanSpecial (char c, Parser& parser, bool& cont); + bool isStringCharacter (char c, bool lookAhead = true); + static bool isWhitespace (char c); public: diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index f5bc2369a4..0d4f2365a6 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -23,6 +23,7 @@ ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { + mEncoding = encoding; if (encoding == QLatin1String("win1252")) mCodec = QTextCodec::codecForName("windows-1252"); @@ -76,7 +77,7 @@ const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(co foreach (const EsmFile *file, mFiles) { - if (name == file->fileProperty (fp).toString()) + if (name.compare(file->fileProperty (fp).toString(), Qt::CaseInsensitive) == 0) return file; } return 0; @@ -119,7 +120,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index { //compare filenames only. Multiple instances //of the filename (with different paths) is not relevant here. - depFound = (dependency->fileName() == fileName); + depFound = (dependency->fileName().compare(fileName, Qt::CaseInsensitive) == 0); if (!depFound) continue; @@ -275,7 +276,6 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const case Qt::CheckStateRole: { int checkValue = value.toInt(); - bool success = false; bool setState = false; if ((checkValue==Qt::Checked) && !isChecked(file->filePath())) { @@ -299,7 +299,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const foreach (EsmFile *file, mFiles) { - if (file->gameFiles().contains(fileName)) + if (file->gameFiles().contains(fileName, Qt::CaseInsensitive)) { QModelIndex idx = indexFromItem(file); emit dataChanged(idx, idx); @@ -449,7 +449,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) try { ESM::ESMReader fileReader; ToUTF8::Utf8Encoder encoder = - ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString()); + ToUTF8::calculateEncoding(mEncoding.toStdString()); fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); @@ -500,7 +500,7 @@ void ContentSelectorModel::ContentModel::sortFiles() //dependencies appear. for (int j = i + 1; j < fileCount; j++) { - if (gamefiles.contains(mFiles.at(j)->fileName())) + if (gamefiles.contains(mFiles.at(j)->fileName(), Qt::CaseInsensitive)) { mFiles.move(j, i); @@ -589,7 +589,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, QFileInfo fileInfo(filepath); QString filename = fileInfo.fileName(); - if (downstreamFile->gameFiles().contains(filename)) + if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive)) { if (mCheckStates.contains(downstreamFile->filePath())) mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 6d147bce98..7b2000b510 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -64,6 +64,7 @@ namespace ContentSelectorModel ContentFileList mFiles; QHash mCheckStates; QTextCodec *mCodec; + QString mEncoding; public: diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index cf4951de7e..209a1fe264 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -13,6 +13,7 @@ namespace ESM void AIPackageList::load(ESMReader &esm) { + mList.clear(); while (esm.hasMoreSubs()) { // initialize every iteration AIPackage pack; diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp index 8a31aadf55..cbe82f16ed 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm/aipackage.hpp @@ -16,8 +16,10 @@ namespace ESM struct AIData { - // These are probabilities - char mHello, mU1, mFight, mFlee, mAlarm, mU2, mU3, mU4; + unsigned char mHello; + char mU1; + unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] + char mU2, mU3, mU4; // Unknown values int mServices; // See the Services enum void blank(); diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 80440bdd3e..0e3e541024 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -58,6 +58,8 @@ namespace AiSequence esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); + mCommanded = false; + esm.getHNOT (mCommanded, "CMND"); } void AiFollow::save(ESMWriter &esm) const @@ -68,6 +70,7 @@ namespace AiSequence if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); esm.writeHNT ("ALWY", mAlwaysFollow); + esm.writeHNT ("CMND", mCommanded); } void AiActivate::load(ESMReader &esm) diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index bf0e17fa06..da16bf867a 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -96,6 +96,7 @@ namespace ESM float mRemainingDuration; bool mAlwaysFollow; + bool mCommanded; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 84c638d9c7..29d26d013f 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -6,10 +6,12 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) { - // NAM0 sometimes appears here, sometimes further on - mNam0 = 0; + // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that + // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. + // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, + // because any item can theoretically be moved by a script. if (esm.isNextSub ("NAM0")) - esm.getHT (mNam0); + esm.skipHSub(); if (wideRefNum) esm.getHNT (mRefNum, "FRMR", 8); @@ -27,12 +29,12 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) esm.getHNOT (mScale, "XSCL"); mOwner = esm.getHNOString ("ANAM"); - mGlob = esm.getHNOString ("BNAM"); + mGlobalVariable = esm.getHNOString ("BNAM"); mSoul = esm.getHNOString ("XSOL"); mFaction = esm.getHNOString ("CNAM"); - mFactIndex = -2; - esm.getHNOT (mFactIndex, "INDX"); + mFactionRank = -2; + esm.getHNOT (mFactionRank, "INDX"); mGoldValue = 1; mCharge = -1; @@ -60,20 +62,14 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) mKey = esm.getHNOString ("KNAM"); mTrap = esm.getHNOString ("TNAM"); - mFltv = 0; esm.getHNOT (mReferenceBlocked, "UNAM"); - esm.getHNOT (mFltv, "FLTV"); + if (esm.isNextSub("FLTV")) // no longer used + esm.skipHSub(); esm.getHNOT(mPos, "DATA", 24); - // Number of references in the cell? Maximum once in each cell, - // but not always at the beginning, and not always right. In other - // words, completely useless. - // Update: Well, maybe not completely useless. This might actually be - // number_of_references + number_of_references_moved_here_Across_boundaries, - // and could be helpful for collecting these weird moved references. - if (esm.isNextSub ("NAM0")) - esm.getHT (mNam0); + if (esm.isNextSub("NAM0")) + esm.skipHSub(); } void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const @@ -90,12 +86,12 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } esm.writeHNOCString("ANAM", mOwner); - esm.writeHNOCString("BNAM", mGlob); + esm.writeHNOCString("BNAM", mGlobalVariable); esm.writeHNOCString("XSOL", mSoul); esm.writeHNOCString("CNAM", mFaction); - if (mFactIndex != -2) { - esm.writeHNT("INDX", mFactIndex); + if (mFactionRank != -2) { + esm.writeHNT("INDX", mFactionRank); } if (mEnchantmentCharge != -1) @@ -127,14 +123,8 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); - if (!inInventory && mFltv != 0) - esm.writeHNT("FLTV", mFltv); - if (!inInventory) esm.writeHNT("DATA", mPos, 24); - - if (!inInventory && mNam0 != 0) - esm.writeHNT("NAM0", mNam0); } void ESM::CellRef::blank() @@ -144,10 +134,10 @@ void ESM::CellRef::blank() mRefID.clear(); mScale = 1; mOwner.clear(); - mGlob.clear(); + mGlobalVariable.clear(); mSoul.clear(); mFaction.clear(); - mFactIndex = -1; + mFactionRank = -2; mCharge = 0; mEnchantmentCharge = 0; mGoldValue = 0; @@ -156,8 +146,6 @@ void ESM::CellRef::blank() mKey.clear(); mTrap.clear(); mReferenceBlocked = 0; - mFltv = 0; - mNam0 = 0; mTeleport = false; for (int i=0; i<3; ++i) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index b875781209..9c57061b00 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -35,12 +35,13 @@ namespace ESM float mScale; // Scale applied to mesh - // The NPC that owns this object (and will get angry if you steal - // it) + // The NPC that owns this object (and will get angry if you steal it) std::string mOwner; - // I have no idea, looks like a link to a global variable? - std::string mGlob; + // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed + // even if it has an Owner field. + // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. + std::string mGlobalVariable; // ID of creature trapped in this soul gem std::string mSoul; @@ -49,9 +50,8 @@ namespace ESM // you take it and are not a faction member) std::string mFaction; - // INDX might be PC faction rank required to use the item? Sometimes - // is -1, which I assume means "any rank". - int mFactIndex; + // PC faction rank required to use the item. Sometimes is -1, which means "any rank". + int mFactionRank; // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. @@ -82,12 +82,6 @@ namespace ESM // -1 is not blocked, otherwise it is blocked. signed char mReferenceBlocked; - // Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza - // Brindisi Dorom", where it has the value 100. Also only for - // activators. - int mFltv; - int mNam0; - // Position and rotation of this object within the cell Position mPos; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 0a9a361093..21803e02f9 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -33,8 +33,11 @@ void ESM::CreatureStats::load (ESMReader &esm) mAlarmed = false; esm.getHNOT (mAlarmed, "ALRM"); - mHostile = false; - esm.getHNOT (mHostile, "HOST"); + mAttacked = false; + esm.getHNOT (mAttacked, "ATKD"); + + if (esm.isNextSub("HOST")) + esm.skipHSub(); // Hostile, no longer used mAttackingOrSpell = false; esm.getHNOT (mAttackingOrSpell, "ATCK"); @@ -83,6 +86,7 @@ void ESM::CreatureStats::load (ESMReader &esm) mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); + mMagicEffects.load(esm); while (esm.isNextSub("SUMM")) { @@ -142,8 +146,8 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mAlarmed) esm.writeHNT ("ALRM", mAlarmed); - if (mHostile) - esm.writeHNT ("HOST", mHostile); + if (mAttacked) + esm.writeHNT ("ATKD", mAttacked); if (mAttackingOrSpell) esm.writeHNT ("ATCK", mAttackingOrSpell); @@ -193,6 +197,7 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); + mMagicEffects.save(esm); for (std::map::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) { diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 7ae57da16c..8f4d4df7b8 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -11,6 +11,7 @@ #include "spellstate.hpp" #include "activespells.hpp" +#include "magiceffects.hpp" #include "aisequence.hpp" namespace ESM @@ -24,6 +25,8 @@ namespace ESM StatState mAttributes[8]; StatState mDynamic[3]; + MagicEffects mMagicEffects; + AiSequence::AiSequence mAiSequence; bool mHasAiSettings; @@ -43,7 +46,6 @@ namespace ESM bool mTalkedTo; bool mAlarmed; bool mAttacked; - bool mHostile; bool mAttackingOrSpell; bool mKnockdown; bool mKnockdownOneFrame; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp new file mode 100644 index 0000000000..6c05fac2a2 --- /dev/null +++ b/components/esm/debugprofile.cpp @@ -0,0 +1,29 @@ + +#include "debugprofile.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; + +void ESM::DebugProfile::load (ESMReader& esm) +{ + mDescription = esm.getHNString ("DESC"); + mScriptText = esm.getHNString ("SCRP"); + esm.getHNT (mFlags, "FLAG"); +} + +void ESM::DebugProfile::save (ESMWriter& esm) const +{ + esm.writeHNCString ("DESC", mDescription); + esm.writeHNCString ("SCRP", mScriptText); + esm.writeHNT ("FLAG", mFlags); +} + +void ESM::DebugProfile::blank() +{ + mDescription.clear(); + mScriptText.clear(); + mFlags = 0; +} diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp new file mode 100644 index 0000000000..b54e8ff5fc --- /dev/null +++ b/components/esm/debugprofile.hpp @@ -0,0 +1,38 @@ +#ifndef COMPONENTS_ESM_DEBUGPROFILE_H +#define COMPONENTS_ESM_DEBUGPROFILE_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct DebugProfile + { + static unsigned int sRecordId; + + enum Flags + { + Flag_Default = 1, // add to newly opened scene subviews + Flag_BypassNewGame = 2, // bypass regular game startup + Flag_Global = 4 // make available from main menu (i.e. not location specific) + }; + + std::string mId; + + std::string mDescription; + + std::string mScriptText; + + unsigned int mFlags; + + void load (ESMReader& esm); + void save (ESMWriter& esm) const; + + /// Set record to default state (does not touch the ID). + void blank(); + }; +} + +#endif diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f967af274a..d5ba919b0a 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -112,9 +112,12 @@ enum RecNameInts REC_MPRJ = FourCC<'M','P','R','J'>::value, REC_PROJ = FourCC<'P','R','O','J'>::value, REC_DCOU = FourCC<'D','C','O','U'>::value, + REC_MARK = FourCC<'M','A','R','K'>::value, + REC_ENAB = FourCC<'E','N','A','B'>::value, // format 1 - REC_FILT = 0x544C4946 + REC_FILT = 0x544C4946, + REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; } diff --git a/components/esm/effectlist.cpp b/components/esm/effectlist.cpp index bc126846b1..fc9acccf20 100644 --- a/components/esm/effectlist.cpp +++ b/components/esm/effectlist.cpp @@ -7,6 +7,7 @@ namespace ESM { void EffectList::load(ESMReader &esm) { + mList.clear(); ENAMstruct s; while (esm.isNextSub("ENAM")) { esm.getHT(s, 24); diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index ebdb1e41f7..6facee381c 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -250,14 +250,6 @@ void ESMReader::skipRecord() mCtx.leftRec = 0; } -void ESMReader::skipHRecord() -{ - if (!mCtx.leftFile) - return; - getRecHeader(); - skipRecord(); -} - void ESMReader::getRecHeader(uint32_t &flags) { // General error checking diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 6b0bb9a27f..1549e15f53 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -83,7 +83,7 @@ public: // indirectly to the load() method. int mIdx; void setIndex(const int index) {mIdx = index; mCtx.index = index;} - const int getIndex() {return mIdx;} + int getIndex() {return mIdx;} void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} std::vector *getGlobalReaderList() {return mGlobalReaderList;} @@ -216,9 +216,6 @@ public: // already been read void skipRecord(); - // Skip an entire record, including the header (but not the name) - void skipHRecord(); - /* Read record header. This updatesleftFile BEYOND the data that follows the header, ie beyond the entire record. You should use leftRec to orient yourself inside the record itself. diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 06572ce8f4..544f8bbedf 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -6,7 +6,12 @@ namespace ESM { - ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} + ESMWriter::ESMWriter() + : mEncoder (0) + , mRecordCount (0) + , mCounting (true) + , mStream(NULL) + {} unsigned int ESMWriter::getVersion() const { diff --git a/components/esm/globalmap.cpp b/components/esm/globalmap.cpp index f17c071ffc..190329c61e 100644 --- a/components/esm/globalmap.cpp +++ b/components/esm/globalmap.cpp @@ -21,7 +21,7 @@ void ESM::GlobalMap::load (ESMReader &esm) CellId cell; esm.getT(cell.first); esm.getT(cell.second); - mMarkers.push_back(cell); + mMarkers.insert(cell); } } @@ -33,7 +33,7 @@ void ESM::GlobalMap::save (ESMWriter &esm) const esm.write(&mImageData[0], mImageData.size()); esm.endRecord("DATA"); - for (std::vector::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) + for (std::set::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) { esm.startSubRecord("MRK_"); esm.writeT(it->first); diff --git a/components/esm/globalmap.hpp b/components/esm/globalmap.hpp index 158f70a6e5..e89123f898 100644 --- a/components/esm/globalmap.hpp +++ b/components/esm/globalmap.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_ESM_GLOBALMAP_H #include +#include namespace ESM { @@ -26,7 +27,7 @@ namespace ESM std::vector mImageData; typedef std::pair CellId; - std::vector mMarkers; + std::set mMarkers; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index dcbd91140a..467fe54a14 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -12,6 +12,8 @@ void ESM::GlobalScript::load (ESMReader &esm) mRunning = 0; esm.getHNOT (mRunning, "RUN_"); + + mTargetId = esm.getHNOString ("TARG"); } void ESM::GlobalScript::save (ESMWriter &esm) const @@ -22,4 +24,6 @@ void ESM::GlobalScript::save (ESMWriter &esm) const if (mRunning) esm.writeHNT ("RUN_", mRunning); + + esm.writeHNOString ("TARG", mTargetId); } \ No newline at end of file diff --git a/components/esm/globalscript.hpp b/components/esm/globalscript.hpp index 4fb8b7c480..43c859e095 100644 --- a/components/esm/globalscript.hpp +++ b/components/esm/globalscript.hpp @@ -15,6 +15,7 @@ namespace ESM std::string mId; Locals mLocals; int mRunning; + std::string mTargetId; // for targeted scripts void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 5bf38c840d..f8c3a4718a 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -9,6 +9,7 @@ namespace ESM void PartReferenceList::load(ESMReader &esm) { + mParts.clear(); while (esm.isNextSub("INDX")) { PartReference pr; diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 991f4e1855..6be9dd9717 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -46,7 +46,7 @@ enum PartReferenceType // Reference to body parts struct PartReference { - char mPart; + unsigned char mPart; // possible values [0, 26] std::string mMale, mFemale; }; diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index 286e3f96e3..5e9869d247 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -49,10 +49,10 @@ struct BodyPart struct BYDTstruct { - char mPart; - char mVampire; - char mFlags; - char mType; + unsigned char mPart; // mesh part + unsigned char mVampire; // boolean + unsigned char mFlags; + unsigned char mType; // mesh type }; BYDTstruct mData; diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 83864569fb..bbd6696f11 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -58,7 +58,7 @@ void Cell::load(ESMReader &esm, bool saveContext) void Cell::loadCell(ESMReader &esm, bool saveContext) { - mNAM0 = 0; + mRefNumCounter = 0; if (mData.mFlags & Interior) { @@ -92,7 +92,7 @@ void Cell::loadCell(ESMReader &esm, bool saveContext) esm.getHNOT(mMapColor, "NAM5"); } if (esm.isNextSub("NAM0")) { - esm.getHT(mNAM0); + esm.getHT(mRefNumCounter); } if (saveContext) { @@ -113,11 +113,6 @@ void Cell::loadData(ESMReader &esm) esm.getHNT(mData, "DATA", 12); } -void Cell::preLoad(ESMReader &esm) //Can't be "load" because it conflicts with function in esmtool -{ - this->load(esm, false); -} - void Cell::postLoad(ESMReader &esm) { // Save position of the cell references and move on @@ -150,8 +145,8 @@ void Cell::save(ESMWriter &esm) const esm.writeHNT("NAM5", mMapColor); } - if (mNAM0 != 0) - esm.writeHNT("NAM0", mNAM0); + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } void Cell::restore(ESMReader &esm, int iCtx) const @@ -220,7 +215,7 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mWater = 0; mWaterInt = false; mMapColor = 0; - mNAM0 = 0; + mRefNumCounter = 0; mData.mFlags = 0; mData.mX = 0; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index fb4b6b28ac..a24b106d40 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -94,14 +94,16 @@ struct Cell float mWater; // Water level bool mWaterInt; int mMapColor; - int mNAM0; + // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. + // It prevents overwriting previous refNums, even if they were deleted. + // as that would collide with refs when a content file is upgraded. + int mRefNumCounter; // References "leased" from another cell (i.e. a different cell // introduced this ref, and it has been moved here by a plugin) CellRefTracker mLeasedRefs; MovedCellRefTracker mMovedRefs; - void preLoad(ESMReader &esm); void postLoad(ESMReader &esm); // This method is left in for compatibility with esmtool. Parsing moved references currently requires diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 7bdf9f05b1..51a385f069 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -9,6 +9,7 @@ namespace ESM void InventoryList::load(ESMReader &esm) { + mList.clear(); ContItem ci; while (esm.isNextSub("NPCO")) { diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index c9860dcef1..7c98cc35b0 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -10,6 +10,9 @@ namespace ESM void DialInfo::load(ESMReader &esm) { + mQuestStatus = QS_None; + mFactionLess = false; + mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -49,7 +52,6 @@ void DialInfo::load(ESMReader &esm) return; } - mFactionLess = false; if (subName.val == REC_FNAM) { mFaction = esm.getHString(); @@ -104,8 +106,6 @@ void DialInfo::load(ESMReader &esm) return; } - mQuestStatus = QS_None; - if (subName.val == REC_QSTN) mQuestStatus = QS_Name; else if (subName.val == REC_QSTF) diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 1b701229e7..91b062596b 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -19,14 +19,14 @@ void Land::LandData::save(ESMWriter &esm) offsets.mUnk1 = mUnk1; offsets.mUnk2 = mUnk2; - float prevY = mHeights[0], prevX; + float prevY = mHeights[0]; int number = 0; // avoid multiplication for (int i = 0; i < LAND_SIZE; ++i) { float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - prevX = prevY = mHeights[number]; + float prevX = prevY = mHeights[number]; ++number; for (int j = 1; j < LAND_SIZE; ++j) { diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 74eb37197e..ffc2916099 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -25,7 +25,7 @@ struct Light Negative = 0x004, // Negative light - i.e. darkness Flicker = 0x008, Fire = 0x010, - OffDefault = 0x020, // Off by default + OffDefault = 0x020, // Off by default - does not burn while placed in a cell, but can burn when equipped by an NPC FlickerSlow = 0x040, Pulse = 0x080, PulseSlow = 0x100 diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index f601915395..04d55c828a 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -42,8 +42,13 @@ void MagicEffect::load(ESMReader &esm) esm.getHNT(mIndex, "INDX"); esm.getHNT(mData, "MEDT", 36); - if (mIndex>=0 && mIndex=0 && mIndex sNames; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 2fe9fe3c11..ef4b5211bb 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -51,6 +51,7 @@ void NPC::load(ESMReader &esm) else mHasAI = false; + mTransport.clear(); while (esm.isNextSub("DODT") || esm.isNextSub("DNAM")) { if (esm.retSubName() == 0x54444f44) { // DODT struct Dest dodt; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index fd3e45bdc3..0e90108c32 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -80,10 +80,12 @@ struct NPC mPersonality, mLuck; - char mSkills[Skill::Length]; - char mReputation; + // mSkill can grow up to 200, it must be unsigned + unsigned char mSkills[Skill::Length]; + + char mFactionID; unsigned short mHealth, mMana, mFatigue; - char mDisposition, mFactionID, mRank; + signed char mDisposition, mReputation, mRank; char mUnknown; int mGold; }; // 52 bytes @@ -91,9 +93,10 @@ struct NPC struct NPDTstruct12 { short mLevel; - char mDisposition, mReputation, mRank; + // see above + signed char mDisposition, mReputation, mRank; char mUnknown1, mUnknown2, mUnknown3; - int mGold; // ?? not certain + int mGold; }; // 12 bytes struct Dest @@ -103,7 +106,7 @@ struct NPC }; #pragma pack(pop) - char mNpdtType; + unsigned char mNpdtType; NPDTstruct52 mNpdt52; NPDTstruct12 mNpdt12; //for autocalculated characters diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index efdbdd86be..974e928d90 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -27,6 +27,9 @@ void Pathgrid::load(ESMReader &esm) esm.getHNT(mData, "DATA", 12); mCell = esm.getHNString("NAME"); + mPoints.clear(); + mEdges.clear(); + // keep track of total connections so we can reserve edge vector size int edgeCount = 0; diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 0c3ccbffbf..1b08b72172 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -39,6 +39,7 @@ void Region::load(ESMReader &esm) esm.getHNT(mMapColor, "CNAM"); + mSoundList.clear(); while (esm.hasMoreSubs()) { SoundRef sr; diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 1992c951b4..c231b6aa0d 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -24,10 +24,11 @@ struct Region #pragma pack(1) struct WEATstruct { - // I guess these are probabilities - char mClear, mCloudy, mFoggy, mOvercast, mRain, mThunder, mAsh, mBlight, + // These are probabilities that add up to 100 + unsigned char mClear, mCloudy, mFoggy, mOvercast, mRain, mThunder, mAsh, mBlight, // Unknown weather, probably snow and something. Only // present in file version 1.3. + // the engine uses mA as "snow" and mB as "blizard" mA, mB; }; // 10 bytes @@ -35,7 +36,7 @@ struct Region struct SoundRef { NAME32 mSound; - char mChance; + unsigned char mChance; }; // 33 bytes #pragma pack(pop) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index aee8628bd6..19e3f3bb3a 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -22,6 +22,8 @@ void Script::load(ESMReader &esm) mData = data.mData; mId = data.mName.toString(); + mVarNames.clear(); + // List of local variables if (esm.isNextSub("SCVR")) { @@ -38,22 +40,51 @@ void Script::load(ESMReader &esm) char* str = &tmp[0]; for (size_t i = 0; i < mVarNames.size(); i++) { + // Support '\r' terminated strings like vanilla. See Bug #1324. char *termsym = strchr(str, '\r'); if(termsym) *termsym = '\0'; mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; if (str - &tmp[0] > s) - esm.fail("String table overflow"); + { + // Apparently SCVR subrecord is not used and variable names are + // determined on the fly from the script text. Therefore don't throw + // an exeption, just log an error and continue. + std::stringstream ss; + + ss << "ESM Error: " << "String table overflow"; + ss << "\n File: " << esm.getName(); + ss << "\n Record: " << esm.getContext().recName.toString(); + ss << "\n Subrecord: " << "SCVR"; + ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); + std::cerr << ss.str() << std::endl; + break; + } + } } // Script mData - mScriptData.resize(mData.mScriptDataSize); - esm.getHNExact(&mScriptData[0], mScriptData.size(), "SCDT"); + if (esm.isNextSub("SCDT")) + { + mScriptData.resize(mData.mScriptDataSize); + esm.getHExact(&mScriptData[0], mScriptData.size()); + } // Script text mScriptText = esm.getHNOString("SCTX"); + + // NOTE: A minor hack/workaround... + // + // MAO_Containers.esp from Morrowind Acoustic Overhaul has SCVR records + // at the end (see Bug #1849). Since OpenMW does not use SCVR subrecords + // for variable names just skip these as a quick fix. An alternative + // solution would be to decode and validate SCVR subrecords even if they + // appear here. + if (esm.isNextSub("SCVR")) { + esm.skipHSub(); + } } void Script::save(ESMWriter &esm) const { @@ -95,7 +126,11 @@ void Script::save(ESMWriter &esm) const mVarNames.clear(); mScriptData.clear(); - mScriptText = "Begin " + mId + "\n\nEnd " + mId + "\n"; + + if (mId.find ("::")!=std::string::npos) + mScriptText = "Begin \"" + mId + "\"\n\nEnd " + mId + "\n"; + else + mScriptText = "Begin " + mId + "\n\nEnd " + mId + "\n"; } } diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index d5200d4c12..38160e7f41 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -23,29 +23,8 @@ public: struct SCHDstruct { - /* Script name. - - NOTE: You should handle the name "Main" (case insensitive) with - care. With tribunal, modders got the ability to add 'start - scripts' to their mods, which is a script that is run at - startup and which runs throughout the game (I think.) - - However, before Tribunal, there was only one startup script, - called "Main". If mods wanted to make their own start scripts, - they had to overwrite Main. This is obviously problem if - multiple mods to this at the same time. - - Although most mods have switched to using Trib-style startup - scripts, some legacy mods might still overwrite Main, and this - can cause problems if several mods do it. I think the best - course of action is to NEVER overwrite main, but instead add - each with a separate unique name and add them to the start - script list. But there might be other problems with this - approach though. - */ - - // These describe the sizes we need to allocate for the script - // data. + /// Data from script-precompling in the editor. + /// \warning Do not use them. OpenCS currently does not precompile scripts. int mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize; }; // 52 bytes @@ -53,9 +32,16 @@ public: SCHDstruct mData; - std::vector mVarNames; // Variable names - std::vector mScriptData; // Compiled bytecode - std::string mScriptText; // Uncompiled script + /// Variable names generated by script-precompiling in the editor. + /// \warning Do not use this field. OpenCS currently does not precompile scripts. + std::vector mVarNames; + + /// Bytecode generated from script-precompiling in the editor. + /// \warning Do not use this field. OpenCS currently does not precompile scripts. + std::vector mScriptData; + + /// Script source code + std::string mScriptText; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/magiceffects.cpp b/components/esm/magiceffects.cpp new file mode 100644 index 0000000000..898e7e4b18 --- /dev/null +++ b/components/esm/magiceffects.cpp @@ -0,0 +1,29 @@ +#include "magiceffects.hpp" + +#include "esmwriter.hpp" +#include "esmreader.hpp" + +namespace ESM +{ + +void MagicEffects::save(ESMWriter &esm) const +{ + for (std::map::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + esm.writeHNT("EFID", it->first); + esm.writeHNT("BASE", it->second); + } +} + +void MagicEffects::load(ESMReader &esm) +{ + while (esm.isNextSub("EFID")) + { + int id, base; + esm.getHT(id); + esm.getHNT(base, "BASE"); + mEffects.insert(std::make_pair(id, base)); + } +} + +} diff --git a/components/esm/magiceffects.hpp b/components/esm/magiceffects.hpp new file mode 100644 index 0000000000..2a6052caa4 --- /dev/null +++ b/components/esm/magiceffects.hpp @@ -0,0 +1,23 @@ +#ifndef COMPONENTS_ESM_MAGICEFFECTS_H +#define COMPONENTS_ESM_MAGICEFFECTS_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + struct MagicEffects + { + // + std::map mEffects; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 3fa954182c..3e6aed99dd 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (0), mReputation (0) {} +ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} void ESM::NpcStats::load (ESMReader &esm) { @@ -78,8 +78,9 @@ void ESM::NpcStats::load (ESMReader &esm) mLastDrowningHit = 0; esm.getHNOT (mLastDrowningHit, "DRLH"); - mLevelHealthBonus = 0; - esm.getHNOT (mLevelHealthBonus, "LVLH"); + // No longer used + float levelHealthBonus = 0; + esm.getHNOT (levelHealthBonus, "LVLH"); mCrimeId = -1; esm.getHNOT (mCrimeId, "CRID"); @@ -98,7 +99,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("FAEX", expelled); } - if (iter->second.mRank) + if (iter->second.mRank >= 0) esm.writeHNT ("FARA", iter->second.mRank); if (iter->second.mReputation) @@ -148,9 +149,6 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mLastDrowningHit) esm.writeHNT ("DRLH", mLastDrowningHit); - if (mLevelHealthBonus) - esm.writeHNT ("LVLH", mLevelHealthBonus); - if (mCrimeId != -1) esm.writeHNT ("CRID", mCrimeId); } diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index ce7c75d2ae..0061fc05f4 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -46,7 +46,6 @@ namespace ESM std::vector mUsedIds; float mTimeToStartDrowning; float mLastDrowningHit; - float mLevelHealthBonus; int mCrimeId; void load (ESMReader &esm); diff --git a/components/esm/spelllist.cpp b/components/esm/spelllist.cpp index 24d3c3d0a5..aeface6173 100644 --- a/components/esm/spelllist.cpp +++ b/components/esm/spelllist.cpp @@ -7,6 +7,7 @@ namespace ESM { void SpellList::load(ESMReader &esm) { + mList.clear(); while (esm.isNextSub("NPCS")) { mList.push_back(esm.getHString()); } diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index 2dca2dcecc..3ed3329b46 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -27,6 +27,34 @@ namespace ESM mSpells[id] = random; } + while (esm.isNextSub("PERM")) + { + std::string spellId = esm.getHString(); + + std::vector permEffectList; + while (esm.isNextSub("EFID")) + { + PermanentSpellEffectInfo info; + esm.getHT(info.mId); + esm.getHNT(info.mArg, "ARG_"); + esm.getHNT(info.mMagnitude, "MAGN"); + + permEffectList.push_back(info); + } + mPermanentSpellEffects[spellId] = permEffectList; + } + + while (esm.isNextSub("CORP")) + { + std::string id = esm.getHString(); + + CorprusStats stats; + esm.getHNT(stats.mWorsenings, "WORS"); + esm.getHNT(stats.mNextWorsening, "TIME"); + + mCorprusSpells[id] = stats; + } + while (esm.isNextSub("USED")) { std::string id = esm.getHString(); @@ -53,6 +81,28 @@ namespace ESM } } + for (std::map >::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + { + esm.writeHNString("PERM", it->first); + + const std::vector & effects = it->second; + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + esm.writeHNT("EFID", effectIt->mId); + esm.writeHNT("ARG_", effectIt->mArg); + esm.writeHNT("MAGN", effectIt->mMagnitude); + } + } + + for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + { + esm.writeHNString("CORP", it->first); + + const CorprusStats & stats = it->second; + esm.writeHNT("WORS", stats.mWorsenings); + esm.writeHNT("TIME", stats.mNextWorsening); + } + for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) { esm.writeHNString("USED", it->first); diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index cb5c0ff0db..2ab27e908b 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESM_SPELLSTATE_H #include +#include #include #include "defs.hpp" @@ -13,9 +14,26 @@ namespace ESM struct SpellState { + struct CorprusStats + { + int mWorsenings; + TimeStamp mNextWorsening; + }; + + struct PermanentSpellEffectInfo + { + int mId; + int mArg; + float mMagnitude; + }; + typedef std::map > TContainer; TContainer mSpells; + std::map > mPermanentSpellEffects; + + std::map mCorprusSpells; + std::map mUsedPowers; std::string mSelectedSpell; diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp index 217ec4407e..4127a9d627 100644 --- a/components/esm/variant.cpp +++ b/components/esm/variant.cpp @@ -17,6 +17,30 @@ namespace ESM::Variant::Variant() : mType (VT_None), mData (0) {} +ESM::Variant::Variant(const std::string &value) +{ + mData = 0; + mType = VT_None; + setType(VT_String); + setString(value); +} + +ESM::Variant::Variant(int value) +{ + mData = 0; + mType = VT_None; + setType(VT_Long); + setInteger(value); +} + +ESM::Variant::Variant(float value) +{ + mData = 0; + mType = VT_None; + setType(VT_Float); + setFloat(value); +} + ESM::Variant::~Variant() { delete mData; diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index 8ba9bb34f1..d6c1a5489f 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -38,6 +38,10 @@ namespace ESM Variant(); + Variant (const std::string& value); + Variant (int value); + Variant (float value); + ~Variant(); Variant& operator= (const Variant& variant); @@ -83,4 +87,4 @@ namespace ESM bool operator!= (const Variant& left, const Variant& right); } -#endif \ No newline at end of file +#endif diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp new file mode 100644 index 0000000000..585c1495d5 --- /dev/null +++ b/components/esmterrain/storage.cpp @@ -0,0 +1,530 @@ +#include "storage.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace ESMTerrain +{ + + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + { + assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); + + /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int cellX = origin.x; + int cellY = origin.y; + + const ESM::Land* land = getLand(cellX, cellY); + if (!land) + return false; + + min = std::numeric_limits().max(); + max = -std::numeric_limits().max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } + } + return true; + } + + void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + } + + void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + { + Ogre::Vector3 n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row); + fixNormal(n2, cellX, cellY, col-1, row); + fixNormal(n3, cellX, cellY, col, row+1); + fixNormal(n4, cellX, cellY, col, row-1); + normal = (n1+n2+n3+n4); + normal.normalise(); + } + + void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mLandData->mUsingColours) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + } + + void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = 1 << lodLevel; + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int startX = origin.x; + int startY = origin.y; + + size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; + + colours.resize(numVerts*numVerts*4); + positions.resize(numVerts*numVerts*3); + normals.resize(numVerts*numVerts*3); + + Ogre::Vector3 normal; + Ogre::ColourValue color; + + float vertY; + float vertX; + + float vertY_ = 0; // of current cell corner + for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + { + ESM::Land* land = getLand(cellX, cellY); + if (land && !land->mHasData) + land = NULL; + bool hasColors = land && land->mLandData->mUsingColours; + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + if (colStart == 0 && vertY_ != 0) + colStart += increment; + if (rowStart == 0 && vertX_ != 0) + rowStart += increment; + + vertY = vertY_; + for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + else + positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; + + if (land) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row); + + assert(normal.z > 0); + + normals[vertX*numVerts*3 + vertY*3] = normal.x; + normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; + normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; + + if (hasColors) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row); + + color.a = 1; + Ogre::uint32 rsColor; + Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); + memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area + } + + Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, + int x, int y) + { + // For the first/last row/column, we need to get the texture from the neighbour cell + // to get consistent blending at the borders + --x; + if (x < 0) + { + --cellX; + x += ESM::Land::LAND_TEXTURE_SIZE; + } + if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? + { + ++cellY; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + + assert(xmLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin + return std::make_pair(tex, land->mPlugin); + } + else + return std::make_pair(0,0); + } + + std::string Storage::getTextureName(UniqueTextureId id) + { + if (id.first == 0) + return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded? + + // NB: All vtex ids are +1 compared to the ltex ids + const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + + //TODO this is needed due to MWs messed up texture handling + std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture); + + return texture; + } + + void Storage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) + { + for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) + { + out.push_back(Terrain::LayerCollection()); + out.back().mTarget = *it; + getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); + } + } + + void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); + } + + void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + // TODO - blending isn't completely right yet; the blending radius appears to be + // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap + // and interpolate the rest of the cell by hand? :/ + + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); + int cellX = origin.x; + int cellY = origin.y; + + // Save the used texture indices so we know the total number of textures + // and number of required blend maps + std::set textureIndices; + // Due to the way the blending works, the base layer will always shine through in between + // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). + // To get a consistent look, we need to make sure to use the same base layer in all cells. + // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. + textureIndices.insert(std::make_pair(0,0)); + + for (int y=0; y textureIndicesMap; + for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) + { + int size = textureIndicesMap.size(); + textureIndicesMap[*it] = size; + layerList.push_back(getLayerInfo(getTextureName(*it))); + } + + int numTextures = textureIndices.size(); + // numTextures-1 since the base layer doesn't need blending + int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); + + int channels = pack ? 4 : 1; + + // Second iteration - create and fill in the blend maps + const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; + + for (int i=0; isecond; + int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); + int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; + + if (blendIndex == i) + pData[y*blendmapSize*channels + x*channels + channel] = 255; + else + pData[y*blendmapSize*channels + x*channels + channel] = 0; + } + } + blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); + } + } + + float Storage::getHeightAt(const Ogre::Vector3 &worldPos) + { + int cellX = std::floor(worldPos.x / 8192.f); + int cellY = std::floor(worldPos.y / 8192.f); + + ESM::Land* land = getLand(cellX, cellY); + if (!land) + return -2048; + + // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition + + // Normalized position in the cell + float nX = (worldPos.x - (cellX * 8192))/8192.f; + float nY = (worldPos.y - (cellY * 8192))/8192.f; + + // get left / bottom points (rounded down) + float factor = ESM::Land::LAND_SIZE - 1.0f; + float invFactor = 1.0f / factor; + + int startX = static_cast(nX * factor); + int startY = static_cast(nY * factor); + int endX = startX + 1; + int endY = startY + 1; + + assert(endX < ESM::Land::LAND_SIZE); + assert(endY < ESM::Land::LAND_SIZE); + + // now get points in terrain space (effectively rounding them to boundaries) + float startXTS = startX * invFactor; + float startYTS = startY * invFactor; + float endXTS = endX * invFactor; + float endYTS = endY * invFactor; + + // get parametric from start coord to next point + float xParam = (nX - startXTS) * factor; + float yParam = (nY - startYTS) * factor; + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + + // Build all 4 positions in normalized cell space, using point-sampled height + Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + // define this plane in terrain space + Ogre::Plane plane; + // (At the moment, all rows have the same triangle alignment) + if (true) + { + // odd row + bool secondTri = ((1.0 - yParam) > xParam); + if (secondTri) + plane.redefine(v0, v1, v3); + else + plane.redefine(v1, v2, v3); + } + else + { + // even row + bool secondTri = (yParam > xParam); + if (secondTri) + plane.redefine(v0, v2, v3); + else + plane.redefine(v0, v1, v2); + } + + // Solve plane equation for z + return (-plane.normal.x * nX + -plane.normal.y * nY + - plane.d) / plane.normal.z * 8192; + + } + + float Storage::getVertexHeight(const ESM::Land *land, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + + Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + Terrain::LayerInfo info; + info.mParallax = false; + info.mSpecular = false; + info.mDiffuseMap = texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + { + info.mNormalMap = texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + info.mNormalMap = texture_; + } + + texture_ = texture; + boost::replace_last(texture_, ".", "_diffusespec."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + { + info.mDiffuseMap = texture_; + info.mSpecular = true; + } + + // This wasn't cached, so the textures are probably not loaded either. + // Background load them so they are hopefully already loaded once we need them! + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); + if (!info.mNormalMap.empty()) + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); + + mLayerInfoMap[texture] = info; + + return info; + } + + Terrain::LayerInfo Storage::getDefaultLayer() + { + Terrain::LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + info.mSpecular = false; + return info; + } + + float Storage::getCellWorldSize() + { + return ESM::Land::REAL_SIZE; + } + + int Storage::getCellVertices() + { + return ESM::Land::LAND_SIZE; + } + +} diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp new file mode 100644 index 0000000000..d25f7552b7 --- /dev/null +++ b/components/esmterrain/storage.hpp @@ -0,0 +1,117 @@ +#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H +#define COMPONENTS_ESM_TERRAIN_STORAGE_H + +#include + +#include +#include + +namespace ESMTerrain +{ + + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) + /// into the terrain component, converting it on the fly as needed. + class Storage : public Terrain::Storage + { + 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::LandTexture* getLandTexture(int index, short plugin) = 0; + + public: + + // 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; + + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. + /// Larger chunks can simply merge AABB of children. + /// @param size size of the chunk in cell units + /// @param center center of the chunk in cell units + /// @param min min height will be stored here + /// @param max max height will be stored here + /// @return true if there was data available for this terrain chunk + virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + + /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. + /// @param lodLevel LOD level, 0 = most detailed + /// @param size size of the terrain chunk in cell units + /// @param center center of the chunk in cell units + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours); + + /// Create textures holding layer blend values for a terrain chunk. + /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. + /// @param chunkSize size of the terrain chunk in cell units + /// @param chunkCenter center of the chunk in cell units + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + /// @param blendmaps created blendmaps will be written here + /// @param layerList names of the layer textures used will be written here + virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); + + virtual float getHeightAt (const Ogre::Vector3& worldPos); + + virtual Terrain::LayerInfo getDefaultLayer(); + + /// Get the transformation factor for mapping cell units to world units. + virtual float getCellWorldSize(); + + /// Get the number of vertices on one side for each cell. Should be (power of two)+1 + virtual int getCellVertices(); + + private: + void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); + void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + float getVertexHeight (const ESM::Land* land, int x, int y); + + // Since plugins can define new texture palettes, we need to know the plugin index too + // in order to retrieve the correct texture name. + // pair + typedef std::pair UniqueTextureId; + + UniqueTextureId getVtexIndexAt(int cellX, int cellY, + int x, int y); + std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + Terrain::LayerInfo getLayerInfo(const std::string& texture); + + // Non-virtual + void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + }; + +} + +#endif diff --git a/components/files/androidpath.cpp b/components/files/androidpath.cpp new file mode 100644 index 0000000000..52ae73803b --- /dev/null +++ b/components/files/androidpath.cpp @@ -0,0 +1,94 @@ +#include "androidpath.hpp" + +#if defined(__ANDROID__) + +#include +#include +#include +#include +#include + +namespace +{ + boost::filesystem::path getUserHome() + { + const char* dir = getenv("HOME"); + if (dir == NULL) + { + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) + { + dir = pwd->pw_dir; + } + } + if (dir == NULL) + return boost::filesystem::path(); + else + return boost::filesystem::path(dir); + } + + boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback) + { + const char* result = getenv(envVariable.c_str()); + if (!result) + return fallback; + boost::filesystem::path dir(result); + if (dir.empty()) + return fallback; + else + return dir; + } +} + +/** + * \namespace Files + */ +namespace Files +{ + +AndroidPath::AndroidPath(const std::string& application_name) + : mName(application_name) +{ +} + +boost::filesystem::path AndroidPath::getUserConfigPath() const +{ + return getEnv("XDG_CONFIG_HOME", "/sdcard/morrowind/config") / mName; +} + +boost::filesystem::path AndroidPath::getUserDataPath() const +{ + return getEnv("XDG_DATA_HOME", "/sdcard/morrowind/share") / mName; +} + +boost::filesystem::path AndroidPath::getCachePath() const +{ + return getEnv("XDG_CACHE_HOME", "/sdcard/morrowind/cache") / mName; +} + +boost::filesystem::path AndroidPath::getGlobalConfigPath() const +{ + boost::filesystem::path globalPath("/sdcard/morrowind/"); + return globalPath / mName; +} + +boost::filesystem::path AndroidPath::getLocalPath() const +{ + return boost::filesystem::path("./"); +} + +boost::filesystem::path AndroidPath::getGlobalDataPath() const +{ + boost::filesystem::path globalDataPath("/sdcard/morrowind/data"); + return globalDataPath / mName; +} + +boost::filesystem::path AndroidPath::getInstallPath() const +{ + return boost::filesystem::path(); +} + + +} /* namespace Files */ + +#endif /* defined(__Android__) */ diff --git a/components/files/androidpath.hpp b/components/files/androidpath.hpp new file mode 100644 index 0000000000..792462fc65 --- /dev/null +++ b/components/files/androidpath.hpp @@ -0,0 +1,54 @@ +#ifndef COMPONENTS_FILES_ANDROIDPATH_H +#define COMPONENTS_FILES_ANDROIDPATH_H + +#if defined(__ANDROID__) + +#include +/** + * \namespace Files + */ +namespace Files +{ + +struct AndroidPath +{ + AndroidPath(const std::string& application_name); + + /** + * \brief Return path to the user directory. + */ + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; + + /** + * \brief Return path to the global (system) directory where config files can be placed. + */ + boost::filesystem::path getGlobalConfigPath() const; + + /** + * \brief Return path to the runtime configuration directory which is the + * place where an application was started. + */ + boost::filesystem::path getLocalPath() const; + + /** + * \brief Return path to the global (system) directory where game files can be placed. + */ + boost::filesystem::path getGlobalDataPath() const; + + /** + * \brief + */ + boost::filesystem::path getCachePath() const; + + boost::filesystem::path getInstallPath() const; + + std::string mName; +}; + +} /* namespace Files */ + +#endif /* defined(__Android__) */ + +#endif /* COMPONENTS_FILES_ANDROIDPATH_H */ diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 58d75d1fd7..942f47d4e1 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -16,13 +16,19 @@ namespace Files static const char* const openmwCfgFile = "openmw.cfg"; +#if defined(_WIN32) || defined(__WINDOWS__) +static const char* const applicationName = "OpenMW"; +#else +static const char* const applicationName = "openmw"; +#endif + const char* const mwToken = "?mw?"; const char* const localToken = "?local?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; ConfigurationManager::ConfigurationManager() - : mFixedPath("openmw") + : mFixedPath(applicationName) { setupTokensMapping(); diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp index cfd3458ce1..9fb36d9845 100644 --- a/components/files/fixedpath.hpp +++ b/components/files/fixedpath.hpp @@ -4,10 +4,14 @@ #include #include -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#ifndef ANDROID #include namespace Files { typedef LinuxPath TargetPathType; } - +#else + #include + namespace Files { typedef AndroidPath TargetPathType; } +#endif #elif defined(__WIN32) || defined(__WINDOWS__) || defined(_WIN32) #include namespace Files { typedef WindowsPath TargetPathType; } @@ -87,6 +91,7 @@ struct FixedPath return mLocalPath; } + const boost::filesystem::path& getInstallPath() const { return mInstallPath; diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index d285f4229c..a105bb928d 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -1,6 +1,6 @@ #include "linuxpath.hpp" -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #include @@ -69,7 +69,7 @@ boost::filesystem::path LinuxPath::getCachePath() const boost::filesystem::path LinuxPath::getGlobalConfigPath() const { - boost::filesystem::path globalPath("/etc/"); + boost::filesystem::path globalPath(GLOBAL_CONFIG_PATH); return globalPath / mName; } @@ -80,7 +80,7 @@ boost::filesystem::path LinuxPath::getLocalPath() const boost::filesystem::path LinuxPath::getGlobalDataPath() const { - boost::filesystem::path globalDataPath("/usr/share/games/"); + boost::filesystem::path globalDataPath(GLOBAL_DATA_PATH); return globalDataPath / mName; } @@ -157,4 +157,4 @@ boost::filesystem::path LinuxPath::getInstallPath() const } /* namespace Files */ -#endif /* defined(__linux__) || defined(__FreeBSD__) */ +#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) */ diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp index b710165b44..ba9756fc00 100644 --- a/components/files/linuxpath.hpp +++ b/components/files/linuxpath.hpp @@ -1,7 +1,7 @@ #ifndef COMPONENTS_FILES_LINUXPATH_H #define COMPONENTS_FILES_LINUXPATH_H -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include @@ -56,6 +56,6 @@ struct LinuxPath } /* namespace Files */ -#endif /* defined(__linux__) || defined(__FreeBSD__) */ +#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) */ #endif /* COMPONENTS_FILES_LINUXPATH_H */ diff --git a/apps/openmw/mwgui/fontloader.cpp b/components/fontloader/fontloader.cpp similarity index 84% rename from apps/openmw/mwgui/fontloader.cpp rename to components/fontloader/fontloader.cpp index 92d9a25b61..e01e4b7bc2 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -123,7 +123,7 @@ namespace } -namespace MWGui +namespace Gui { FontLoader::FontLoader(ToUTF8::FromType encoding) @@ -134,7 +134,7 @@ namespace MWGui mEncoding = encoding; } - void FontLoader::loadAllFonts() + void FontLoader::loadAllFonts(bool exportToFile) { Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) @@ -142,7 +142,7 @@ namespace MWGui Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "*.fnt"); for (Ogre::StringVector::iterator resource = resourcesInThisGroup->begin(); resource != resourcesInThisGroup->end(); ++resource) { - loadFont(*resource); + loadFont(*resource, exportToFile); } } } @@ -168,7 +168,7 @@ namespace MWGui float ascent; } GlyphInfo; - void FontLoader::loadFont(const std::string &fileName) + void FontLoader::loadFont(const std::string &fileName, bool exportToFile) { Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName); @@ -221,6 +221,9 @@ namespace MWGui width, height, 0, Ogre::PF_BYTE_RGBA); texture->loadImage(image); + if (exportToFile) + image.save(resourceName + ".png"); + // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); @@ -240,10 +243,10 @@ namespace MWGui for(int i = 0; i < 256; i++) { - int x1 = data[i].top_left.x*width; - int y1 = data[i].top_left.y*height; - int w = data[i].top_right.x*width - x1; - int h = data[i].bottom_left.y*height - y1; + float x1 = data[i].top_left.x*width; + float y1 = data[i].top_left.y*height; + float w = data[i].top_right.x*width - x1; + float h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding)); @@ -257,16 +260,38 @@ namespace MWGui code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); // More hacks! The french game uses several win1252 characters that are not included // in the cp437 encoding of the font. Fall back to similar available characters. if (mEncoding == ToUTF8::CP437) { - std::multimap additional; + std::multimap additional; // additional.insert(std::make_pair(39, 0x2019)); // apostrophe additional.insert(std::make_pair(45, 0x2013)); // dash + additional.insert(std::make_pair(45, 0x2014)); // dash additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark + additional.insert(std::make_pair(44, 0x201A)); + additional.insert(std::make_pair(44, 0x201E)); + additional.insert(std::make_pair(43, 0x2020)); + additional.insert(std::make_pair(94, 0x02C6)); + additional.insert(std::make_pair(37, 0x2030)); + additional.insert(std::make_pair(83, 0x0160)); + additional.insert(std::make_pair(60, 0x2039)); + additional.insert(std::make_pair(79, 0x0152)); + additional.insert(std::make_pair(90, 0x017D)); + additional.insert(std::make_pair(39, 0x2019)); + additional.insert(std::make_pair(126, 0x02DC)); + additional.insert(std::make_pair(84, 0x2122)); + additional.insert(std::make_pair(83, 0x0161)); + additional.insert(std::make_pair(62, 0x203A)); + additional.insert(std::make_pair(111, 0x0153)); + additional.insert(std::make_pair(122, 0x017E)); + additional.insert(std::make_pair(89, 0x0178)); + additional.insert(std::make_pair(156, 0x00A2)); + additional.insert(std::make_pair(46, 0x2026)); + for (std::multimap::iterator it = additional.begin(); it != additional.end(); ++it) { if (it->first != i) @@ -281,6 +306,7 @@ namespace MWGui code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } } @@ -296,6 +322,7 @@ namespace MWGui cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } // Question mark, use for NotDefined marker (used for glyphs not existing in the font) @@ -310,6 +337,7 @@ namespace MWGui cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } } @@ -327,7 +355,13 @@ namespace MWGui cursorCode->addAttribute("coord", "0 0 0 0"); cursorCode->addAttribute("advance", "0"); cursorCode->addAttribute("bearing", "0 0"); + cursorCode->addAttribute("size", "0 0"); + } + if (exportToFile) + { + xmlDocument.createDeclaration(); + xmlDocument.save(resourceName + ".xml"); } font->deserialization(root, MyGUI::Version(3,2,0)); diff --git a/components/fontloader/fontloader.hpp b/components/fontloader/fontloader.hpp new file mode 100644 index 0000000000..a41506dbb4 --- /dev/null +++ b/components/fontloader/fontloader.hpp @@ -0,0 +1,28 @@ +#ifndef MWGUI_FONTLOADER_H +#define MWGUI_FONTLOADER_H + +#include + +namespace Gui +{ + + + /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and Ogre + class FontLoader + { + public: + FontLoader (ToUTF8::FromType encoding); + + /// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files? + void loadAllFonts (bool exportToFile); + + private: + ToUTF8::FromType mEncoding; + + /// @param exportToFile export the converted font (Image and XML with glyph metrics) to files? + void loadFont (const std::string& fileName, bool exportToFile); + }; + +} + +#endif diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 97e4fad4fc..881687366b 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -81,7 +81,7 @@ namespace Interpreter virtual bool isScriptRunning (const std::string& name) const = 0; - virtual void startScript (const std::string& name) = 0; + virtual void startScript (const std::string& name, const std::string& targetId = "") = 0; virtual void stopScript (const std::string& name) = 0; @@ -108,6 +108,8 @@ namespace Interpreter virtual void setMemberFloat (const std::string& id, const std::string& name, float value, bool global) = 0; + + virtual std::string getTargetId() const = 0; }; } diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt index 990762268f..5d1eba088d 100644 --- a/components/interpreter/docs/vmformat.txt +++ b/components/interpreter/docs/vmformat.txt @@ -133,5 +133,6 @@ op 67: store stack[0] in member float stack[2] of global script with ID stack[1] op 68: replace stack[0] with member short stack[1] of global script with ID stack[0] op 69: replace stack[0] with member short stack[1] of global script with ID stack[0] op 70: replace stack[0] with member short stack[1] of global script with ID stack[0] -opcodes 71-33554431 unused +op 71: explicit reference (target) = stack[0]; pop; start script stack[0] and pop +opcodes 72-33554431 unused opcodes 33554432-67108863 reserved for extensions diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index 721cde3d8d..d705a109c7 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -113,6 +113,7 @@ namespace Interpreter interpreter.installSegment5 (46, new OpScriptRunning); interpreter.installSegment5 (47, new OpStartScript); interpreter.installSegment5 (48, new OpStopScript); + interpreter.installSegment5 (71, new OpStartScriptExplicit); // spacial interpreter.installSegment5 (49, new OpGetDistance); diff --git a/components/interpreter/scriptopcodes.hpp b/components/interpreter/scriptopcodes.hpp index 56502d510a..976390eb59 100644 --- a/components/interpreter/scriptopcodes.hpp +++ b/components/interpreter/scriptopcodes.hpp @@ -10,36 +10,52 @@ namespace Interpreter class OpScriptRunning : public Opcode0 { public: - + virtual void execute (Runtime& runtime) { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime[0].mInteger = runtime.getContext().isScriptRunning (name); - } + } }; class OpStartScript : public Opcode0 { public: - + virtual void execute (Runtime& runtime) { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - runtime.getContext().startScript (name); - } + runtime.getContext().startScript (name, runtime.getContext().getTargetId()); + } + }; + + class OpStartScriptExplicit : public Opcode0 + { + public: + + virtual void execute (Runtime& runtime) + { + std::string targetId = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + runtime.getContext().startScript (name, targetId); + } }; class OpStopScript : public Opcode0 { public: - + virtual void execute (Runtime& runtime) { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); runtime.getContext().stopScript (name); - } + } }; } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp new file mode 100644 index 0000000000..9eaf441ef0 --- /dev/null +++ b/components/misc/resourcehelpers.cpp @@ -0,0 +1,90 @@ +#include "resourcehelpers.hpp" + +#include + +#include + +bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) +{ + Ogre::String::size_type pos = path.rfind('.'); + if(pos != Ogre::String::npos && path.compare(pos, path.length() - pos, ".dds") != 0) + { + path.replace(pos, path.length(), ".dds"); + return true; + } + return false; +} + +std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath) +{ + /* Bethesda at some point converted all their BSA + * textures from tga to dds for increased load speed, but all + * texture file name references were kept as .tga. + */ + + std::string prefix1 = topLevelDirectory + '\\'; + std::string prefix2 = topLevelDirectory + '/'; + + std::string correctedPath = resPath; + Misc::StringUtils::toLower(correctedPath); + + // Apparently, leading separators are allowed + while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) + correctedPath.erase(0, 1); + + if(correctedPath.compare(0, prefix1.size(), prefix1.data()) != 0 && + correctedPath.compare(0, prefix2.size(), prefix2.data()) != 0) + correctedPath = prefix1 + correctedPath; + + std::string origExt = correctedPath; + + // since we know all (GOTY edition or less) textures end + // in .dds, we change the extension + if (changeExtensionToDds(correctedPath)) + { + // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) + // verify, and revert if false (this call succeeds quickly, but fails slowly) + if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(correctedPath)) + { + return origExt; + } + } + + return correctedPath; +} + +std::string Misc::ResourceHelpers::correctTexturePath(const std::string &resPath) +{ + static const std::string dir = "textures"; + return correctResourcePath(dir, resPath); +} + +std::string Misc::ResourceHelpers::correctIconPath(const std::string &resPath) +{ + static const std::string dir = "icons"; + return correctResourcePath(dir, resPath); +} + +std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath) +{ + static const std::string dir = "bookart"; + std::string image = correctResourcePath(dir, resPath); + + return image; +} + +std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, int width, int height) +{ + std::string image = correctBookartPath(resPath); + + // Apparently a bug with some morrowind versions, they reference the image without the size suffix. + // So if the image isn't found, try appending the size. + if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(image)) + { + std::stringstream str; + str << image.substr(0, image.rfind('.')) << "_" << width << "_" << height << image.substr(image.rfind('.')); + image = Misc::ResourceHelpers::correctBookartPath(str.str()); + } + + return image; +} diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp new file mode 100644 index 0000000000..3cf0f4c279 --- /dev/null +++ b/components/misc/resourcehelpers.hpp @@ -0,0 +1,19 @@ +#ifndef MISC_RESOURCEHELPERS_H +#define MISC_RESOURCEHELPERS_H + +#include + +namespace Misc +{ + namespace ResourceHelpers + { + bool changeExtensionToDds(std::string &path); + std::string correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath); + std::string correctTexturePath(const std::string &resPath); + std::string correctIconPath(const std::string &resPath); + std::string correctBookartPath(const std::string &resPath); + std::string correctBookartPath(const std::string &resPath, int width, int height); + } +} + +#endif diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 5e9dde2514..4856d65039 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -73,7 +73,7 @@ public: while (cur != eoc) { if ((*cur & 0xC0) != 0x80) // check continuation mark - return std::make_pair (sBadChar(), cur);; + return std::make_pair (sBadChar(), cur); chr = (chr << 6) | UnicodeChar ((*cur++) & 0x3F); } diff --git a/components/nif/data.cpp b/components/nif/data.cpp new file mode 100644 index 0000000000..4248b93d2f --- /dev/null +++ b/components/nif/data.cpp @@ -0,0 +1,29 @@ +#include "data.hpp" +#include "node.hpp" + +namespace Nif +{ +void NiSkinInstance::post(NIFFile *nif) +{ + data.post(nif); + root.post(nif); + bones.post(nif); + + if(data.empty() || root.empty()) + nif->fail("NiSkinInstance missing root or data"); + + size_t bnum = bones.length(); + if(bnum != data->bones.size()) + nif->fail("Mismatch in NiSkinData bone count"); + + root->makeRootBone(&data->trafo); + + for(size_t i=0; ifail("Oops: Missing bone! Don't know how to handle this."); + bones[i]->makeBone(i, data->bones[i]); + } +} + +} // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index e943887682..34ef3a34ed 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -25,9 +25,8 @@ #define OPENMW_COMPONENTS_NIF_DATA_HPP #include "controlled.hpp" - -#include -#include +#include "nifstream.hpp" +#include "nifkey.hpp" namespace Nif { @@ -211,7 +210,7 @@ public: class NiPosData : public Record { public: - Vector3KeyList mKeyList; + Vector3KeyMap mKeyList; void read(NIFStream *nif) { @@ -222,7 +221,7 @@ public: class NiUVData : public Record { public: - FloatKeyList mKeyList[4]; + FloatKeyMap mKeyList[4]; void read(NIFStream *nif) { @@ -234,7 +233,7 @@ public: class NiFloatData : public Record { public: - FloatKeyList mKeyList; + FloatKeyMap mKeyList; void read(NIFStream *nif) { @@ -284,11 +283,11 @@ public: class NiColorData : public Record { public: - Vector4KeyList mKeyList; + Vector4KeyMap mKeyMap; void read(NIFStream *nif) { - mKeyList.read(nif); + mKeyMap.read(nif); } }; @@ -389,7 +388,7 @@ public: struct NiMorphData : public Record { struct MorphData { - FloatKeyList mData; + FloatKeyMap mData; std::vector mVertices; }; std::vector mMorphs; @@ -412,11 +411,11 @@ struct NiMorphData : public Record struct NiKeyframeData : public Record { - QuaternionKeyList mRotations; + QuaternionKeyMap mRotations; //\FIXME mXYZ_Keys are read, but not used. - FloatKeyList mXYZ_Keys; - Vector3KeyList mTranslations; - FloatKeyList mScales; + FloatKeyMap mXYZ_Keys; + Vector3KeyMap mTranslations; + FloatKeyMap mScales; void read(NIFStream *nif) { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 84f4aaceeb..2240d65b07 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -1,177 +1,15 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (nif_file.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - #include "niffile.hpp" -#include "record.hpp" -#include "components/misc/stringops.hpp" - -#include "extra.hpp" -#include "controlled.hpp" -#include "node.hpp" -#include "property.hpp" -#include "data.hpp" #include "effect.hpp" -#include "controller.hpp" -#include +#include -//TODO: when threading is needed, enable these -//#include -#include +#include namespace Nif { -class NIFFile::LoadedCache -{ - //TODO: enable this to make cache thread safe... - //typedef boost::mutex mutex; - - struct mutex - { - void lock () {}; - void unlock () {} - }; - - typedef boost::lock_guard lock_guard; - typedef std::map < std::string, boost::weak_ptr > loaded_map; - typedef std::vector < boost::shared_ptr > locked_files; - - static int sLockLevel; - static mutex sProtector; - static loaded_map sLoadedMap; - static locked_files sLockedFiles; - -public: - - static ptr create (const std::string &name) - { - lock_guard _ (sProtector); - - ptr result; - - // lookup the resource - loaded_map::iterator i = sLoadedMap.find (name); - - if (i == sLoadedMap.end ()) // it doesn't existing currently, - { // or hasn't in the very near past - - // create it now, for smoother threading if needed, the - // loading should be performed outside of the sLoaderMap - // lock and an alternate mechanism should be used to - // synchronize threads competing to load the same resource - result = boost::make_shared (name, psudo_private_modifier()); - - // if we are locking the cache add an extra reference - // to keep the file in memory - if (sLockLevel > 0) - sLockedFiles.push_back (result); - - // stash a reference to the resource so that future - // calls can benefit - sLoadedMap [name] = boost::weak_ptr (result); - } - else // it may (probably) still exists - { - // attempt to get the reference - result = i->second.lock (); - - if (!result) // resource is in the process of being destroyed - { - // create a new instance, to replace the one that has - // begun the irreversible process of being destroyed - result = boost::make_shared (name, psudo_private_modifier()); - - // respect the cache lock... - if (sLockLevel > 0) - sLockedFiles.push_back (result); - - // we potentially overwrite an expired pointer here - // but the other thread performing the delete on - // the previous copy of this resource will detect it - // and make sure not to erase the new reference - sLoadedMap [name] = boost::weak_ptr (result); - } - } - - // we made it! - return result; - } - - static void release (NIFFile * file) - { - lock_guard _ (sProtector); - - loaded_map::iterator i = sLoadedMap.find (file->filename); - - // its got to be in here, it just might not be us... - assert (i != sLoadedMap.end ()); - - // if weak_ptr is still expired, this resource hasn't been recreated - // between the initiation of the final release due to destruction - // of the last shared pointer and this thread acquiring the lock on - // the loader map - if (i->second.expired ()) - sLoadedMap.erase (i); - } - - static void lockCache () - { - lock_guard _ (sProtector); - - sLockLevel++; - } - - static void unlockCache () - { - locked_files resetList; - - { - lock_guard _ (sProtector); - - if (--sLockLevel) - sLockedFiles.swap(resetList); - } - - // this not necessary, but makes it clear that the - // deletion of the locked cache entries is being done - // outside the protection of sProtector - resetList.clear (); - } -}; - -int NIFFile::LoadedCache::sLockLevel = 0; -NIFFile::LoadedCache::mutex NIFFile::LoadedCache::sProtector; -NIFFile::LoadedCache::loaded_map NIFFile::LoadedCache::sLoadedMap; -NIFFile::LoadedCache::locked_files NIFFile::LoadedCache::sLockedFiles; - -// these three calls are forwarded to the cache implementation... -void NIFFile::lockCache () { LoadedCache::lockCache (); } -void NIFFile::unlockCache () { LoadedCache::unlockCache (); } -NIFFile::ptr NIFFile::create (const std::string &name) { return LoadedCache::create (name); } - /// Open a NIF stream. The name is used for error messages. -NIFFile::NIFFile(const std::string &name, psudo_private_modifier) +NIFFile::NIFFile(const std::string &name) : ver(0) , filename(name) { @@ -180,10 +18,10 @@ NIFFile::NIFFile(const std::string &name, psudo_private_modifier) NIFFile::~NIFFile() { - LoadedCache::release (this); - - for(std::size_t i=0; i::iterator it = records.begin() ; it != records.end(); ++it) + { + delete *it; + } } template static Record* construct() { return new NodeType; } @@ -192,100 +30,84 @@ struct RecordFactoryEntry { typedef Record* (*create_t) (); - char const * mName; create_t mCreate; RecordType mType; }; -/* These are all the record types we know how to read. - - This can be heavily optimized later if needed. For example, a - hash table or a FSM-based parser could be used to look up - node names. -*/ - -static const RecordFactoryEntry recordFactories [] = { - - { "NiNode", &construct , RC_NiNode }, - { "AvoidNode", &construct , RC_AvoidNode }, - { "NiBSParticleNode", &construct , RC_NiBSParticleNode }, - { "NiBSAnimationNode", &construct , RC_NiBSAnimationNode }, - { "NiBillboardNode", &construct , RC_NiBillboardNode }, - { "NiTriShape", &construct , RC_NiTriShape }, - { "NiRotatingParticles", &construct , RC_NiRotatingParticles }, - { "NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles }, - { "NiCamera", &construct , RC_NiCamera }, - { "RootCollisionNode", &construct , RC_RootCollisionNode }, - { "NiTexturingProperty", &construct , RC_NiTexturingProperty }, - { "NiMaterialProperty", &construct , RC_NiMaterialProperty }, - { "NiZBufferProperty", &construct , RC_NiZBufferProperty }, - { "NiAlphaProperty", &construct , RC_NiAlphaProperty }, - { "NiVertexColorProperty", &construct , RC_NiVertexColorProperty }, - { "NiShadeProperty", &construct , RC_NiShadeProperty }, - { "NiDitherProperty", &construct , RC_NiDitherProperty }, - { "NiWireframeProperty", &construct , RC_NiWireframeProperty }, - { "NiSpecularProperty", &construct , RC_NiSpecularProperty }, - { "NiStencilProperty", &construct , RC_NiStencilProperty }, - { "NiVisController", &construct , RC_NiVisController }, - { "NiGeomMorpherController", &construct , RC_NiGeomMorpherController }, - { "NiKeyframeController", &construct , RC_NiKeyframeController }, - { "NiAlphaController", &construct , RC_NiAlphaController }, - { "NiUVController", &construct , RC_NiUVController }, - { "NiPathController", &construct , RC_NiPathController }, - { "NiMaterialColorController", &construct , RC_NiMaterialColorController }, - { "NiBSPArrayController", &construct , RC_NiBSPArrayController }, - { "NiParticleSystemController", &construct , RC_NiParticleSystemController }, - { "NiFlipController", &construct , RC_NiFlipController }, - { "NiAmbientLight", &construct , RC_NiLight }, - { "NiDirectionalLight", &construct , RC_NiLight }, - { "NiTextureEffect", &construct , RC_NiTextureEffect }, - { "NiVertWeightsExtraData", &construct , RC_NiVertWeightsExtraData }, - { "NiTextKeyExtraData", &construct , RC_NiTextKeyExtraData }, - { "NiStringExtraData", &construct , RC_NiStringExtraData }, - { "NiGravity", &construct , RC_NiGravity }, - { "NiPlanarCollider", &construct , RC_NiPlanarCollider }, - { "NiParticleGrowFade", &construct , RC_NiParticleGrowFade }, - { "NiParticleColorModifier", &construct , RC_NiParticleColorModifier }, - { "NiParticleRotation", &construct , RC_NiParticleRotation }, - { "NiFloatData", &construct , RC_NiFloatData }, - { "NiTriShapeData", &construct , RC_NiTriShapeData }, - { "NiVisData", &construct , RC_NiVisData }, - { "NiColorData", &construct , RC_NiColorData }, - { "NiPixelData", &construct , RC_NiPixelData }, - { "NiMorphData", &construct , RC_NiMorphData }, - { "NiKeyframeData", &construct , RC_NiKeyframeData }, - { "NiSkinData", &construct , RC_NiSkinData }, - { "NiUVData", &construct , RC_NiUVData }, - { "NiPosData", &construct , RC_NiPosData }, - { "NiRotatingParticlesData", &construct , RC_NiRotatingParticlesData }, - { "NiAutoNormalParticlesData", &construct , RC_NiAutoNormalParticlesData }, - { "NiSequenceStreamHelper", &construct , RC_NiSequenceStreamHelper }, - { "NiSourceTexture", &construct , RC_NiSourceTexture }, - { "NiSkinInstance", &construct , RC_NiSkinInstance }, -}; - -static RecordFactoryEntry const * recordFactories_begin = &recordFactories [0]; -static RecordFactoryEntry const * recordFactories_end = &recordFactories [sizeof (recordFactories) / sizeof (recordFactories[0])]; - -RecordFactoryEntry const * lookupRecordFactory (char const * name) +///Helper function for adding records to the factory map +static std::pair makeEntry(std::string recName, Record* (*create_t) (), RecordType type) { - RecordFactoryEntry const * i; - - for (i = recordFactories_begin; i != recordFactories_end; ++i) - if (strcmp (name, i->mName) == 0) - break; - - if (i == recordFactories_end) - return NULL; - - return i; + RecordFactoryEntry anEntry = {create_t,type}; + return std::make_pair(recName, anEntry); } -/* This file implements functions from the NIFFile class. It is also - where we stash all the functions we couldn't add as inline - definitions in the record types. - */ +///These are all the record types we know how to read. +static std::map makeFactory() +{ + std::map newFactory; + newFactory.insert(makeEntry("NiNode", &construct , RC_NiNode )); + newFactory.insert(makeEntry("AvoidNode", &construct , RC_AvoidNode )); + newFactory.insert(makeEntry("NiBSParticleNode", &construct , RC_NiBSParticleNode )); + newFactory.insert(makeEntry("NiBSAnimationNode", &construct , RC_NiBSAnimationNode )); + newFactory.insert(makeEntry("NiBillboardNode", &construct , RC_NiBillboardNode )); + newFactory.insert(makeEntry("NiTriShape", &construct , RC_NiTriShape )); + newFactory.insert(makeEntry("NiRotatingParticles", &construct , RC_NiRotatingParticles )); + newFactory.insert(makeEntry("NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles )); + newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); + newFactory.insert(makeEntry("RootCollisionNode", &construct , RC_RootCollisionNode )); + newFactory.insert(makeEntry("NiTexturingProperty", &construct , RC_NiTexturingProperty )); + newFactory.insert(makeEntry("NiMaterialProperty", &construct , RC_NiMaterialProperty )); + newFactory.insert(makeEntry("NiZBufferProperty", &construct , RC_NiZBufferProperty )); + newFactory.insert(makeEntry("NiAlphaProperty", &construct , RC_NiAlphaProperty )); + newFactory.insert(makeEntry("NiVertexColorProperty", &construct , RC_NiVertexColorProperty )); + newFactory.insert(makeEntry("NiShadeProperty", &construct , RC_NiShadeProperty )); + newFactory.insert(makeEntry("NiDitherProperty", &construct , RC_NiDitherProperty )); + newFactory.insert(makeEntry("NiWireframeProperty", &construct , RC_NiWireframeProperty )); + newFactory.insert(makeEntry("NiSpecularProperty", &construct , RC_NiSpecularProperty )); + newFactory.insert(makeEntry("NiStencilProperty", &construct , RC_NiStencilProperty )); + newFactory.insert(makeEntry("NiVisController", &construct , RC_NiVisController )); + newFactory.insert(makeEntry("NiGeomMorpherController", &construct , RC_NiGeomMorpherController )); + newFactory.insert(makeEntry("NiKeyframeController", &construct , RC_NiKeyframeController )); + newFactory.insert(makeEntry("NiAlphaController", &construct , RC_NiAlphaController )); + newFactory.insert(makeEntry("NiUVController", &construct , RC_NiUVController )); + newFactory.insert(makeEntry("NiPathController", &construct , RC_NiPathController )); + newFactory.insert(makeEntry("NiMaterialColorController", &construct , RC_NiMaterialColorController )); + newFactory.insert(makeEntry("NiBSPArrayController", &construct , RC_NiBSPArrayController )); + newFactory.insert(makeEntry("NiParticleSystemController", &construct , RC_NiParticleSystemController )); + newFactory.insert(makeEntry("NiFlipController", &construct , RC_NiFlipController )); + newFactory.insert(makeEntry("NiAmbientLight", &construct , RC_NiLight )); + newFactory.insert(makeEntry("NiDirectionalLight", &construct , RC_NiLight )); + newFactory.insert(makeEntry("NiTextureEffect", &construct , RC_NiTextureEffect )); + newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct , RC_NiVertWeightsExtraData )); + newFactory.insert(makeEntry("NiTextKeyExtraData", &construct , RC_NiTextKeyExtraData )); + newFactory.insert(makeEntry("NiStringExtraData", &construct , RC_NiStringExtraData )); + newFactory.insert(makeEntry("NiGravity", &construct , RC_NiGravity )); + newFactory.insert(makeEntry("NiPlanarCollider", &construct , RC_NiPlanarCollider )); + newFactory.insert(makeEntry("NiParticleGrowFade", &construct , RC_NiParticleGrowFade )); + newFactory.insert(makeEntry("NiParticleColorModifier", &construct , RC_NiParticleColorModifier )); + newFactory.insert(makeEntry("NiParticleRotation", &construct , RC_NiParticleRotation )); + newFactory.insert(makeEntry("NiFloatData", &construct , RC_NiFloatData )); + newFactory.insert(makeEntry("NiTriShapeData", &construct , RC_NiTriShapeData )); + newFactory.insert(makeEntry("NiVisData", &construct , RC_NiVisData )); + newFactory.insert(makeEntry("NiColorData", &construct , RC_NiColorData )); + newFactory.insert(makeEntry("NiPixelData", &construct , RC_NiPixelData )); + newFactory.insert(makeEntry("NiMorphData", &construct , RC_NiMorphData )); + newFactory.insert(makeEntry("NiKeyframeData", &construct , RC_NiKeyframeData )); + newFactory.insert(makeEntry("NiSkinData", &construct , RC_NiSkinData )); + newFactory.insert(makeEntry("NiUVData", &construct , RC_NiUVData )); + newFactory.insert(makeEntry("NiPosData", &construct , RC_NiPosData )); + newFactory.insert(makeEntry("NiRotatingParticlesData", &construct , RC_NiRotatingParticlesData )); + newFactory.insert(makeEntry("NiAutoNormalParticlesData", &construct , RC_NiAutoNormalParticlesData )); + newFactory.insert(makeEntry("NiSequenceStreamHelper", &construct , RC_NiSequenceStreamHelper )); + newFactory.insert(makeEntry("NiSourceTexture", &construct , RC_NiSourceTexture )); + newFactory.insert(makeEntry("NiSkinInstance", &construct , RC_NiSkinInstance )); + return newFactory; +} + + +///Make the factory map used for parsing the file +static const std::map factories = makeFactory(); void NIFFile::parse() { @@ -322,12 +144,13 @@ void NIFFile::parse() if(rec.empty()) fail("Record number " + Ogre::StringConverter::toString(i) + " out of " + Ogre::StringConverter::toString(recNum) + " is blank."); - RecordFactoryEntry const * entry = lookupRecordFactory (rec.c_str ()); - if (entry != NULL) + std::map::const_iterator entry = factories.find(rec); + + if (entry != factories.end()) { - r = entry->mCreate (); - r->recType = entry->mType; + r = entry->second.mCreate (); + r->recType = entry->second.mType; } else fail("Unknown record type " + rec); @@ -338,24 +161,24 @@ void NIFFile::parse() r->recIndex = i; records[i] = r; r->read(&nif); - - // Discard tranformations for the root node, otherwise some meshes - // occasionally get wrong orientation. Only for NiNode-s for now, but - // can be expanded if needed. - // This should be rewritten when the method is cleaned up. - if (0 == i && rec == "NiNode") - { - static_cast(r)->trafo = Nif::Transformation::getIdentity(); - } } size_t rootNum = nif.getUInt(); roots.resize(rootNum); + //Determine which records are roots for(size_t i = 0;i < rootNum;i++) { - intptr_t idx = nif.getInt(); - roots[i] = ((idx >= 0) ? records.at(idx) : NULL); + int idx = nif.getInt(); + if (idx >= 0) + { + roots[i] = records.at(idx); + } + else + { + roots[i] = NULL; + warn("Null Root found"); + } } // Once parsing is done, do post-processing. @@ -363,81 +186,4 @@ void NIFFile::parse() records[i]->post(this); } -/// \todo move to the write cpp file - -void NiSkinInstance::post(NIFFile *nif) -{ - data.post(nif); - root.post(nif); - bones.post(nif); - - if(data.empty() || root.empty()) - nif->fail("NiSkinInstance missing root or data"); - - size_t bnum = bones.length(); - if(bnum != data->bones.size()) - nif->fail("Mismatch in NiSkinData bone count"); - - root->makeRootBone(&data->trafo); - - for(size_t i=0; ifail("Oops: Missing bone! Don't know how to handle this."); - bones[i]->makeBone(i, data->bones[i]); - } -} - - -void Node::getProperties(const Nif::NiTexturingProperty *&texprop, - const Nif::NiMaterialProperty *&matprop, - const Nif::NiAlphaProperty *&alphaprop, - const Nif::NiVertexColorProperty *&vertprop, - const Nif::NiZBufferProperty *&zprop, - const Nif::NiSpecularProperty *&specprop, - const Nif::NiWireframeProperty *&wireprop) const -{ - if(parent) - parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); - - for(size_t i = 0;i < props.length();i++) - { - // Entries may be empty - if(props[i].empty()) - continue; - - const Nif::Property *pr = props[i].getPtr(); - if(pr->recType == Nif::RC_NiTexturingProperty) - texprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiMaterialProperty) - matprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiAlphaProperty) - alphaprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiVertexColorProperty) - vertprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiZBufferProperty) - zprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiSpecularProperty) - specprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiWireframeProperty) - wireprop = static_cast(pr); - else - std::cerr<< "Unhandled property type: "<recName <getWorldTransform() * getLocalTransform(); - return getLocalTransform(); -} - } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 87e46b2347..f7cee6c409 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -1,40 +1,11 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (nif_file.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ +///Main header for reading .nif files #ifndef OPENMW_COMPONENTS_NIF_NIFFILE_HPP #define OPENMW_COMPONENTS_NIF_NIFFILE_HPP -#include -#include -#include -#include -#include -#include -#include -#include - #include #include +<<<<<<< HEAD #include #include #include @@ -45,10 +16,11 @@ #include #include +======= +#include +>>>>>>> master #include "record.hpp" -#include "niftypes.hpp" -#include "nifstream.hpp" namespace Nif { @@ -62,62 +34,46 @@ class NIFFile /// Nif file version int ver; - /// File name, used for error messages + /// File name, used for error messages and opening the file std::string filename; /// Record list std::vector records; - /// Root list + /// Root list. This is a select portion of the pointers from records std::vector roots; /// Parse the file void parse(); - class LoadedCache; - friend class LoadedCache; - - // attempt to protect NIFFile from misuse... - struct psudo_private_modifier {}; // this dirty little trick should optimize out + ///Private Copy Constructor NIFFile (NIFFile const &); + ///\overload void operator = (NIFFile const &); public: - /// Used for error handling + /// Used if file parsing fails void fail(const std::string &msg) { std::string err = "NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } - + /// Used when something goes wrong, but not catastrophically so void warn(const std::string &msg) { std::cerr << "NIFFile Warning: " << msg < ptr; - - /// Open a NIF stream. The name is used for error messages. - NIFFile(const std::string &name, psudo_private_modifier); + /// Open a NIF stream. The name is used for error messages and opening the file. + NIFFile(const std::string &name); ~NIFFile(); - static ptr create (const std::string &name); - static void lockCache (); - static void unlockCache (); - - struct CacheLock - { - CacheLock () { lockCache (); } - ~CacheLock () { unlockCache (); } - }; - /// Get a given record Record *getRecord(size_t index) const { Record *res = records.at(index); - assert(res != NULL); return res; } /// Number of records @@ -127,7 +83,6 @@ public: Record *getRoot(size_t index=0) const { Record *res = roots.at(index); - assert(res != NULL); return res; } /// Number of roots @@ -135,131 +90,6 @@ public: }; -template -struct KeyT { - float mTime; - T mValue; - T mForwardValue; // Only for Quadratic interpolation, and never for QuaternionKeyList - T mBackwardValue; // Only for Quadratic interpolation, and never for QuaternionKeyList - float mTension; // Only for TBC interpolation - float mBias; // Only for TBC interpolation - float mContinuity; // Only for TBC interpolation -}; -typedef KeyT FloatKey; -typedef KeyT Vector3Key; -typedef KeyT Vector4Key; -typedef KeyT QuaternionKey; - -template -struct KeyListT { - typedef std::vector< KeyT > VecType; - - static const unsigned int sLinearInterpolation = 1; - static const unsigned int sQuadraticInterpolation = 2; - static const unsigned int sTBCInterpolation = 3; - static const unsigned int sXYZInterpolation = 4; - - unsigned int mInterpolationType; - VecType mKeys; - - KeyListT() : mInterpolationType(sLinearInterpolation) {} - - //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) - void read(NIFStream *nif, bool force=false) - { - assert(nif); - - mInterpolationType = 0; - - size_t count = nif->getUInt(); - if(count == 0 && !force) - return; - - //If we aren't forcing things, make sure that read clears any previous keys - if(!force) - mKeys.clear(); - - mInterpolationType = nif->getUInt(); - - KeyT key; - NIFStream &nifReference = *nif; - - if(mInterpolationType == sLinearInterpolation) - { - for(size_t i = 0;i < count;i++) - { - readTimeAndValue(nifReference, key); - mKeys.push_back(key); - } - } - else if(mInterpolationType == sQuadraticInterpolation) - { - for(size_t i = 0;i < count;i++) - { - readQuadratic(nifReference, key); - mKeys.push_back(key); - } - } - else if(mInterpolationType == sTBCInterpolation) - { - for(size_t i = 0;i < count;i++) - { - readTBC(nifReference, key); - mKeys.push_back(key); - } - } - //XYZ keys aren't actually read here. - //data.hpp sees that the last type read was sXYZInterpolation and: - // Eats a floating point number, then - // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. - // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. - else if(mInterpolationType == sXYZInterpolation) - { - //Don't try to read XYZ keys into the wrong part - if ( count != 1 ) - nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); - } - else if (0 == mInterpolationType) - { - if (count != 0) - nif->file->fail("Interpolation type 0 doesn't work with keys"); - } - else - nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); - } - -private: - static void readTimeAndValue(NIFStream &nif, KeyT &key) - { - key.mTime = nif.getFloat(); - key.mValue = (nif.*getValue)(); - } - - static void readQuadratic(NIFStream &nif, KeyT &key) - { - readTimeAndValue(nif, key); - } - - template - static void readQuadratic(NIFStream &nif, KeyT &key) - { - readTimeAndValue(nif, key); - key.mForwardValue = (nif.*getValue)(); - key.mBackwardValue = (nif.*getValue)(); - } - - static void readTBC(NIFStream &nif, KeyT &key) - { - readTimeAndValue(nif, key); - key.mTension = nif.getFloat(); - key.mBias = nif.getFloat(); - key.mContinuity = nif.getFloat(); - } -}; -typedef KeyListT FloatKeyList; -typedef KeyListT Vector3KeyList; -typedef KeyListT Vector4KeyList; -typedef KeyListT QuaternionKeyList; } // Namespace #endif diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp new file mode 100644 index 0000000000..b0db80914f --- /dev/null +++ b/components/nif/nifkey.hpp @@ -0,0 +1,141 @@ +///File to handle keys used by nif file records + +#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP +#define OPENMW_COMPONENTS_NIF_NIFKEY_HPP + +#include + +#include "nifstream.hpp" + +namespace Nif +{ + +template +struct KeyT { + T mValue; + T mForwardValue; // Only for Quadratic interpolation, and never for QuaternionKeyList + T mBackwardValue; // Only for Quadratic interpolation, and never for QuaternionKeyList + float mTension; // Only for TBC interpolation + float mBias; // Only for TBC interpolation + float mContinuity; // Only for TBC interpolation +}; +typedef KeyT FloatKey; +typedef KeyT Vector3Key; +typedef KeyT Vector4Key; +typedef KeyT QuaternionKey; + +template +struct KeyMapT { + typedef std::map< float, KeyT > MapType; + + static const unsigned int sLinearInterpolation = 1; + static const unsigned int sQuadraticInterpolation = 2; + static const unsigned int sTBCInterpolation = 3; + static const unsigned int sXYZInterpolation = 4; + + unsigned int mInterpolationType; + MapType mKeys; + + KeyMapT() : mInterpolationType(sLinearInterpolation) {} + + //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) + void read(NIFStream *nif, bool force=false) + { + assert(nif); + + mInterpolationType = 0; + + size_t count = nif->getUInt(); + if(count == 0 && !force) + return; + + //If we aren't forcing things, make sure that read clears any previous keys + if(!force) + mKeys.clear(); + + mInterpolationType = nif->getUInt(); + + KeyT key; + NIFStream &nifReference = *nif; + + if(mInterpolationType == sLinearInterpolation) + { + for(size_t i = 0;i < count;i++) + { + float time = nif->getFloat(); + readValue(nifReference, key); + mKeys[time] = key; + } + } + else if(mInterpolationType == sQuadraticInterpolation) + { + for(size_t i = 0;i < count;i++) + { + float time = nif->getFloat(); + readQuadratic(nifReference, key); + mKeys[time] = key; + } + } + else if(mInterpolationType == sTBCInterpolation) + { + for(size_t i = 0;i < count;i++) + { + float time = nif->getFloat(); + readTBC(nifReference, key); + mKeys[time] = key; + } + } + //XYZ keys aren't actually read here. + //data.hpp sees that the last type read was sXYZInterpolation and: + // Eats a floating point number, then + // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. + // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. + else if(mInterpolationType == sXYZInterpolation) + { + //Don't try to read XYZ keys into the wrong part + if ( count != 1 ) + nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); + } + else if (0 == mInterpolationType) + { + if (count != 0) + nif->file->fail("Interpolation type 0 doesn't work with keys"); + } + else + nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + } + +private: + static void readValue(NIFStream &nif, KeyT &key) + { + key.mValue = (nif.*getValue)(); + } + + static void readQuadratic(NIFStream &nif, KeyT &key) + { + readValue(nif, key); + } + + template + static void readQuadratic(NIFStream &nif, KeyT &key) + { + readValue(nif, key); + key.mForwardValue = (nif.*getValue)(); + key.mBackwardValue = (nif.*getValue)(); + } + + static void readTBC(NIFStream &nif, KeyT &key) + { + readValue(nif, key); + key.mTension = nif.getFloat(); + key.mBias = nif.getFloat(); + key.mContinuity = nif.getFloat(); + } +}; +typedef KeyMapT FloatKeyMap; +typedef KeyMapT Vector3KeyMap; +typedef KeyMapT Vector4KeyMap; +typedef KeyMapT QuaternionKeyMap; + +} // Namespace +#endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index a2595d17b8..22d39b8d78 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -1,6 +1,20 @@ +///Functions used to read raw binary data from .nif files + #ifndef OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP #define OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "niftypes.hpp" + namespace Nif { diff --git a/components/nif/node.cpp b/components/nif/node.cpp new file mode 100644 index 0000000000..b7ca981133 --- /dev/null +++ b/components/nif/node.cpp @@ -0,0 +1,57 @@ +#include "node.hpp" + +namespace Nif +{ + +void Node::getProperties(const Nif::NiTexturingProperty *&texprop, + const Nif::NiMaterialProperty *&matprop, + const Nif::NiAlphaProperty *&alphaprop, + const Nif::NiVertexColorProperty *&vertprop, + const Nif::NiZBufferProperty *&zprop, + const Nif::NiSpecularProperty *&specprop, + const Nif::NiWireframeProperty *&wireprop) const +{ + if(parent) + parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + + for(size_t i = 0;i < props.length();i++) + { + // Entries may be empty + if(props[i].empty()) + continue; + + const Nif::Property *pr = props[i].getPtr(); + if(pr->recType == Nif::RC_NiTexturingProperty) + texprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiMaterialProperty) + matprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiAlphaProperty) + alphaprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiVertexColorProperty) + vertprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiZBufferProperty) + zprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiSpecularProperty) + specprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiWireframeProperty) + wireprop = static_cast(pr); + else + std::cerr<< "Unhandled property type: "<recName <getWorldTransform() * getLocalTransform(); + return getLocalTransform(); +} + +} diff --git a/components/nif/node.hpp b/components/nif/node.hpp index eebcd8be8c..01e0e9a2de 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -1,26 +1,3 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (node.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_NODE_HPP #define OPENMW_COMPONENTS_NIF_NODE_HPP @@ -29,6 +6,7 @@ #include "controlled.hpp" #include "data.hpp" #include "property.hpp" +#include "niftypes.hpp" namespace Nif { @@ -149,6 +127,14 @@ struct NiNode : Node Node::read(nif); children.read(nif); effects.read(nif); + + // Discard tranformations for the root node, otherwise some meshes + // occasionally get wrong orientation. Only for NiNode-s for now, but + // can be expanded if needed. + if (0 == recIndex) + { + static_cast(this)->trafo = Nif::Transformation::getIdentity(); + } } void post(NIFFile *nif) diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 1a4fc235bc..5eac277d02 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -1,30 +1,8 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (record_ptr.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_RECORDPTR_HPP #define OPENMW_COMPONENTS_NIF_RECORDPTR_HPP #include "niffile.hpp" +#include "nifstream.hpp" #include namespace Nif diff --git a/components/nif/tests/.gitignore b/components/nif/tests/.gitignore index b01c11f272..397b4a7624 100644 --- a/components/nif/tests/.gitignore +++ b/components/nif/tests/.gitignore @@ -1,5 +1 @@ -niftool -*_test -*.nif -*.kf -output.txt +*.log diff --git a/components/nif/tests/CMakeLists.txt b/components/nif/tests/CMakeLists.txt new file mode 100644 index 0000000000..a45298180a --- /dev/null +++ b/components/nif/tests/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_LIBRARIES} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(niftest gcov) +endif() diff --git a/components/nif/tests/Makefile b/components/nif/tests/Makefile deleted file mode 100644 index 0754bdfa61..0000000000 --- a/components/nif/tests/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -GCC=g++ - -all: niftool nif_bsa_test - -niftool: niftool.cpp ../nif_file.hpp ../nif_file.cpp ../record.hpp - $(GCC) $< ../nif_file.cpp ../../tools/stringops.cpp -o $@ - -nif_bsa_test: nif_bsa_test.cpp ../nif_file.cpp ../../bsa/bsa_file.cpp ../../tools/stringops.cpp - $(GCC) $^ -o $@ - -clean: - rm niftool *_test diff --git a/components/nif/tests/nif_bsa_test.cpp b/components/nif/tests/nif_bsa_test.cpp deleted file mode 100644 index c22aad6802..0000000000 --- a/components/nif/tests/nif_bsa_test.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - Runs NIFFile through all the NIFs in Morrowind.bsa. - */ - -#include "../nif_file.hpp" -#include "../../bsa/bsa_file.hpp" -#include "../../tools/stringops.hpp" -#include - -using namespace Mangle::Stream; -using namespace std; -using namespace Nif; - -int main(int argc, char **args) -{ - BSAFile bsa; - cout << "Reading Morrowind.bsa\n"; - bsa.open("../../data/Morrowind.bsa"); - - const BSAFile::FileList &files = bsa.getList(); - - for(int i=0; i +#include +#include +#include +#include + +///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 the given BSA archive +void readBSA(std::string filename) +{ + Bsa::BSAFile bsa; + bsa.open(filename.c_str()); + + const Bsa::BSAFile::FileList &files = bsa.getList(); + Bsa::addBSA(filename,"Bsa Files"); + + for(unsigned int i=0; i -#include -#include "../../mangle/stream/servers/file_stream.hpp" -#include "../node.hpp" -#include "../controller.hpp" -#include "../data.hpp" - -using namespace Mangle::Stream; -using namespace std; -using namespace Nif; - -// Display very verbose information -bool verbose = false; - -void doVector(const Vector *vec) -{ - cout << "[" - << vec->array[0] << "," - << vec->array[1] << "," - << vec->array[2] << "]\n"; -} - -void doVector4(const Vector4 *vec) -{ - cout << "[" - << vec->array[0] << "," - << vec->array[1] << "," - << vec->array[2] << "," - << vec->array[3] << "]\n"; -} - -void doMatrix(const Matrix *mat) -{ - cout << "Matrix:\n"; - for(int i=0; i<3; i++) - { - cout << " "; - doVector(&mat->v[i]); - } -} - -void doTrafo(const Transformation* trafo) -{ - cout << "--- transformation:\n"; - cout << "Pos: "; doVector(&trafo->pos); - cout << "Rot: "; doMatrix(&trafo->rotation); - cout << "Scale: " << trafo->scale << endl; - cout << "Vel: "; doVector(&trafo->velocity); - cout << "--- end transformation\n"; -} - -void doExtra(Extra *e) -{ - cout << "Extra: " << e->extra.getIndex() << endl; -} - -void doControlled(Controlled *c) -{ - doExtra(c); - cout << "Controller: " << c->controller.getIndex() << endl; -} - -void doNamed(Named *n) -{ - doControlled(n); - cout << "Name: " << n->name.toString() << endl; -} - -void doNode(Node *n) -{ - doNamed(n); - - cout << "Flags: 0x" << hex << n->flags << dec << endl; - doTrafo(n->trafo); - - cout << "Properties:"; - for(int i=0; iprops.length(); i++) - cout << " " << n->props.getIndex(i); - cout << endl; - - if(n->hasBounds) - { - cout << "Bounding box:\n"; - doVector(n->boundPos); - doMatrix(n->boundRot); - doVector(n->boundXYZ); - } - - if(n->boneTrafo) - { - cout << "This is a bone: "; - if(n->boneIndex == -1) - cout << "root bone\n"; - else - cout << "index " << n->boneIndex << endl; - } -} - -void doNiTriShape(NiTriShape *n) -{ - doNode(n); - - cout << "Shape data: " << n->data.getIndex() << endl; - cout << "Skin instance: " << n->skin.getIndex() << endl; -} - -void doNiSkinData(NiSkinData *n) -{ - int c = n->bones.size(); - - cout << "Global transformation:\n"; - doMatrix(&n->trafo->rotation); - doVector(&n->trafo->trans); - cout << "Scale: " << n->trafo->scale << endl; - - cout << "Bone number: " << c << endl; - for(int i=0; ibones[i]; - - cout << "-- Bone " << i << ":\n"; - doMatrix(&bi.trafo->rotation); - doVector(&bi.trafo->trans); - cout << "Scale: " << bi.trafo->scale << endl; - cout << "Unknown: "; doVector4(bi.unknown); - cout << "Weight number: " << bi.weights.length << endl; - - if(verbose) - for(int j=0; jdata.getIndex() << endl; - cout << "Root: " << n->root.getIndex() << endl; - cout << "Bones:"; - for(int i=0; ibones.length(); i++) - cout << " " << n->bones.getIndex(i); - cout << endl; -} - -void doNiNode(NiNode *n) -{ - doNode(n); - - cout << "Children:"; - for(int i=0; ichildren.length(); i++) - cout << " " << n->children.getIndex(i); - cout << endl; - - cout << "Effects:"; - for(int i=0; ieffects.length(); i++) - cout << " " << n->effects.getIndex(i); - cout << endl; -} - -void doNiStringExtraData(NiStringExtraData *s) -{ - doExtra(s); - cout << "String: " << s->string.toString() << endl; -} - -void doNiTextKeyExtraData(NiTextKeyExtraData *t) -{ - doExtra(t); - for(int i=0; ilist.size(); i++) - { - cout << "@time " << t->list[i].time << ":\n\"" - << t->list[i].text.toString() << "\"" << endl; - } -} - -void doController(Controller *r) -{ - cout << "Next controller: " << r->next.getIndex() << endl; - cout << "Flags: " << hex << r->flags << dec << endl; - cout << "Frequency: " << r->frequency << endl; - cout << "Phase: " << r->phase << endl; - cout << "Time start: " << r->timeStart << endl; - cout << "Time stop: " << r->timeStop << endl; - cout << "Target: " << r->target.getIndex() << endl; -} - -void doNiKeyframeController(NiKeyframeController *k) -{ - doController(k); - cout << "Data: " << k->data.getIndex() << endl; -} - -int main(int argc, char **args) -{ - if(argc != 2) - { - cout << "Specify a NIF file on the command line\n"; - return 1; - } - - StreamPtr file(new FileStream(args[1])); - NIFFile nif(file, args[1]); - - int num = nif.numRecords(); - - for(int i=0; irecName.toString() << endl; - - switch(r->recType) - { - case RC_NiNode: doNiNode((NiNode*)r); break; - case RC_NiSkinData: doNiSkinData((NiSkinData*)r); break; - case RC_NiSkinInstance: doNiSkinInstance((NiSkinInstance*)r); break; - case RC_NiTriShape: doNiTriShape((NiTriShape*)r); break; - case RC_NiStringExtraData: doNiStringExtraData((NiStringExtraData*)r); break; - case RC_NiSequenceStreamHelper: doNamed((Named*)r); break; - case RC_NiTextKeyExtraData: doNiTextKeyExtraData((NiTextKeyExtraData*)r); break; - case RC_NiKeyframeController: doNiKeyframeController((NiKeyframeController*)r); break; - } - - cout << endl; - } -} diff --git a/components/nif/tests/output/nif_bsa_test.out b/components/nif/tests/output/nif_bsa_test.out deleted file mode 100644 index 5499dafc79..0000000000 --- a/components/nif/tests/output/nif_bsa_test.out +++ /dev/null @@ -1,5799 +0,0 @@ -Reading Morrowind.bsa -Decoding meshes\m\probe_journeyman_01.nif -Decoding meshes\b\b_n_redguard_f_skins.nif -Decoding meshes\b\b_n_redguard_m_skins.nif -Decoding meshes\b\b_n_redguard_f_wrist.nif -Decoding meshes\b\b_n_redguard_m_foot.nif -Decoding meshes\b\b_n_redguard_m_knee.nif -Decoding meshes\b\b_n_redguard_f_knee.nif -Decoding meshes\b\b_n_redguard_m_neck.nif -Decoding meshes\b\b_n_redguard_f_neck.nif -Decoding meshes\b\b_n_redguard_m_ankle.nif -Decoding meshes\b\b_n_redguard_f_ankle.nif -Decoding meshes\b\b_n_redguard_f_foot.nif -Decoding meshes\b\b_n_redguard_m_wrist.nif -Decoding meshes\b\b_n_redguard_f_groin.nif -Decoding meshes\b\b_n_redguard_m_groin.nif -Decoding meshes\b\b_n_redguard_m_head_02.nif -Decoding meshes\b\b_n_redguard_m_head_06.nif -Decoding meshes\b\b_n_redguard_m_head_04.nif -Decoding meshes\b\b_n_redguard_f_head_02.nif -Decoding meshes\b\b_n_redguard_f_head_06.nif -Decoding meshes\b\b_n_redguard_f_head_04.nif -Decoding meshes\b\b_n_redguard_m_hair_05.nif -Decoding meshes\b\b_n_redguard_m_hair_03.nif -Decoding meshes\b\b_n_redguard_m_hair_01.nif -Decoding meshes\b\b_n_redguard_f_hair_05.nif -Decoding meshes\b\b_n_redguard_f_hair_03.nif -Decoding meshes\b\b_n_redguard_f_hair_01.nif -Decoding meshes\b\b_n_redguard_m_forearm.nif -Decoding meshes\b\b_n_redguard_m_head_03.nif -Decoding meshes\b\b_n_redguard_m_head_01.nif -Decoding meshes\b\b_n_redguard_m_head_05.nif -Decoding meshes\b\b_n_redguard_f_head_03.nif -Decoding meshes\b\b_n_redguard_f_head_01.nif -Decoding meshes\b\b_n_redguard_f_head_05.nif -Decoding meshes\b\b_n_redguard_m_hair_06.nif -Decoding meshes\b\b_n_redguard_m_hair_04.nif -Decoding meshes\b\b_n_redguard_m_hair_02.nif -Decoding meshes\b\b_n_redguard_m_hair_00.nif -Decoding meshes\b\b_n_redguard_f_hair_04.nif -Decoding meshes\b\b_n_redguard_f_hair_02.nif -Decoding meshes\b\b_n_redguard_f_forearm.nif -Decoding meshes\b\b_n_redguard_f_upper leg.nif -Decoding meshes\b\b_n_redguard_f_upper arm.nif -Decoding meshes\b\b_n_redguard_m_upper leg.nif -Decoding meshes\b\b_n_redguard_m_hands.1st.nif -Decoding meshes\b\b_n_redguard_m_upper arm.nif -Decoding meshes\b\b_n_redguard_f_hands.1st.nif -Decoding meshes\w\w_longspear_daedric.nif -Decoding meshes\w\w_longsword_crystal.nif -Decoding meshes\w\w_longsword_daedric.nif -Decoding meshes\l\light_com_lantern_01.nif -Decoding meshes\l\light_com_candle_15.nif -Decoding meshes\l\light_com_candle_05.nif -Decoding meshes\l\light_com_candle_14.nif -Decoding meshes\l\light_com_candle_04.nif -Decoding meshes\l\light_com_candle_07.nif -Decoding meshes\l\light_com_candle_16.nif -Decoding meshes\l\light_com_candle_06.nif -Decoding meshes\l\light_com_candle_11.nif -Decoding meshes\l\light_com_candle_01.nif -Decoding meshes\l\light_com_candle_10.nif -Decoding meshes\l\light_com_candle_13.nif -Decoding meshes\l\light_com_candle_03.nif -Decoding meshes\l\light_com_candle_12.nif -Decoding meshes\l\light_com_candle_02.nif -Decoding meshes\l\light_com_candle_09.nif -Decoding meshes\l\light_com_candle_08.nif -Decoding meshes\l\light_com_sconce_02.nif -Decoding meshes\l\light_com_sconce_01.nif -Decoding meshes\l\light_com_lantern_02.nif -Decoding meshes\l\light_com_chandelier_03.nif -Decoding meshes\l\light_com_chandelier_01.nif -Decoding meshes\l\light_com_chandelier_05.nif -Decoding meshes\l\light_com_chandelier_02.nif -Decoding meshes\l\light_com_chandelier_06.nif -Decoding meshes\l\light_com_chandelier_04.nif -Decoding meshes\f\flora_ash_grass_b_01.nif -Decoding meshes\f\flora_ash_grass_r_01.nif -Decoding meshes\f\flora_ash_grass_w_01.nif -Decoding meshes\a\a_art_helm_bearclaw.nif -Decoding meshes\l\light_com_chandelier_01_l.nif -Decoding meshes\l\light_com_chandelier_05_l.nif -Decoding meshes\l\light_com_chandelier_03_l.nif -Decoding meshes\b\b_n_breton_f_head_04.nif -Decoding meshes\b\b_n_breton_f_hair_04.nif -Decoding meshes\b\b_n_breton_m_head_01.nif -Decoding meshes\b\b_n_breton_f_head_01.nif -Decoding meshes\b\b_n_breton_m_head_03.nif -Decoding meshes\b\b_n_breton_m_hair_01.nif -Decoding meshes\b\b_n_breton_m_head_04.nif -Decoding meshes\b\b_n_breton_f_head_05.nif -Decoding meshes\b\b_n_breton_m_head_07.nif -Decoding meshes\b\b_n_breton_m_head_08.nif -Decoding meshes\b\b_n_breton_m_head_06.nif -Decoding meshes\b\b_n_breton_m_forearm.nif -Decoding meshes\b\b_n_breton_m_head_05.nif -Decoding meshes\b\b_n_breton_f_head_06.nif -Decoding meshes\b\b_n_breton_f_hair_03.nif -Decoding meshes\b\b_n_breton_m_hair_04.nif -Decoding meshes\b\b_n_breton_m_hair_05.nif -Decoding meshes\b\b_n_breton_m_hair_03.nif -Decoding meshes\b\b_n_breton_f_head_03.nif -Decoding meshes\b\b_n_breton_f_hair_02.nif -Decoding meshes\b\b_n_breton_m_head_02.nif -Decoding meshes\b\b_n_breton_f_forearm.nif -Decoding meshes\b\b_n_breton_m_hair_02.nif -Decoding meshes\b\b_n_breton_f_head_02.nif -Decoding meshes\b\b_n_breton_m_hair_00.nif -Decoding meshes\b\b_n_breton_f_hair_05.nif -Decoding meshes\b\b_n_breton_f_hair_01.nif -Decoding meshes\b\b_n_breton_m_upper leg.nif -Decoding meshes\b\b_n_breton_f_upper leg.nif -Decoding meshes\b\b_n_breton_f_upper arm.nif -Decoding meshes\b\b_n_breton_m_upper arm.nif -Decoding meshes\b\b_n_breton_m_hand.1st.nif -Decoding meshes\b\b_n_breton_f_hands.1st.nif -Decoding meshes\a\a_gondolier_m_helmet.nif -Decoding meshes\m\light_com_candle_07.nif -Decoding meshes\m\misc_dwrv_ark_cube00.nif -Decoding meshes\m\misc_dwrv_artifact30.nif -Decoding meshes\m\misc_dwrv_artifact20.nif -Decoding meshes\m\misc_dwrv_artifact10.nif -Decoding meshes\m\misc_dwrv_artifact00.nif -Decoding meshes\m\misc_dwrv_artifact70.nif -Decoding meshes\m\misc_dwrv_artifact60.nif -Decoding meshes\m\misc_dwrv_artifact50.nif -Decoding meshes\m\misc_dwrv_artifact40.nif -Decoding meshes\m\misc_dwrv_artifact80.nif -Decoding meshes\m\misc_dwrv_pitcher00.nif -Decoding meshes\m\misc_dwrv_ark_key00.nif -Decoding meshes\i\in_dae_hall_l_corner.nif -Decoding meshes\i\in_dae_doorjamb_load.nif -Decoding meshes\i\in_dae_connect_lcave.nif -Decoding meshes\i\in_dae_platform_stairs.nif -Decoding meshes\i\in_dae_pillar_tall.max.nif -Decoding meshes\i\in_dae_platform_512_01.nif -Decoding meshes\i\in_dae_room_l_roof_01.nif -Decoding meshes\i\in_dae_room_l_floor_01.nif -Decoding meshes\i\in_dae_room_l_roof_02.nif -Decoding meshes\i\in_dae_room_l_side_01.nif -Decoding meshes\i\in_dae_room_l_roof_03.nif -Decoding meshes\i\in_dae_hall_ruin_l_02.nif -Decoding meshes\i\in_dae_hall_l_entry_01.nif -Decoding meshes\i\in_dae_hall_l_4way_02.nif -Decoding meshes\i\in_dae_hall_ruin_l_01.nif -Decoding meshes\i\in_dae_hall_l_3way_01.nif -Decoding meshes\i\in_dae_hall_l_4way_01.nif -Decoding meshes\i\in_dae_mezzanine_edge.nif -Decoding meshes\i\in_dae_room_ruin_roof_03.nif -Decoding meshes\i\in_dae_room_r_corner_02.nif -Decoding meshes\i\in_dae_room_l_corner_02.nif -Decoding meshes\i\in_dae_room_r_corner_01.nif -Decoding meshes\i\in_dae_room_l_corner_01.nif -Decoding meshes\i\in_dae_room_ruin_side_01.nif -Decoding meshes\i\in_dae_hall_l_endcap_01.nif -Decoding meshes\i\in_dae_hall_l_stairs_01.nif -Decoding meshes\i\in_com_trapbottom_01.nif -Decoding meshes\i\in_t_l_room_ceiling.nif -Decoding meshes\i\in_v_l_int_center_02.nif -Decoding meshes\i\in_v_l_int_corner_01.nif -Decoding meshes\i\in_v_l_int_stairs_04.nif -Decoding meshes\i\in_v_l_int_column_03.nif -Decoding meshes\i\in_v_l_int_column_01.nif -Decoding meshes\i\in_v_l_int_pillar_01.nif -Decoding meshes\i\in_v_l_int_bridge_02.nif -Decoding meshes\i\in_v_l_int_stairs_02.nif -Decoding meshes\i\in_v_l_int_stairs_03.nif -Decoding meshes\i\in_v_l_int_stairs_01.nif -Decoding meshes\i\in_v_l_int_lwall_01.nif -Decoding meshes\i\in_v_l_int_lwall_02.nif -Decoding meshes\i\in_v_l_int_center_01.nif -Decoding meshes\i\in_v_l_int_corner_03.nif -Decoding meshes\i\in_v_l_int_column_02.nif -Decoding meshes\i\in_v_l_int_bridge_01.nif -Decoding meshes\i\in_v_l_int_corner_02.nif -Decoding meshes\i\in_v_l_int_arches_01.nif -Decoding meshes\i\in_r_l_int_center_02.nif -Decoding meshes\i\in_r_l_int_corner_01.nif -Decoding meshes\i\in_r_l_int_column_01.nif -Decoding meshes\i\in_r_l_int_pillar_01.nif -Decoding meshes\i\in_r_l_int_bridge_02.nif -Decoding meshes\i\in_r_l_int_stairs_02.nif -Decoding meshes\i\in_r_l_short_ramp_01.nif -Decoding meshes\i\in_r_l_int_stairs_03.nif -Decoding meshes\i\in_r_l_int_stairs_01.nif -Decoding meshes\i\in_r_l_long_ramp_01.nif -Decoding meshes\i\in_r_l_int_lwall_01.nif -Decoding meshes\i\in_r_l_int_lwall_02.nif -Decoding meshes\i\in_r_l_int_center_01.nif -Decoding meshes\i\in_r_l_int_corner_03.nif -Decoding meshes\i\in_r_l_int_pillar_02.nif -Decoding meshes\i\in_r_l_int_bridge_01.nif -Decoding meshes\i\in_r_l_int_corner_02.nif -Decoding meshes\i\in_r_l_int_arches_01.nif -Decoding meshes\i\in_v_l_int_lcenter_02.nif -Decoding meshes\i\in_v_l_int_lcorner_01.nif -Decoding meshes\i\in_v_l_int_lcolumn_03.nif -Decoding meshes\i\in_v_l_int_lcolumn_01.nif -Decoding meshes\i\in_v_l_int_ceiling_01.nif -Decoding meshes\i\in_v_l_int_entrance_02.nif -Decoding meshes\i\in_v_l_int_lcorner_03.nif -Decoding meshes\i\in_v_l_int_lcenter_01.nif -Decoding meshes\i\in_v_l_int_lcolumn_02.nif -Decoding meshes\i\in_v_l_int_lcorner_02.nif -Decoding meshes\i\in_v_l_int_entrance_01.nif -Decoding meshes\i\in_r_l_int_lcenter_02.nif -Decoding meshes\i\in_r_l_int_lcorner_01.nif -Decoding meshes\i\in_r_l_int_lcolumn_01.nif -Decoding meshes\i\in_r_l_int_entrance_02.nif -Decoding meshes\i\in_r_l_int_lcorner_03.nif -Decoding meshes\i\in_r_l_int_lcenter_01.nif -Decoding meshes\i\in_r_l_int_lcenter_04.nif -Decoding meshes\i\in_r_l_int_lcenter_03.nif -Decoding meshes\i\in_r_l_int_balcony_01.nif -Decoding meshes\i\in_r_l_int_railing_01.nif -Decoding meshes\i\in_r_l_int_lcorner_02.nif -Decoding meshes\i\in_r_l_int_entrance_01.nif -Decoding meshes\i\in_r_l_int_entrance_03.nif -Decoding meshes\i\in_t_l_room_highentry.nif -Decoding meshes\m\misc_kwamaegg_gold_01.nif -Decoding meshes\i\in_v_l_int_lentrance_02.nif -Decoding meshes\i\in_v_l_int_partition_01.nif -Decoding meshes\i\in_v_l_int_lentrance_01.nif -Decoding meshes\i\in_r_l_int_lentrance_02.nif -Decoding meshes\i\in_r_l_int_partition_01.nif -Decoding meshes\i\in_r_l_int_lentrance_01.nif -Decoding meshes\i\in_r_l_int_lpartition_01.nif -Decoding meshes\i\cap01.nif -Decoding meshes\i\box02.nif -Decoding meshes\i\in_ar_s2.nif -Decoding meshes\i\in_ar_s3.nif -Decoding meshes\i\in_ar_s1.nif -Decoding meshes\i\in_ar_s6.nif -Decoding meshes\i\in_ar_s7.nif -Decoding meshes\i\in_ar_s4.nif -Decoding meshes\i\in_ar_s5.nif -Decoding meshes\i\in_ar_02.nif -Decoding meshes\i\in_ar_03.nif -Decoding meshes\i\in_ar_01.nif -Decoding meshes\i\in_ar_06.nif -Decoding meshes\i\in_ar_07.nif -Decoding meshes\i\in_ar_04.nif -Decoding meshes\i\in_ar_05.nif -Decoding meshes\i\in_ar_08.nif -Decoding meshes\i\in_ar_09.nif -Decoding meshes\i\in_ci_01.nif -Decoding meshes\i\in_ar_10.nif -Decoding meshes\i\in_imp_fireplace_grand.nif -Decoding meshes\i\in_t_s_plain_hall_01.nif -Decoding meshes\i\in_t_s_hallshaft_cap.nif -Decoding meshes\i\in_t_s_hall_ramp_01.nif -Decoding meshes\i\in_t_s_room_side_01.nif -Decoding meshes\i\in_v_s_int_center_02.nif -Decoding meshes\i\in_v_s_int_corner_01.nif -Decoding meshes\i\in_v_s_int_column_01.nif -Decoding meshes\i\in_v_s_int_lwall_01.nif -Decoding meshes\i\in_v_s_int_lwall_03.nif -Decoding meshes\i\in_v_s_int_lwall_02.nif -Decoding meshes\i\in_v_s_int_lwall_04.nif -Decoding meshes\i\in_v_s_int_center_01.nif -Decoding meshes\i\in_v_s_int_corner_03.nif -Decoding meshes\i\in_v_s_int_center_04.nif -Decoding meshes\i\in_v_s_int_center_03.nif -Decoding meshes\i\in_v_s_int_corner_02.nif -Decoding meshes\i\in_v_s_int_lrail_01.nif -Decoding meshes\i\in_r_s_int_center_02.nif -Decoding meshes\i\in_r_s_int_center_06.nif -Decoding meshes\i\in_r_s_int_corner_01.nif -Decoding meshes\i\in_r_s_int_stairs_04.nif -Decoding meshes\i\in_r_s_int_column_01.nif -Decoding meshes\i\in_r_s_int_pillar_01.nif -Decoding meshes\i\in_r_s_int_bridge_02.nif -Decoding meshes\i\in_r_s_int_ledge_03.nif -Decoding meshes\i\in_r_s_int_ledge_02.nif -Decoding meshes\i\in_r_s_int_ledge_01.nif -Decoding meshes\i\in_r_s_int_bridge_03.nif -Decoding meshes\i\in_r_s_int_stairs_05.nif -Decoding meshes\i\in_r_s_int_stairs_02.nif -Decoding meshes\i\in_r_s_int_center_05.nif -Decoding meshes\i\in_r_s_short_ramp_01.nif -Decoding meshes\i\in_r_s_int_stairs_03.nif -Decoding meshes\i\in_r_s_int_stairs_01.nif -Decoding meshes\i\in_r_s_long_ramp_01.nif -Decoding meshes\i\in_r_s_int_lwall_01.nif -Decoding meshes\i\in_r_s_int_lwall_02.nif -Decoding meshes\i\in_r_s_int_center_01.nif -Decoding meshes\i\in_r_s_int_corner_03.nif -Decoding meshes\i\in_r_s_int_center_04.nif -Decoding meshes\i\in_r_s_int_pillar_02.nif -Decoding meshes\i\in_r_s_int_center_03.nif -Decoding meshes\i\in_r_s_int_bridge_01.nif -Decoding meshes\i\in_r_s_int_corner_02.nif -Decoding meshes\i\in_r_s_int_steps_01.nif -Decoding meshes\i\in_r_s_int_steps_02.nif -Decoding meshes\i\in_r_s_int_steps_03.nif -Decoding meshes\i\in_r_s_int_stairs_06.nif -Decoding meshes\i\in_t_s_hall_endcap_01.nif -Decoding meshes\i\in_v_s_int_lcenter_02.nif -Decoding meshes\i\in_v_s_int_lstairs_04.nif -Decoding meshes\i\in_v_s_int_lcorner_01.nif -Decoding meshes\i\in_v_s_int_lcolumn_03.nif -Decoding meshes\i\in_v_s_int_lcolumn_01.nif -Decoding meshes\i\in_v_s_int_lbridge_02.nif -Decoding meshes\i\in_v_s_int_lpillar_01.nif -Decoding meshes\i\in_v_s_int_entrance_02.nif -Decoding meshes\i\in_v_s_int_lstairs_03.nif -Decoding meshes\i\in_v_s_int_lstairs_02.nif -Decoding meshes\i\in_v_s_int_lcorner_03.nif -Decoding meshes\i\in_v_s_int_lcenter_01.nif -Decoding meshes\i\in_v_s_int_lcenter_04.nif -Decoding meshes\i\in_v_s_int_lstairs_01.nif -Decoding meshes\i\in_v_s_int_lcolumn_02.nif -Decoding meshes\i\in_v_s_int_larches_01.nif -Decoding meshes\i\in_v_s_int_lcenter_03.nif -Decoding meshes\i\in_v_s_int_lcorner_02.nif -Decoding meshes\i\in_v_s_int_lbridge_01.nif -Decoding meshes\i\in_v_s_int_entrance_01.nif -Decoding meshes\i\in_v_s_lint_center_01.nif -Decoding meshes\i\in_r_s_int_lcenter_02.nif -Decoding meshes\i\in_r_s_int_lcenter_06.nif -Decoding meshes\i\in_r_s_int_lcorner_01.nif -Decoding meshes\i\in_r_s_int_lcolumn_01.nif -Decoding meshes\i\in_r_s_int_entrance_02.nif -Decoding meshes\i\in_r_s_int_lcenter_05.nif -Decoding meshes\i\in_r_s_int_lcorner_03.nif -Decoding meshes\i\in_r_s_int_lcenter_01.nif -Decoding meshes\i\in_r_s_int_lcenter_04.nif -Decoding meshes\i\in_r_s_int_larches_01.nif -Decoding meshes\i\in_r_s_int_lcenter_03.nif -Decoding meshes\i\in_r_s_int_balcony_01.nif -Decoding meshes\i\in_r_s_int_railing_01.nif -Decoding meshes\i\in_r_s_int_lcorner_02.nif -Decoding meshes\i\in_r_s_int_entrance_01.nif -Decoding meshes\i\in_t_s_pillar_large_01.nif -Decoding meshes\i\in_t_s_plain_hall_ramp.nif -Decoding meshes\i\in_t_s_plain_turret_02.nif -Decoding meshes\i\in_t_s_plain_shaft_cap.nif -Decoding meshes\i\in_t_s_plain_hall_4way.nif -Decoding meshes\i\in_t_s_plain_hall_3way.nif -Decoding meshes\i\in_t_s_plain_hall_plug.nif -Decoding meshes\i\in_t_s_shaft_elbow_01.nif -Decoding meshes\i\in_t_s_shaft_vconnect.nif -Decoding meshes\i\in_v_s_wall_column_01.nif -Decoding meshes\i\in_v_s_wall_column_02.nif -Decoding meshes\i\in_t_s_hall_small_corner.nif -Decoding meshes\i\in_t_s_plain_hall_corner.nif -Decoding meshes\i\in_t_s_plain_hall_endcap.nif -Decoding meshes\i\in_t_s_plain_room_center.nif -Decoding meshes\i\in_v_s_int_lentrance_02.nif -Decoding meshes\i\in_v_s_int_lentrance_04.nif -Decoding meshes\i\in_v_s_int_lentrance_06.nif -Decoding meshes\i\in_v_s_int_lentrance_01.nif -Decoding meshes\i\in_v_s_int_lentrance_03.nif -Decoding meshes\i\in_v_s_int_lentrance_05.nif -Decoding meshes\i\in_v_s_int_lpartition_01.nif -Decoding meshes\i\in_r_s_int_lentrance_02.nif -Decoding meshes\i\in_r_s_int_lentrance_04.nif -Decoding meshes\i\in_r_s_int_lentrance_06.nif -Decoding meshes\i\in_r_s_int_partition_01.nif -Decoding meshes\i\in_r_s_int_lentrance_01.nif -Decoding meshes\i\in_r_s_int_lentrance_03.nif -Decoding meshes\i\in_r_s_int_lentrance_05.nif -Decoding meshes\i\in_r_s_int_lplatform_01.nif -Decoding meshes\i\in_r_s_int_lpartition_01.nif -Decoding meshes\m\apparatus_a_spipe_01.nif -Decoding meshes\m\apparatus_s_alembic_01.nif -Decoding meshes\m\apparatus_g_alembic_01.nif -Decoding meshes\m\apparatus_a_alembic_01.nif -Decoding meshes\m\apparatus_m_alembic_01.nif -Decoding meshes\m\apparatus_j_alembic_01.nif -Decoding meshes\m\apparatus_s_retort_01.nif -Decoding meshes\m\apparatus_m_retort_01.nif -Decoding meshes\m\apparatus_j_retort_01.nif -Decoding meshes\m\apparatus_g_retort_01.nif -Decoding meshes\m\apparatus_a_retort_01.nif -Decoding meshes\i\in_dae_pillar_verytall.max.nif -Decoding meshes\i\in_dae_hall_l_staircurve2.nif -Decoding meshes\i\in_dae_hall_l_staircurve1.nif -Decoding meshes\i\in_dae_room_l_cornerout_01.nif -Decoding meshes\i\in_dae_room_ruin_cornerout.nif -Decoding meshes\i\scene root.nif -Decoding meshes\i\in_t_s_plain_hallshaft_cap.nif -Decoding meshes\m\apparatus_a_calcinator_01.nif -Decoding meshes\a\a_dragonscale_cuirass.nif -Decoding meshes\a\a_dragonscale_cuir_gnd.nif -Decoding meshes\l\light_ashl_lantern_01.nif -Decoding meshes\l\light_ashl_lantern_04.nif -Decoding meshes\l\light_ashl_lantern_05.nif -Decoding meshes\l\light_ashl_lantern_07.nif -Decoding meshes\l\light_ashl_lantern_03.nif -Decoding meshes\l\light_ashl_lantern_02.nif -Decoding meshes\l\light_ashl_lantern_06.nif -Decoding meshes\d\door_cavern_doors20.nif -Decoding meshes\d\door_cavern_doors10.nif -Decoding meshes\d\door_cavern_doors00.nif -Decoding meshes\m\misc_6th_ash_statue_01.nif -Decoding meshes\b\b_n_argonian_m_wrist.nif -Decoding meshes\b\b_n_argonian_m_skins.nif -Decoding meshes\b\b_n_argonian_f_skins.nif -Decoding meshes\b\b_n_argonian_f_neck.nif -Decoding meshes\b\b_n_argonian_m_neck.nif -Decoding meshes\b\b_n_argonian_f_wrist.nif -Decoding meshes\b\b_n_argonian_f_knee.nif -Decoding meshes\b\b_n_argonian_m_groin.nif -Decoding meshes\b\b_n_argonian_m_knee.nif -Decoding meshes\b\b_n_argonian_f_groin.nif -Decoding meshes\b\b_n_argonian_f_ankle.nif -Decoding meshes\b\b_n_argonian_m_ankle.nif -Decoding meshes\b\b_n_argonian_m_hair02.nif -Decoding meshes\b\b_n_argonian_f_hair02.nif -Decoding meshes\b\b_n_argonian_m_hair06.nif -Decoding meshes\b\b_n_argonian_m_head_02.nif -Decoding meshes\b\b_n_argonian_f_head_02.nif -Decoding meshes\b\b_n_argonian_f_hair03.nif -Decoding meshes\b\b_n_argonian_m_hair03.nif -Decoding meshes\b\b_n_argonian_m_forearm.nif -Decoding meshes\b\b_n_argonian_m_hair04.nif -Decoding meshes\b\b_n_argonian_f_hair04.nif -Decoding meshes\b\b_n_argonian_f_hair01.nif -Decoding meshes\b\b_n_argonian_m_hair01.nif -Decoding meshes\b\b_n_argonian_m_head_03.nif -Decoding meshes\b\b_n_argonian_m_head_01.nif -Decoding meshes\b\b_n_argonian_f_head_03.nif -Decoding meshes\b\b_n_argonian_f_head_01.nif -Decoding meshes\b\b_n_argonian_m_hair05.nif -Decoding meshes\b\b_n_argonian_f_hair05.nif -Decoding meshes\b\b_n_argonian_f_upper leg.nif -Decoding meshes\b\b_n_argonian_m_upper leg.nif -Decoding meshes\b\b_n_argonian_m_hands.1st.nif -Decoding meshes\b\b_n_argonian_f_hands.1st.nif -Decoding meshes\b\b_n_argonian_f_upper arm.nif -Decoding meshes\b\b_n_argonian_m_upper arm.nif -Decoding meshes\c\c_f_pants_g_common00.nif -Decoding meshes\c\c_f_pants_a_common00.nif -Decoding meshes\c\c_f_pants_k_common00.nif -Decoding meshes\c\c_f_pants_k_common02.nif -Decoding meshes\c\c_f_pants_a_common02.nif -Decoding meshes\c\c_f_pants_g_common02.nif -Decoding meshes\c\c_f_pants_k_common01.nif -Decoding meshes\c\c_f_pants_g_common01.nif -Decoding meshes\c\c_f_pants_a_common01.nif -Decoding meshes\c\c_f_pants_ul_common00.nif -Decoding meshes\c\c_f_pants_ul_common02.nif -Decoding meshes\c\c_f_pants_ul_common01.nif -Decoding meshes\c\c_f_pants_common_4_b_a.nif -Decoding meshes\c\c_f_pants_common_4_b_g.nif -Decoding meshes\c\c_f_pants_common_4_b_k.nif -Decoding meshes\c\c_f_pants_common_4_b_ul.nif -Decoding meshes\b\b_n_high elf_f_skins.nif -Decoding meshes\b\b_n_high elf_m_skins.nif -Decoding meshes\b\b_n_high elf_m_foot.nif -Decoding meshes\b\b_n_high elf_m_wrist.nif -Decoding meshes\b\b_n_high elf_m_knee.nif -Decoding meshes\b\b_n_high elf_f_knee.nif -Decoding meshes\b\b_n_high elf_f_groin.nif -Decoding meshes\b\b_n_high elf_m_groin.nif -Decoding meshes\b\b_n_high elf_m_neck.nif -Decoding meshes\b\b_n_high elf_f_neck.nif -Decoding meshes\b\b_n_high elf_f_wrist.nif -Decoding meshes\b\b_n_high elf_f_foot.nif -Decoding meshes\b\b_n_high elf_m_ankle.nif -Decoding meshes\b\b_n_high elf_f_ankle.nif -Decoding meshes\b\b_n_high elf_f_head_02.nif -Decoding meshes\b\b_n_high elf_f_head_06.nif -Decoding meshes\b\b_n_high elf_f_head_04.nif -Decoding meshes\b\b_n_high elf_m_head_02.nif -Decoding meshes\b\b_n_high elf_m_head_06.nif -Decoding meshes\b\b_n_high elf_m_head_04.nif -Decoding meshes\b\b_n_high elf_f_hair_01.nif -Decoding meshes\b\b_n_high elf_f_hair_03.nif -Decoding meshes\b\b_n_high elf_m_hair_05.nif -Decoding meshes\b\b_n_high elf_m_hair_01.nif -Decoding meshes\b\b_n_high elf_m_hair_03.nif -Decoding meshes\b\b_n_high elf_m_forearm.nif -Decoding meshes\b\b_n_high elf_f_head_03.nif -Decoding meshes\b\b_n_high elf_f_head_01.nif -Decoding meshes\b\b_n_high elf_f_head_05.nif -Decoding meshes\b\b_n_high elf_m_head_03.nif -Decoding meshes\b\b_n_high elf_m_head_01.nif -Decoding meshes\b\b_n_high elf_m_head_05.nif -Decoding meshes\b\b_n_high elf_f_hair_04.nif -Decoding meshes\b\b_n_high elf_f_hair_02.nif -Decoding meshes\b\b_n_high elf_m_hair_04.nif -Decoding meshes\b\b_n_high elf_m_hair_02.nif -Decoding meshes\b\b_n_high elf_f_forearm.nif -Decoding meshes\b\b_n_high elf_m_upper arm.nif -Decoding meshes\b\b_n_high elf_f_upper leg.nif -Decoding meshes\b\b_n_high elf_m_upper leg.nif -Decoding meshes\b\b_n_high elf_f_hands.1st.nif -Decoding meshes\b\b_n_high elf_m_hands.1st.nif -Decoding meshes\b\b_n_high elf_f_upper arm.nif -Decoding meshes\f\flora_bc_mushroom_08.nif -Decoding meshes\f\flora_bc_lilypad_02.nif -Decoding meshes\f\flora_bc_lilypad_03.nif -Decoding meshes\f\flora_bc_lilypad_01.nif -Decoding meshes\f\flora_bc_mushroom_05.nif -Decoding meshes\f\flora_bc_mushroom_01.nif -Decoding meshes\f\flora_bc_mushroom_02.nif -Decoding meshes\f\flora_bc_mushroom_03.nif -Decoding meshes\f\flora_bc_podplant_03.nif -Decoding meshes\f\flora_bc_podplant_02.nif -Decoding meshes\f\flora_bc_mushroom_07.nif -Decoding meshes\f\flora_bc_mushroom_06.nif -Decoding meshes\f\flora_bc_podplant_01.nif -Decoding meshes\f\flora_bc_mushroom_04.nif -Decoding meshes\f\flora_bc_shelffungus_02.nif -Decoding meshes\f\flora_bc_shelffungus_04.nif -Decoding meshes\f\flora_bc_shelffungus_03.nif -Decoding meshes\f\flora_bc_shelffungus_01.nif -Decoding meshes\m\misc_muck_shovel_01.nif -Decoding meshes\m\pick_secretmaster_01.nif -Decoding meshes\c\c_f_shirt_c_common01.nif -Decoding meshes\c\c_f_shirt_common_4_a_c.nif -Decoding meshes\c\c_f_shirt_common_4_c_c.nif -Decoding meshes\c\c_f_shirt_common_4_b_c.nif -Decoding meshes\c\c_f_shirt_c_commonl04.nif -Decoding meshes\c\c_f_shirt_c_commonl02.nif -Decoding meshes\c\c_f_skirt_g_common01.nif -Decoding meshes\c\c_f_skirt_g_common_4_c.nif -Decoding meshes\f\flora_emp_parasol_01.nif -Decoding meshes\f\flora_emp_parasol_02.nif -Decoding meshes\f\flora_emp_parasol_03.nif -Decoding meshes\c\c_m_robe_common_02r.nif -Decoding meshes\c\c_m_robe_common_02t.nif -Decoding meshes\c\c_m_robe_common_03a.nif -Decoding meshes\c\c_m_robe_common_05a.nif -Decoding meshes\c\c_m_robe_common_05c.nif -Decoding meshes\c\c_m_robe_common_03b.nif -Decoding meshes\c\c_m_robe_common_05b.nif -Decoding meshes\c\c_m_robe_common_02h.nif -Decoding meshes\c\c_m_robe_common_02tt.nif -Decoding meshes\c\c_m_robe_expensive_1.nif -Decoding meshes\c\c_m_robe_exquisite_1.nif -Decoding meshes\c\c_m_robe_common_02rr.nif -Decoding meshes\c\c_m_robe_extrav_1_c.nif -Decoding meshes\c\c_m_robe_common_02hh.nif -Decoding meshes\c\c_m_robe_expensive_2.nif -Decoding meshes\c\c_m_robe_common_02_gnd.nif -Decoding meshes\c\c_m_robe_common_01_gnd.nif -Decoding meshes\c\c_m_robe_extrav_2_gnd.nif -Decoding meshes\c\c_m_robe_expens_3.1st.nif -Decoding meshes\c\c_m_robe_common_4.1st.nif -Decoding meshes\c\c_m_robe_common_4_gnd.nif -Decoding meshes\c\c_m_robe_common_3_gnd.nif -Decoding meshes\c\c_m_robe_common_5.1st.nif -Decoding meshes\c\c_m_robe_common_02.1st.nif -Decoding meshes\c\c_m_robe_common_01.1st.nif -Decoding meshes\c\c_m_robe_expens_3_gnd.nif -Decoding meshes\c\c_m_robe_common_3.1st.nif -Decoding meshes\c\c_m_robe_extrav_1_gnd.nif -Decoding meshes\c\c_m_robe_extrav_2.1st.nif -Decoding meshes\c\c_m_robe_extrav_1r_gnd.nif -Decoding meshes\c\c_m_robe_extrav_1t_gnd.nif -Decoding meshes\c\c_m_robe_extrav_1h_gnd.nif -Decoding meshes\c\c_m_robe_extrav_1a_gnd.nif -Decoding meshes\c\c_m_robe_extrav_1b_gnd.nif -Decoding meshes\c\c_m_robe_extrav_1c_gnd.nif -Decoding meshes\c\c_m_robe_common_5_gnd.nif -Decoding meshes\c\c_m_robe_extrav_1t.1st.nif -Decoding meshes\c\c_m_robe_extrav_1r.1st.nif -Decoding meshes\c\c_m_robe_extrav_1a.1st.nif -Decoding meshes\c\c_m_robe_extrav_1b.1st.nif -Decoding meshes\c\c_m_robe_extrav_1c.1st.nif -Decoding meshes\c\c_m_robe_extrav_1h.1st.nif -Decoding meshes\c\c_m_robe_expensive_2a.nif -Decoding meshes\c\c_m_robe_extrav_1.1st.nif -Decoding meshes\c\c_m_robe_exquisite_1.1st.nif -Decoding meshes\c\c_m_robe_common_02hh_gnd.nif -Decoding meshes\c\c_m_robe_common_02tt.1st.nif -Decoding meshes\c\c_m_robe_common_02rr.1st.nif -Decoding meshes\c\c_m_robe_common_02hh.1st.nif -Decoding meshes\c\c_m_robe_exquisite_1_gnd.nif -Decoding meshes\c\c_m_robe_common_05a.1st.nif -Decoding meshes\c\c_m_robe_common_05b.1st.nif -Decoding meshes\c\c_m_robe_common_05c.1st.nif -Decoding meshes\c\c_m_robe_extrav_1_c.1st.nif -Decoding meshes\c\c_m_robe_common_03b_gnd.nif -Decoding meshes\c\c_m_robe_common_03a_gnd.nif -Decoding meshes\c\c_m_robe_expensive_1_gnd.nif -Decoding meshes\c\c_m_robe_common_05a_gnd.nif -Decoding meshes\c\c_m_robe_common_05b_gnd.nif -Decoding meshes\c\c_m_robe_common_05c_gnd.nif -Decoding meshes\c\c_m_robe_common_02tt_gnd.nif -Decoding meshes\c\c_m_robe_common_03b.1st.nif -Decoding meshes\c\c_m_robe_common_03a.1st.nif -Decoding meshes\c\c_m_robe_expensive_1.1st.nif -Decoding meshes\c\c_m_robe_expensive_2.1st.nif -Decoding meshes\c\c_m_robe_expensive_2_gnd.nif -Decoding meshes\c\c_m_robe_common_02h.1st.nif -Decoding meshes\c\c_m_robe_common_02t.1st.nif -Decoding meshes\c\c_m_robe_common_02r.1st.nif -Decoding meshes\c\c_m_robe_common_02rr_gnd.nif -Decoding meshes\c\c_m_robe_common_02h_gnd.nif -Decoding meshes\c\c_m_robe_common_02r_gnd.nif -Decoding meshes\c\c_m_robe_common_02t_gnd.nif -Decoding meshes\m\misc_argonianhead_01.nif -Decoding meshes\lavasteam.nif -Decoding meshes\left_arrow.nif -Decoding meshes\f\furn_ex_ashl_guarskin.nif -Decoding meshes\l\light_fire.nif -Decoding meshes\lower_arrow.nif -Decoding meshes\lava_sparks.nif -Decoding meshes\c\c_m_robe_expensive_2a_gnd.nif -Decoding meshes\c\c_m_robe_expensive_2a.1st.nif -Decoding meshes\b\b_n_khajiit_m_ankle.nif -Decoding meshes\b\b_n_khajiit_f_ankle.nif -Decoding meshes\b\b_n_khajiit_m_hair02.nif -Decoding meshes\b\b_n_khajiit_f_hair02.nif -Decoding meshes\b\b_n_khajiit_m_hair04.nif -Decoding meshes\b\b_n_khajiit_f_hair04.nif -Decoding meshes\b\b_n_khajiit_f_groin.nif -Decoding meshes\b\b_n_khajiit_m_groin.nif -Decoding meshes\b\b_n_khajiit_m_wrist.nif -Decoding meshes\b\b_n_khajiit_f_wrist.nif -Decoding meshes\b\b_n_khajiit_f_skins.nif -Decoding meshes\b\b_n_khajiit_m_skins.nif -Decoding meshes\b\b_n_khajiit_f_hair03.nif -Decoding meshes\b\b_n_khajiit_m_hair03.nif -Decoding meshes\b\b_n_khajiit_m_hair05.nif -Decoding meshes\b\b_n_khajiit_f_hair05.nif -Decoding meshes\b\b_n_khajiit_f_hair01.nif -Decoding meshes\b\b_n_khajiit_m_hair01.nif -Decoding meshes\b\b_n_khajiit_f_forearm.nif -Decoding meshes\b\b_n_khajiit_f_head_04.nif -Decoding meshes\b\b_n_khajiit_m_head_01.nif -Decoding meshes\b\b_n_khajiit_m_head_03.nif -Decoding meshes\b\b_n_khajiit_m_forearm.nif -Decoding meshes\b\b_n_khajiit_m_head_04.nif -Decoding meshes\b\b_n_khajiit_f_head_03.nif -Decoding meshes\b\b_n_khajiit_f_head_02.nif -Decoding meshes\b\b_n_khajiit_m_head_02.nif -Decoding meshes\b\b_n_khajiit_f_head_01.nif -Decoding meshes\b\b_n_khajiit_f_upper arm.nif -Decoding meshes\b\b_n_khajiit_m_upper arm.nif -Decoding meshes\b\b_n_khajiit_m_upper leg.nif -Decoding meshes\b\b_n_khajiit_f_upper leg.nif -Decoding meshes\b\b_n_khajiit_f_hands.1st.nif -Decoding meshes\b\b_n_khajiit_m_hands.1st.nif -Decoding meshes\o\flora_marshmerrow_02.nif -Decoding meshes\o\flora_marshmerrow_03.nif -Decoding meshes\o\flora_marshmerrow_01.nif -Decoding meshes\f\furn_pycave_spout00.nif -Decoding meshes\m\gold_100.nif -Decoding meshes\m\gold_025.nif -Decoding meshes\m\gold_010.nif -Decoding meshes\m\gold_001.nif -Decoding meshes\m\gold_005.nif -Decoding meshes\menu_help.nif -Decoding meshes\menu_main.nif -Decoding meshes\menu_book.nif -Decoding meshes\marker_light.nif -Decoding meshes\marker_north.nif -Decoding meshes\marker_error.nif -Decoding meshes\marker_arrow.nif -Decoding meshes\m\misc_quill.nif -Decoding meshes\menu_scroll.nif -Decoding meshes\menu_target.nif -Decoding meshes\a\a_bonemold_cuirass_c.nif -Decoding meshes\a\a_bonemold_bracer_w.nif -Decoding meshes\a\a_bonemold_boot_gnd.nif -Decoding meshes\a\a_bonemold_greaves_g.nif -Decoding meshes\a\a_bonemold_greaves_k.nif -Decoding meshes\a\a_bonemold_pauldron_fa.nif -Decoding meshes\a\a_bonemold_pauldron_ua.nif -Decoding meshes\a\a_bonemold_cuirass_gnd.nif -Decoding meshes\a\a_bonemold_gah_julan_c.nif -Decoding meshes\a\a_bonemold_armun_an_ua.nif -Decoding meshes\a\a_bonemold_greaves_gnd.nif -Decoding meshes\a\a_bonemold_bracer_gnd.nif -Decoding meshes\a\a_bonemold_armun_an_cl.nif -Decoding meshes\a\a_bonemold_greaves_ul.nif -Decoding meshes\a\a_bonemold_gah_julan_h.nif -Decoding meshes\a\a_bonemold_gah_j_ua_gnd.nif -Decoding meshes\a\a_bonemold_pauldron_gnd.nif -Decoding meshes\a\a_bonemold_armun_an_helm.nif -Decoding meshes\a\a_bonemold_gah_julan_cl.nif -Decoding meshes\a\a_bonemold_gah_julan_ua.nif -Decoding meshes\a\a_bonemold_armun_ua_gnd.nif -Decoding meshes\a\a_bonemold_chuzei_helmet.nif -Decoding meshes\d\door_redoran_tower_01.nif -Decoding meshes\a\a_bonemold_gah_julan_cgnd.nif -Decoding meshes\c\c_m_bracer_w_leather01.nif -Decoding meshes\b\b_n_orc_m_upper leg.nif -Decoding meshes\b\b_n_orc_f_upper leg.nif -Decoding meshes\b\b_n_orc_m_hands.1st.nif -Decoding meshes\b\b_n_orc_f_hands.1st.nif -Decoding meshes\b\b_n_orc_f_upper arm.nif -Decoding meshes\b\b_n_orc_m_upper arm.nif -Decoding meshes\c\c_m_bracer_w_clothwrap02.nif -Decoding meshes\l\light_de_lantern_08.nif -Decoding meshes\l\light_de_lantern_09.nif -Decoding meshes\l\light_de_lantern_04.nif -Decoding meshes\l\light_de_lantern_14.nif -Decoding meshes\l\light_de_lantern_05.nif -Decoding meshes\l\light_de_lantern_06.nif -Decoding meshes\l\light_de_lantern_07.nif -Decoding meshes\l\light_de_lantern_10.nif -Decoding meshes\l\light_de_lantern_01.nif -Decoding meshes\l\light_de_lantern_11.nif -Decoding meshes\l\light_de_lantern_02.nif -Decoding meshes\l\light_de_lantern_12.nif -Decoding meshes\l\light_de_lantern_03.nif -Decoding meshes\l\light_de_lantern_13.nif -Decoding meshes\l\light_dae_brazier00.nif -Decoding meshes\l\light_dwrv_neonbroke00.nif -Decoding meshes\l\light_de_candle_red_01.nif -Decoding meshes\l\light_de_candle_red_02.nif -Decoding meshes\l\light_de_candle_green_01.nif -Decoding meshes\l\light_de_streetlight_01.nif -Decoding meshes\l\light_de_candle_blue_01.nif -Decoding meshes\l\light_de_candle_ivory_01.nif -Decoding meshes\l\light_de_candle_blue_02.nif -Decoding meshes\o\contain_de_closet_02.nif -Decoding meshes\o\contain_com_chest_01.nif -Decoding meshes\o\contain_egg_kwama00.nif -Decoding meshes\o\contain_tramaroot_05.nif -Decoding meshes\o\contain_tramaroot_02.nif -Decoding meshes\o\contain_pot_blue_02.nif -Decoding meshes\o\contain_pot_blue_01.nif -Decoding meshes\o\contain_tramaroot_03.nif -Decoding meshes\o\contain_com_chest_02.nif -Decoding meshes\o\contain_ropecage_01.nif -Decoding meshes\o\contain_com_hutch_01.nif -Decoding meshes\o\contain_dwrv_desk00.nif -Decoding meshes\o\contain_de_closet_01.nif -Decoding meshes\o\contain_de_table_02.nif -Decoding meshes\o\contain_de_table_01.nif -Decoding meshes\o\contain_tramaroot_06.nif -Decoding meshes\o\contain_com_sack_02.nif -Decoding meshes\o\contain_com_sack_03.nif -Decoding meshes\o\contain_com_sack_01.nif -Decoding meshes\o\contain_tramaroot_01.nif -Decoding meshes\o\contain_dwrv_table00.nif -Decoding meshes\o\contain_de_chest_02.nif -Decoding meshes\o\contain_de_chest_01.nif -Decoding meshes\o\contain_tramaroot_04.nif -Decoding meshes\o\contain_dwrv_chest10.nif -Decoding meshes\o\contain_dwrv_chest00.nif -Decoding meshes\o\contain_dwrv_drawers00.nif -Decoding meshes\o\contain_de_drawers_01.nif -Decoding meshes\o\contain_rock_ebony_03.nif -Decoding meshes\o\contain_cavern_spore00.nif -Decoding meshes\o\contain_pot_redware_01.nif -Decoding meshes\o\contain_rock_glass_03.nif -Decoding meshes\o\contain_rock_glass_04.nif -Decoding meshes\o\contain_ropesphere_01.nif -Decoding meshes\o\contain_trama_shrub_06.nif -Decoding meshes\o\contain_trama_shrub_04.nif -Decoding meshes\o\contain_trama_shrub_02.nif -Decoding meshes\o\contain_rock_glass_01.nif -Decoding meshes\o\contain_rock_ebony_04.nif -Decoding meshes\o\contain_rock_ebony_07.nif -Decoding meshes\o\contain_rock_ebony_06.nif -Decoding meshes\o\contain_com_basket_01.nif -Decoding meshes\o\contain_com_closet_01.nif -Decoding meshes\o\contain_de_crate_logo.nif -Decoding meshes\o\contain_rock_glass_05.nif -Decoding meshes\o\contain_rock_ebony_05.nif -Decoding meshes\o\contain_pot_mottled_01.nif -Decoding meshes\o\contain_chest_small_02.nif -Decoding meshes\o\contain_rock_ebony_02.nif -Decoding meshes\o\contain_rock_glass_07.nif -Decoding meshes\o\contain_rock_ebony_01.nif -Decoding meshes\o\contain_dwrv_closet00.nif -Decoding meshes\o\contain_trama_shrub_05.nif -Decoding meshes\o\contain_trama_shrub_03.nif -Decoding meshes\o\contain_trama_shrub_01.nif -Decoding meshes\o\contain_chest_large_01.nif -Decoding meshes\o\contain_com_drawers_01.nif -Decoding meshes\o\contain_de_drawers_02.nif -Decoding meshes\o\contain_dwrv_barrel10.nif -Decoding meshes\o\contain_dwrv_barrel00.nif -Decoding meshes\o\contain_rock_glass_06.nif -Decoding meshes\o\contain_ropesphere_02.nif -Decoding meshes\o\contain_rock_glass_02.nif -Decoding meshes\o\contain_chest_small_01.nif -Decoding meshes\o\contain_rock_diamond_01.nif -Decoding meshes\o\contain_rock_diamond_03.nif -Decoding meshes\o\contain_rock_diamond_05.nif -Decoding meshes\o\contain_rock_diamond_07.nif -Decoding meshes\o\contain_rock_diamond_02.nif -Decoding meshes\o\contain_rock_diamond_04.nif -Decoding meshes\o\contain_rock_diamond_06.nif -Decoding meshes\o\contain_com_cupboard_01.nif -Decoding meshes\o\lootbag.nif -Decoding meshes\x\furn_de_lightpost_01.nif -Decoding meshes\c\c_m_pants_extrav_2_g.nif -Decoding meshes\c\c_m_pants_extrav_1_g.nif -Decoding meshes\c\c_m_pants_extrav_1_k.nif -Decoding meshes\c\c_m_pants_common_3_k.nif -Decoding meshes\c\c_m_pants_common_5_k.nif -Decoding meshes\c\c_m_pants_g_common00.nif -Decoding meshes\c\c_m_pants_common_5_g.nif -Decoding meshes\c\c_m_pants_common_3_g.nif -Decoding meshes\c\c_m_pants_a_common00.nif -Decoding meshes\c\c_m_pants_k_common00.nif -Decoding meshes\c\c_m_pants_k_common02.nif -Decoding meshes\c\c_m_pants_a_common02.nif -Decoding meshes\c\c_m_pants_g_common02.nif -Decoding meshes\c\c_m_pants_k_common01.nif -Decoding meshes\c\c_m_pants_g_common01.nif -Decoding meshes\c\c_m_pants_a_common01.nif -Decoding meshes\c\c_m_pants_extrav_2_k.nif -Decoding meshes\c\c_m_pants_extrav_1_a.nif -Decoding meshes\c\c_m_pants_extrav_2_a.nif -Decoding meshes\c\c_m_pants_common_5_a.nif -Decoding meshes\c\c_m_pants_common_3_a.nif -Decoding meshes\c\c_m_pants_expens_3_a.nif -Decoding meshes\c\c_m_pants_common_3c_k.nif -Decoding meshes\c\c_m_pants_extrav_1_ul.nif -Decoding meshes\c\c_m_pants_extrav_2_ul.nif -Decoding meshes\c\c_m_pants_ul_common00.nif -Decoding meshes\c\c_m_pants_common_3_ul.nif -Decoding meshes\c\c_m_pants_common_5_ul.nif -Decoding meshes\c\c_m_pants_common_3c_g.nif -Decoding meshes\c\c_m_pants_common_3b_g.nif -Decoding meshes\c\c_m_pants_ul_common02.nif -Decoding meshes\c\c_m_pants_gnd_common02.nif -Decoding meshes\c\c_m_pants_gnd_common00.nif -Decoding meshes\c\c_m_pants_ul_common01.nif -Decoding meshes\c\c_m_pants_common_3b_a.nif -Decoding meshes\c\c_m_pants_common_3c_a.nif -Decoding meshes\c\c_m_pants_expens_1_e_g.nif -Decoding meshes\c\c_m_pants_expens_1_e_a.nif -Decoding meshes\c\c_m_pants_expens_1_e_k.nif -Decoding meshes\c\c_m_pants_expens_1_a_g.nif -Decoding meshes\c\c_m_pants_expens_1_a_a.nif -Decoding meshes\c\c_m_pants_expens_1_a_k.nif -Decoding meshes\c\c_m_pants_expens_1_z_g.nif -Decoding meshes\c\c_m_pants_expens_1_z_a.nif -Decoding meshes\c\c_m_pants_expens_1_z_k.nif -Decoding meshes\c\c_m_pants_common_1_z_k.nif -Decoding meshes\c\c_m_pants_common_1_z_a.nif -Decoding meshes\c\c_m_pants_common_1_z_g.nif -Decoding meshes\c\c_m_pants_common_1_u_k.nif -Decoding meshes\c\c_m_pants_common_1_u_a.nif -Decoding meshes\c\c_m_pants_common_1_u_g.nif -Decoding meshes\c\c_m_pants_common_1_e_k.nif -Decoding meshes\c\c_m_pants_common_1_e_a.nif -Decoding meshes\c\c_m_pants_common_1_e_g.nif -Decoding meshes\c\c_m_pants_common_1_a_k.nif -Decoding meshes\c\c_m_pants_common_1_a_a.nif -Decoding meshes\c\c_m_pants_common_1_a_g.nif -Decoding meshes\c\c_m_pants_common_4_b_a.nif -Decoding meshes\c\c_m_pants_common_4_b_g.nif -Decoding meshes\c\c_m_pants_common_4_b_k.nif -Decoding meshes\c\c_m_pants_common_3b_k.nif -Decoding meshes\c\c_m_pants_common_5_gnd.nif -Decoding meshes\c\c_m_pants_common_3_gnd.nif -Decoding meshes\c\c_m_pants_extrav_1_gnd.nif -Decoding meshes\c\c_m_pants_extrav_2_gnd.nif -Decoding meshes\c\c_m_pants_common_3c_ul.nif -Decoding meshes\c\c_m_pants_common_3b_ul.nif -Decoding meshes\c\c_m_pants_gnd_common01.nif -Decoding meshes\c\c_m_pants_exquisite_1g.nif -Decoding meshes\c\c_m_pants_expens_3_gnd.nif -Decoding meshes\c\c_m_pants_common_4_b_ul.nif -Decoding meshes\c\c_m_pants_expens_1_a_ul.nif -Decoding meshes\c\c_m_pants_expens_1_e_ul.nif -Decoding meshes\c\c_m_pants_expens_1_u_ul.nif -Decoding meshes\c\c_m_pants_expens_1_z_ul.nif -Decoding meshes\c\c_m_pants_expens_1_z_gnd.nif -Decoding meshes\c\c_m_pants_expensive_1_k.nif -Decoding meshes\c\c_m_pants_expensive_1_g.nif -Decoding meshes\c\c_m_pants_expensive_1_a.nif -Decoding meshes\c\c_m_pants_expensive_2_k.nif -Decoding meshes\c\c_m_pants_expensive_2_g.nif -Decoding meshes\c\c_m_pants_expensive_2_a.nif -Decoding meshes\c\c_m_pants_exquisite_1_ul.nif -Decoding meshes\c\c_m_pants_common_1_u_gnd.nif -Decoding meshes\c\c_m_pants_common_1_a_ul.nif -Decoding meshes\c\c_m_pants_common_1_e_ul.nif -Decoding meshes\c\c_m_pants_common_1_z_ul.nif -Decoding meshes\c\c_m_pants_common_1_u_ul.nif -Decoding meshes\c\c_m_pants_common_1_z_gnd.nif -Decoding meshes\c\c_m_pants_exquisite_1_k.nif -Decoding meshes\c\c_m_pants_exquisite_1_g.nif -Decoding meshes\c\c_m_pants_exquisite_1_a.nif -Decoding meshes\c\c_m_pants_expens_1_u_gnd.nif -Decoding meshes\c\c_m_pants_common_4_b_gnd.nif -Decoding meshes\c\c_m_pants_common_3c_gnd.nif -Decoding meshes\c\c_m_pants_common_3b_gnd.nif -Decoding meshes\c\c_m_pants_common_1_a_gnd.nif -Decoding meshes\c\c_m_pants_common_1_e_gnd.nif -Decoding meshes\c\c_m_pants_exqisite_1_gnd.nif -Decoding meshes\c\c_m_pants_expens_1_a_gnd.nif -Decoding meshes\c\c_m_pants_expensive_1_ul.nif -Decoding meshes\c\c_m_pants_expensive_2_ul.nif -Decoding meshes\c\c_m_pants_expens_1_e_gnd.nif -Decoding meshes\a\a_dwemer_cl_pauldron.nif -Decoding meshes\a\a_dwemer_pauldron_ua.nif -Decoding meshes\a\a_dwemer_pauldron_fa.nif -Decoding meshes\a\a_dwemer_greaves_gnd.nif -Decoding meshes\a\a_dwemer_greaves_ul.nif -Decoding meshes\a\a_dwemer_pauldron_gnd.nif -Decoding meshes\a\a_dwemer_bracer_w_gnd.nif -Decoding meshes\i\xin_dagoth_bridge00.nif -Decoding meshes\m\text_quarto_open_04.nif -Decoding meshes\m\text_quarto_open_01.nif -Decoding meshes\m\text_quarto_open_03.nif -Decoding meshes\m\text_quarto_open_02.nif -Decoding meshes\c\c_m_pants_expensive_1_u_ul.nif -Decoding meshes\c\c_m_pants_exquisite_1_gnd.nif -Decoding meshes\c\c_m_pants_expensive_1_u_k.nif -Decoding meshes\c\c_m_pants_expensive_1_u_a.nif -Decoding meshes\c\c_m_pants_expensive_1_u_g.nif -Decoding meshes\c\c_m_pants_expensive_1_gnd.nif -Decoding meshes\c\c_m_pants_expensive_2_gnd.nif -Decoding meshes\f\furn_6th_bellhammer.nif -Decoding meshes\f\furn_6th_tallbanner.nif -Decoding meshes\f\furn_6th_dagothsymbol.nif -Decoding meshes\f\furn_6th_corpus_plate_01.nif -Decoding meshes\w\w_silver_shortsword.nif -Decoding meshes\c\c_glove_common1.1st.nif -Decoding meshes\c\c_glove_balmolagmer.nif -Decoding meshes\c\c_glove_extravagant1.nif -Decoding meshes\c\c_glove_common1_gnd.nif -Decoding meshes\c\c_glove_expensive1.1st.nif -Decoding meshes\c\c_glove_moragtong.1st.nif -Decoding meshes\c\c_glove_moragtong_gnd.nif -Decoding meshes\c\c_glove_expensive1_gnd.nif -Decoding meshes\c\c_glove_balmolagmer_gnd.nif -Decoding meshes\c\c_glove_balmolagmer.1st.nif -Decoding meshes\c\c_glove_extravagant1_gnd.nif -Decoding meshes\c\c_glove_extravagant1.1st.nif -Decoding meshes\e\magic_cast_levitate.nif -Decoding meshes\f\xfurn_redoran_flag_01.nif -Decoding meshes\c\c_m_shoe_f_leather00.nif -Decoding meshes\c\c_m_shoe_f_leather01.nif -Decoding meshes\c\c_m_shoe_common01_gnd.nif -Decoding meshes\c\c_m_shoe_expensive_1_f.nif -Decoding meshes\c\c_m_shoe_common02_gnd.nif -Decoding meshes\c\c_m_shoe_expens_1_gnd.nif -Decoding meshes\w\w_warhammer_daedric.nif -Decoding meshes\a\a_art_apostle_boots_r.nif -Decoding meshes\ashcloud.nif -Decoding meshes\w\w_art_warhammer_crusher.nif -Decoding meshes\a\a_art_apostle_boots_gnd.nif -Decoding meshes\f\furn_mudcave_pool00.nif -Decoding meshes\f\furn_mudcave_spout00.nif -Decoding meshes\f\furn_cushion_round_07.nif -Decoding meshes\f\furn_cushion_round_03.nif -Decoding meshes\f\furn_cushion_round_04.nif -Decoding meshes\f\furn_cushion_square_03.nif -Decoding meshes\f\furn_cushion_square_01.nif -Decoding meshes\f\furn_cushion_square_07.nif -Decoding meshes\f\furn_cushion_square_05.nif -Decoding meshes\f\furn_cushion_square_09.nif -Decoding meshes\f\furn_cushion_round_02.nif -Decoding meshes\f\furn_cushion_round_01.nif -Decoding meshes\f\furn_cushion_round_06.nif -Decoding meshes\f\furn_cushion_square_02.nif -Decoding meshes\f\furn_cushion_square_06.nif -Decoding meshes\f\furn_cushion_square_04.nif -Decoding meshes\f\furn_cushion_square_08.nif -Decoding meshes\f\furn_cushion_round_05.nif -Decoding meshes\f\furn_rug_big_04_collision.nif -Decoding meshes\a\a_dreugh_cuirass_gnd.nif -Decoding meshes\o\flora_bittergreen_07.nif -Decoding meshes\o\flora_bittergreen_02.nif -Decoding meshes\o\flora_bc_podplant_04.nif -Decoding meshes\o\flora_bittergreen_03.nif -Decoding meshes\o\flora_bittergreen_08.nif -Decoding meshes\o\flora_bittergreen_09.nif -Decoding meshes\o\flora_bittergreen_04.nif -Decoding meshes\o\flora_bittergreen_06.nif -Decoding meshes\o\flora_bittergreen_01.nif -Decoding meshes\o\flora_bittergreen_10.nif -Decoding meshes\o\flora_bittergreen_05.nif -Decoding meshes\o\flora_black_lichen_02.nif -Decoding meshes\o\flora_black_anther_01.nif -Decoding meshes\o\flora_black_lichen_03.nif -Decoding meshes\o\flora_black_anther_02.nif -Decoding meshes\o\flora_black_lichen_01.nif -Decoding meshes\o\flora_bittergreen_pod_01.nif -Decoding meshes\button.nif -Decoding meshes\base_anim.nif -Decoding meshes\bloodsplat.nif -Decoding meshes\bloodsplat2.nif -Decoding meshes\bloodsplat3.nif -Decoding meshes\blightcloud.nif -Decoding meshes\base_animkna.nif -Decoding meshes\m\probe_apprentice_01.nif -Decoding meshes\c\c_shoes_extrav_2_gnd.nif -Decoding meshes\c\c_shoes_common_4_gnd.nif -Decoding meshes\c\c_shoes_common_3_gnd.nif -Decoding meshes\c\c_shoes_expensive_2.nif -Decoding meshes\c\c_shoes_extrav_1_gnd.nif -Decoding meshes\c\c_shoes_common_5_gnd.nif -Decoding meshes\c\c_shoes_expensive_03.nif -Decoding meshes\c\c_shoes_exquisite_1_f.nif -Decoding meshes\c\c_shoes_expensive_2_gnd.nif -Decoding meshes\c\c_shoes_expensive_3_gnd.nif -Decoding meshes\c\c_shoes_exquisite_1_gnd.nif -Decoding meshes\l\light_hangingl_blue_01.nif -Decoding meshes\l\light_hangings_blue_01.nif -Decoding meshes\x\flora_t_mushroom_02.nif -Decoding meshes\x\flora_t_mushroom_01.nif -Decoding meshes\x\flora_t_shelffungus_01.nif -Decoding meshes\b\b_n_wood elf_m_neck.nif -Decoding meshes\b\b_n_wood elf_f_neck.nif -Decoding meshes\b\b_n_wood elf_f_skins.nif -Decoding meshes\b\b_n_wood elf_m_skins.nif -Decoding meshes\b\b_n_wood elf_m_foot.nif -Decoding meshes\b\b_n_wood elf_m_wrist.nif -Decoding meshes\b\b_n_wood elf_m_knee.nif -Decoding meshes\b\b_n_wood elf_f_knee.nif -Decoding meshes\b\b_n_wood elf_f_groin.nif -Decoding meshes\b\b_n_wood elf_m_groin.nif -Decoding meshes\b\b_n_wood elf_f_wrist.nif -Decoding meshes\b\b_n_wood elf_f_foot.nif -Decoding meshes\b\b_n_wood elf_m_ankle.nif -Decoding meshes\b\b_n_wood elf_f_ankle.nif -Decoding meshes\b\b_n_wood elf_f_head_02.nif -Decoding meshes\b\b_n_wood elf_f_head_06.nif -Decoding meshes\b\b_n_wood elf_f_head_04.nif -Decoding meshes\b\b_n_wood elf_m_head_02.nif -Decoding meshes\b\b_n_wood elf_m_head_06.nif -Decoding meshes\b\b_n_wood elf_m_head_04.nif -Decoding meshes\b\b_n_wood elf_m_head_08.nif -Decoding meshes\b\b_n_wood elf_f_hair_05.nif -Decoding meshes\b\b_n_wood elf_f_hair_01.nif -Decoding meshes\b\b_n_wood elf_f_hair_03.nif -Decoding meshes\b\b_n_wood elf_m_hair_05.nif -Decoding meshes\b\b_n_wood elf_m_hair_01.nif -Decoding meshes\b\b_n_wood elf_m_hair_03.nif -Decoding meshes\b\b_n_wood elf_m_forearm.nif -Decoding meshes\b\b_n_wood elf_f_head_03.nif -Decoding meshes\b\b_n_wood elf_f_head_01.nif -Decoding meshes\b\b_n_wood elf_f_head_05.nif -Decoding meshes\b\b_n_wood elf_m_head_03.nif -Decoding meshes\b\b_n_wood elf_m_head_01.nif -Decoding meshes\b\b_n_wood elf_m_head_07.nif -Decoding meshes\b\b_n_wood elf_m_head_05.nif -Decoding meshes\b\b_n_wood elf_f_hair_04.nif -Decoding meshes\b\b_n_wood elf_f_hair_02.nif -Decoding meshes\b\b_n_wood elf_m_hair_04.nif -Decoding meshes\b\b_n_wood elf_m_hair_06.nif -Decoding meshes\b\b_n_wood elf_m_hair_02.nif -Decoding meshes\b\b_n_wood elf_f_forearm.nif -Decoding meshes\b\b_n_wood elf_m_upper arm.nif -Decoding meshes\b\b_n_wood elf_f_upper leg.nif -Decoding meshes\b\b_n_wood elf_m_upper leg.nif -Decoding meshes\b\b_n_wood elf_f_hands.1st.nif -Decoding meshes\b\b_n_wood elf_m_hands.1st.nif -Decoding meshes\b\b_n_wood elf_f_upper arm.nif -Decoding meshes\x\terraiin_rock_ma_07.nif -Decoding meshes\x\terraiin_rock_rm_07.nif -Decoding meshes\a\a_art_cuirass_lords_c.nif -Decoding meshes\a\a_art_cuirass_savior_c.nif -Decoding meshes\cursor.nif -Decoding meshes\cursormove.nif -Decoding meshes\a\a_art_cuirass_lords_gnd.nif -Decoding meshes\a\a_art_cuirass_savior_gnd.nif -Decoding meshes\f\furn_dwrv_fitting50.nif -Decoding meshes\f\furn_dwrv_fitting40.nif -Decoding meshes\f\furn_dwrv_fitting10.nif -Decoding meshes\f\furn_dwrv_fitting00.nif -Decoding meshes\f\furn_dwrv_fitting30.nif -Decoding meshes\f\furn_dwrv_fitting20.nif -Decoding meshes\f\furn_dwrv_cabinet00.nif -Decoding meshes\f\furn_dwrv_beltdrive00.nif -Decoding meshes\f\furn_dwrv_bookshelf00.nif -Decoding meshes\cursor_drop.nif -Decoding meshes\c\c_skirt_exquisite_1.nif -Decoding meshes\c\c_skirt_common_3_gnd.nif -Decoding meshes\c\c_skirt_common_2_gnd.nif -Decoding meshes\c\c_skirt_expensive_1.nif -Decoding meshes\c\c_skirt_expensive_3.nif -Decoding meshes\c\c_skirt_expensive_2.nif -Decoding meshes\c\c_skirt_common_5_gnd.nif -Decoding meshes\c\c_skirt_extravagant_1.nif -Decoding meshes\c\c_skirt_extravagant_2.nif -Decoding meshes\c\c_skirt_extravagant_1gnd.nif -Decoding meshes\c\c_skirt_extravagant_2gnd.nif -Decoding meshes\c\c_skirt_expensive_2_gnd.nif -Decoding meshes\c\c_skirt_expensive_3_gnd.nif -Decoding meshes\c\c_skirt_expensive_1_gnd.nif -Decoding meshes\c\c_skirt_exquisite_1_gnd.nif -Decoding meshes\w\magic_target_poison.nif -Decoding meshes\w\magic_target_conjure.nif -Decoding meshes\f\flora_muckspunge_06.nif -Decoding meshes\f\flora_muckspunge_07.nif -Decoding meshes\f\flora_muckspunge_04.nif -Decoding meshes\f\flora_muckspunge_05.nif -Decoding meshes\f\flora_muckspunge_02.nif -Decoding meshes\f\flora_muckspunge_03.nif -Decoding meshes\f\flora_muckspunge_01.nif -Decoding meshes\b\b_v_imperial_m_head_01.nif -Decoding meshes\b\b_v_imperial_f_head_01.nif -Decoding meshes\x\terrain_rock_ma_550.nif -Decoding meshes\x\terrain_lava_ventlg.nif -Decoding meshes\x\terrain_rock_ma_arch.nif -Decoding meshes\x\terrain_lava_ventlg01.nif -Decoding meshes\x\terrain_ashland_rock_16.nif -Decoding meshes\x\terrain_ashland_rock_14.nif -Decoding meshes\x\terrain_ashland_rock_12.nif -Decoding meshes\x\terrain_ashland_rock_10.nif -Decoding meshes\x\terrain_ashland_rock_08.nif -Decoding meshes\x\terrain_ashland_rock_04.nif -Decoding meshes\x\terrain_ashland_rock_06.nif -Decoding meshes\x\terrain_ashland_rock_02.nif -Decoding meshes\x\terrain_ashland_rock_15.nif -Decoding meshes\x\terrain_ashland_rock_13.nif -Decoding meshes\x\terrain_ashland_rock_11.nif -Decoding meshes\x\terrain_ashland_rock_09.nif -Decoding meshes\x\terrain_ashland_rock_05.nif -Decoding meshes\x\terrain_ashland_rock_07.nif -Decoding meshes\x\terrain_ashland_rock_01.nif -Decoding meshes\x\terrain_ashland_rock_03.nif -Decoding meshes\f\furn_spinningwheel_01.nif -Decoding meshes\f\xact_banner_hla_oad.nif -Decoding meshes\f\xact_banner_tel_fyr.nif -Decoding meshes\f\xact_banner_tel_mora.nif -Decoding meshes\f\xact_banner_tel_vos.nif -Decoding meshes\f\xact_banner_tel_aruhn.nif -Decoding meshes\f\xact_banner_gnaar_mok.nif -Decoding meshes\f\xact_banner_sadrith_mora.nif -Decoding meshes\f\xact_banner_ald_velothi.nif -Decoding meshes\f\xact_banner_tel_branora.nif -Decoding meshes\a\a_art_dragon_cuirass_c.nif -Decoding meshes\d\ex_dae_door_load_oval.nif -Decoding meshes\d\scene root.nif -Decoding meshes\c\c_skirt_extravagant_2_gnd.nif -Decoding meshes\c\c_skirt_extravagant_1_gnd.nif -Decoding meshes\m\probe_grandmaster_01.nif -Decoding meshes\a\a_m_chitin_hands.1st.nif -Decoding meshes\a\a_m_chitin_g_greaves.nif -Decoding meshes\a\a_m_chitin_boot_gnd.nif -Decoding meshes\a\a_m_chitin_ua_pauldron.nif -Decoding meshes\a\a_m_chitin_greaves_gnd.nif -Decoding meshes\a\a_m_chitin_ul_greaves.nif -Decoding meshes\a\a_m_chitin_cuirass_gnd.nif -Decoding meshes\a\a_m_chitin_pauldron_cl.nif -Decoding meshes\a\a_m_chitin_gauntlet_gnd.nif -Decoding meshes\a\a_art_ebon_cuirass_c.nif -Decoding meshes\a\a_art_ebon_cuirass_gnd.nif -Decoding meshes\w\w_art_staff_hasedoki.nif -Decoding meshes\e\blight.nif -Decoding meshes\e\hand01.nif -Decoding meshes\e\absorb.nif -Decoding meshes\e\cure_hit.nif -Decoding meshes\e\corprus.nif -Decoding meshes\w\shadowshortbladeonehand.nif -Decoding meshes\e\magic_hit.nif -Decoding meshes\e\magic_cast.nif -Decoding meshes\e\magic_area.nif -Decoding meshes\editormarker.nif -Decoding meshes\e\frost_hit.nif -Decoding meshes\b\b_v_dark elf_f_head_01.nif -Decoding meshes\b\b_v_dark elf_m_head_01.nif -Decoding meshes\a\a_nordicfur_skinned.nif -Decoding meshes\a\a_nordicfur_bracer_w.nif -Decoding meshes\a\a_nordicfur_boot_gnd.nif -Decoding meshes\a\a_nordicfur_greave_g.nif -Decoding meshes\a\a_nordicfur_greave_gnd.nif -Decoding meshes\a\a_nordicfur_hands.1st.nif -Decoding meshes\a\a_nordicfur_greave_ul.nif -Decoding meshes\a\a_nordicfur_pauldron_gnd.nif -Decoding meshes\a\a_nordicfur_gauntlet_gnd.nif -Decoding meshes\a\a_nordicfur_pauldron_cl.nif -Decoding meshes\a\a_nordicfur_pauldron_ua.nif -Decoding meshes\f\furn_practice_dummy.nif -Decoding meshes\a\a_art_fists_gauntlets.nif -Decoding meshes\fire_small.nif -Decoding meshes\f\ex_turf00.nif -Decoding meshes\f\furn_web00.nif -Decoding meshes\f\furn_web10.nif -Decoding meshes\f\furn_cot00.nif -Decoding meshes\a\a_art_fists_gauntlets.1st.nif -Decoding meshes\a\a_nordicfur_m_cuirass_gnd.nif -Decoding meshes\a\a_netch_m_greave_ul.nif -Decoding meshes\a\a_netch_m_greave_gnd.nif -Decoding meshes\a\a_netch_m_hands.1st.nif -Decoding meshes\a\a_netch_boiled_helm.nif -Decoding meshes\a\a_netch_m_pauldron_ua.nif -Decoding meshes\a\a_netch_m_pauldron_gnd.nif -Decoding meshes\a\a_netch_m_currais2_gnd.nif -Decoding meshes\a\a_netch_m_cuirass_gnd.nif -Decoding meshes\a\a_netch_m_pauldron_cl.nif -Decoding meshes\a\a_netch_m_gauntlet_gnd.nif -Decoding meshes\c\c_m_shirt_common_3_w.nif -Decoding meshes\c\c_m_shirt_common_5_w.nif -Decoding meshes\c\c_m_shirt_common_5_c.nif -Decoding meshes\c\c_m_shirt_common_3_c.nif -Decoding meshes\c\c_m_shirt_expens_3_w.nif -Decoding meshes\c\c_m_shirt_extrav_1_c.nif -Decoding meshes\c\c_m_shirt_extrav_2_c.nif -Decoding meshes\c\c_m_shirt_c_common01.nif -Decoding meshes\c\c_m_shirt_common_5_f.nif -Decoding meshes\c\c_m_shirt_extrav_1_w.nif -Decoding meshes\c\c_m_shirt_extrav_2_w.nif -Decoding meshes\c\c_m_shirt_c_common03.nif -Decoding meshes\c\c_m_shirt_w_common03.nif -Decoding meshes\c\c_m_shirt_expens_3_c.nif -Decoding meshes\c\c_m_shirt_extrav_1_tfa.nif -Decoding meshes\c\c_m_shirt_extrav_1_rfa.nif -Decoding meshes\c\c_m_shirt_extrav_1_hfa.nif -Decoding meshes\c\c_m_shirt_expens_1_z_f.nif -Decoding meshes\c\c_m_shirt_common_1_z_c.nif -Decoding meshes\c\c_m_shirt_common_1_u_w.nif -Decoding meshes\c\c_m_shirt_common_1_u_c.nif -Decoding meshes\c\c_m_shirt_common_2tt_w.nif -Decoding meshes\c\c_m_shirt_common_2tt_c.nif -Decoding meshes\c\c_m_shirt_common_2_t_w.nif -Decoding meshes\c\c_m_shirt_common_2_t_c.nif -Decoding meshes\c\c_m_shirt_common_2rr_w.nif -Decoding meshes\c\c_m_shirt_common_2rr_c.nif -Decoding meshes\c\c_m_shirt_common_2_r_w.nif -Decoding meshes\c\c_m_shirt_common_2_r_c.nif -Decoding meshes\c\c_m_shirt_common_2hh_w.nif -Decoding meshes\c\c_m_shirt_common_2hh_c.nif -Decoding meshes\c\c_m_shirt_common_2_h_w.nif -Decoding meshes\c\c_m_shirt_common_2_h_c.nif -Decoding meshes\c\c_m_shirt_common_1_e_w.nif -Decoding meshes\c\c_m_shirt_common_1_e_c.nif -Decoding meshes\c\c_m_shirt_common_4_a_w.nif -Decoding meshes\c\c_m_shirt_common_1_a_w.nif -Decoding meshes\c\c_m_shirt_common_4_a_c.nif -Decoding meshes\c\c_m_shirt_common_1_a_c.nif -Decoding meshes\c\c_m_shirt_common_4_c_w.nif -Decoding meshes\c\c_m_shirt_common_4_c_c.nif -Decoding meshes\c\c_m_shirt_common_4_b_w.nif -Decoding meshes\c\c_m_shirt_common_4_b_c.nif -Decoding meshes\c\c_m_shirt_gondalier_c.nif -Decoding meshes\c\c_m_shirt_common_3c_w.nif -Decoding meshes\c\c_m_shirt_extrav_2_ua.nif -Decoding meshes\c\c_m_shirt_extrav_1_ua.nif -Decoding meshes\c\c_m_shirt_extrav_2_fa.nif -Decoding meshes\c\c_m_shirt_extrav_1_fa.nif -Decoding meshes\c\c_m_shirt_extrav_1_tw.nif -Decoding meshes\c\c_m_shirt_fa_commonl02.nif -Decoding meshes\c\c_m_shirt_fa_commonl04.nif -Decoding meshes\c\c_m_shirt_ua_commonl02.nif -Decoding meshes\c\c_m_shirt_ua_commonl04.nif -Decoding meshes\c\c_m_shirt_common_3c_c.nif -Decoding meshes\c\c_m_shirt_common_3b_c.nif -Decoding meshes\c\c_m_shirt_common_3c_ua.nif -Decoding meshes\c\c_m_shirt_common_3b_ua.nif -Decoding meshes\c\c_m_shirt_extrav_1_rw.nif -Decoding meshes\c\c_m_shirt_ua_common01.nif -Decoding meshes\c\c_m_shirt_common_3c_fa.nif -Decoding meshes\c\c_m_shirt_expens_1_e_w.nif -Decoding meshes\c\c_m_shirt_expens_1_e_c.nif -Decoding meshes\c\c_m_shirt_expens_1_a_w.nif -Decoding meshes\c\c_m_shirt_expens_1_z_w.nif -Decoding meshes\c\c_m_shirt_expens_1_z_c.nif -Decoding meshes\c\c_m_shirt_common_1_u_f.nif -Decoding meshes\c\c_m_shirt_common_1_a_f.nif -Decoding meshes\c\c_m_shirt_common_5_gnd.nif -Decoding meshes\c\c_m_shirt_common_3_gnd.nif -Decoding meshes\c\c_m_shirt_extrav_1_hw.nif -Decoding meshes\c\c_m_shirt_common_3_ua.nif -Decoding meshes\c\c_m_shirt_common_5_ua.nif -Decoding meshes\c\c_m_shirt_common_3_fa.nif -Decoding meshes\c\c_m_shirt_extrav_1_hua.nif -Decoding meshes\c\c_m_shirt_expens_3_fa.nif -Decoding meshes\c\c_m_shirt_expens_3_ua.nif -Decoding meshes\c\c_m_shirt_extrav_1_rua.nif -Decoding meshes\c\c_m_shirt_w_commonl04.nif -Decoding meshes\c\c_m_shirt_c_commonl04.nif -Decoding meshes\c\c_m_shirt_extrav_1_gnd.nif -Decoding meshes\c\c_m_shirt_extrav_2_gnd.nif -Decoding meshes\c\c_m_shirt_extrav_1_tua.nif -Decoding meshes\c\c_m_shirt_ua_common03.nif -Decoding meshes\c\c_m_shirt_fa_common03.nif -Decoding meshes\c\c_m_shirt_gnd_common03.nif -Decoding meshes\c\c_m_shirt_gnd_common01.nif -Decoding meshes\c\c_m_shirt_extrav_1_hc.nif -Decoding meshes\c\c_m_shirt_extrav_1_tc.nif -Decoding meshes\c\c_m_shirt_extrav_1_rc.nif -Decoding meshes\c\c_m_shirt_expens_3_gnd.nif -Decoding meshes\c\c_m_shirt_c_commonl02.nif -Decoding meshes\c\c_m_shirt_expens_1_z_gnd.nif -Decoding meshes\c\c_m_shirt_expensive_1_c.nif -Decoding meshes\c\c_m_shirt_expensive_1_w.nif -Decoding meshes\c\c_m_shirt_expensive_2_c.nif -Decoding meshes\c\c_m_shirt_expensive_2_w.nif -Decoding meshes\c\c_m_shirt_exquisite_1_ua.nif -Decoding meshes\c\c_m_shirt_common_1_u_gnd.nif -Decoding meshes\c\c_m_shirt_common_1_z_gnd.nif -Decoding meshes\c\c_m_shirt_common_1_e_fa.nif -Decoding meshes\c\c_m_shirt_exquisite_1_w.nif -Decoding meshes\c\c_m_shirt_exquisite_1_c.nif -Decoding meshes\c\c_m_shirt_exquisite_1_fa.nif -Decoding meshes\c\c_m_shirt_common_2hh_gnd.nif -Decoding meshes\c\c_m_shirt_common_2_h_gnd.nif -Decoding meshes\c\c_m_shirt_expens_1_u_gnd.nif -Decoding meshes\c\c_m_shirt_gondolier_gnd.nif -Decoding meshes\c\c_m_shirt_common_4_c_gnd.nif -Decoding meshes\c\c_m_shirt_common_4_a_ua.nif -Decoding meshes\c\c_m_shirt_common_4_c_ua.nif -Decoding meshes\c\c_m_shirt_common_4_b_ua.nif -Decoding meshes\c\c_m_shirt_common_2tt_gnd.nif -Decoding meshes\c\c_m_shirt_common_2_t_gnd.nif -Decoding meshes\c\c_m_shirt_common_4_b_gnd.nif -Decoding meshes\c\c_m_shirt_expens_1_u_fa.nif -Decoding meshes\c\c_m_shirt_expens_1_e_fa.nif -Decoding meshes\c\c_m_shirt_expens_1_a_fa.nif -Decoding meshes\c\c_m_shirt_expens_1_a_ua.nif -Decoding meshes\c\c_m_shirt_expens_1_e_ua.nif -Decoding meshes\c\c_m_shirt_expens_1_u_ua.nif -Decoding meshes\c\c_m_shirt_expens_1_z_ua.nif -Decoding meshes\c\c_m_shirt_common_4_a_gnd.nif -Decoding meshes\c\c_m_shirt_common_3c_gnd.nif -Decoding meshes\c\c_m_shirt_common_3b_gnd.nif -Decoding meshes\c\c_m_shirt_common_1_a_gnd.nif -Decoding meshes\c\c_m_shirt_expensive_1_fa.nif -Decoding meshes\c\c_m_shirt_expensive_2_fa.nif -Decoding meshes\c\c_m_shirt_common_2_h_fa.nif -Decoding meshes\c\c_m_shirt_common_2_r_fa.nif -Decoding meshes\c\c_m_shirt_common_2_t_fa.nif -Decoding meshes\c\c_m_shirt_common_2rr_gnd.nif -Decoding meshes\c\c_m_shirt_common_2_r_gnd.nif -Decoding meshes\c\c_m_shirt_common_1_e_gnd.nif -Decoding meshes\c\c_m_shirt_gnd_commonl02.nif -Decoding meshes\c\c_m_shirt_gnd_commonl04.nif -Decoding meshes\c\c_m_shirt_common_1_a_ua.nif -Decoding meshes\c\c_m_shirt_common_1_e_ua.nif -Decoding meshes\c\c_m_shirt_common_1_z_ua.nif -Decoding meshes\c\c_m_shirt_common_1_u_ua.nif -Decoding meshes\c\c_m_shirt_common_2hh_fa.nif -Decoding meshes\c\c_m_shirt_extrav_1_hgnd.nif -Decoding meshes\c\c_m_shirt_common_2_t_ua.nif -Decoding meshes\c\c_m_shirt_common_2tt_ua.nif -Decoding meshes\c\c_m_shirt_expens_1_a_gnd.nif -Decoding meshes\c\c_m_shirt_common_2_r_ua.nif -Decoding meshes\c\c_m_shirt_common_2rr_ua.nif -Decoding meshes\c\c_m_shirt_common_2_h_ua.nif -Decoding meshes\c\c_m_shirt_common_2hh_ua.nif -Decoding meshes\c\c_m_shirt_common_4_a_fa.nif -Decoding meshes\c\c_m_shirt_common_4_b_fa.nif -Decoding meshes\c\c_m_shirt_common_4_c_fa.nif -Decoding meshes\c\c_m_shirt_expensive_1_ua.nif -Decoding meshes\c\c_m_shirt_expensive_2_ua.nif -Decoding meshes\c\c_m_shirt_expens_1_e_gnd.nif -Decoding meshes\c\c_m_shirt_common_2rr_fa.nif -Decoding meshes\c\c_m_shirt_extrav_1_tgnd.nif -Decoding meshes\c\c_m_shirt_common_2tt_fa.nif -Decoding meshes\c\c_m_shirt_extrav_1_rgnd.nif -Decoding meshes\c\c_m_skirt_imperial_gnd.nif -Decoding meshes\c\c_m_skirt_templar_gnd.nif -Decoding meshes\c\c_m_skirt_common_01_gnd.nif -Decoding meshes\o\flora_gold_kanet_02.nif -Decoding meshes\o\flora_gold_kanet_01.nif -Decoding meshes\o\flora_green_lichen_02.nif -Decoding meshes\o\flora_green_lichen_03.nif -Decoding meshes\o\flora_green_lichen_01.nif -Decoding meshes\a\a_art_gauntlet_fist_gnd.nif -Decoding meshes\x\furn_imp_rubble_ring.nif -Decoding meshes\f\furn_ashl_bugbowl_01.nif -Decoding meshes\f\furn_ashl_bugbowl_02.nif -Decoding meshes\f\furn_ashl_bugbowl_03.nif -Decoding meshes\f\furn_ashl_chimes_01.nif -Decoding meshes\c\c_m_skirt_common_04_c_gnd.nif -Decoding meshes\c\c_m_shirt_expensive_1_a_c.nif -Decoding meshes\c\c_m_shirt_expensive_1_u_fa.nif -Decoding meshes\c\c_m_shirt_expensive_1_u_ua.nif -Decoding meshes\c\c_m_shirt_exquisite_1_gnd.nif -Decoding meshes\c\c_m_shirt_expensive_1_u_c.nif -Decoding meshes\c\c_m_shirt_expensive_1_u_w.nif -Decoding meshes\c\c_m_shirt_expensive_1_gnd.nif -Decoding meshes\c\c_m_shirt_expensive_2_gnd.nif -Decoding meshes\l\light_spear_skull00.nif -Decoding meshes\c\c_indoril_m_ul_pants.nif -Decoding meshes\c\c_indoril_m_g_pants.nif -Decoding meshes\c\c_indoril_m_k_pants.nif -Decoding meshes\x\ex_imp_plaza_stairs.nif -Decoding meshes\x\ex_imp_guardtower_02.nif -Decoding meshes\x\ex_imp_foundation_01.nif -Decoding meshes\x\ex_imp_towerb_med_01.nif -Decoding meshes\x\ex_imp_towers_med_01.nif -Decoding meshes\x\ex_imp_wall_arch_01.nif -Decoding meshes\x\ex_imp_arrowslit_01.nif -Decoding meshes\x\ex_imp_govman_stair.nif -Decoding meshes\x\ex_imp_kdoorframe_01.nif -Decoding meshes\x\ex_imp_wall_tower_01.nif -Decoding meshes\x\ex_imp_towerb_top_01.nif -Decoding meshes\x\ex_imp_towers_top_01.nif -Decoding meshes\x\ex_imp_gov_arrowlit.nif -Decoding meshes\x\ex_imp_guardtower_01.nif -Decoding meshes\x\ex_imp_foundation_02.nif -Decoding meshes\x\ex_imp_dragonstatue.nif -Decoding meshes\x\ex_imp_towers_light_01.nif -Decoding meshes\x\ex_imp_towerb_cons_02.nif -Decoding meshes\x\ex_imp_towers_base_01.nif -Decoding meshes\x\ex_imp_towerb_dark_01.nif -Decoding meshes\x\ex_imp_towerb_base_01.nif -Decoding meshes\x\ex_imp_towerb_cons_01.nif -Decoding meshes\x\ex_imp_towers_dark_01.nif -Decoding meshes\x\ex_imp_towerb_light_01.nif -Decoding meshes\x\ex_imp_wall_stairs_02.nif -Decoding meshes\x\ex_imp_wall_stairs_03.nif -Decoding meshes\x\ex_imp_wall_corner_02.nif -Decoding meshes\x\ex_imp_wall_stairs_04.nif -Decoding meshes\x\ex_imp_wall_corner_01.nif -Decoding meshes\x\ex_imp_wall_stairs_01.nif -Decoding meshes\x\ex_imp_govmansion_gate.nif -Decoding meshes\x\ex_imp_govmansion_wing.nif -Decoding meshes\x\ex_imp_towerb_roof_pitch.nif -Decoding meshes\x\ex_imp_govmansion_donjon.nif -Decoding meshes\x\contain_tramaroot_06.nif -Decoding meshes\xyz_pole2.nif -Decoding meshes\x\ex_skiff.nif -Decoding meshes\x\ex_ar_01.nif -Decoding meshes\x\ex_gg_02.nif -Decoding meshes\x\ex_gg_03.nif -Decoding meshes\x\ex_gg_01.nif -Decoding meshes\xbase_anim.nif -Decoding meshes\x\ex_dae_ruin_02_skew.nif -Decoding meshes\x\ex_dae_mehrunesdagon.nif -Decoding meshes\x\ex_dae_b_bo_slpiece.nif -Decoding meshes\x\ex_dae_b_bo_bskirt1.nif -Decoding meshes\x\ex_dae_b_bo_bskirt2.nif -Decoding meshes\x\ex_dae_pillar_02_ruin.nif -Decoding meshes\x\ex_dae_ruin_entry.max.nif -Decoding meshes\x\ex_dae_ruin_04_skew_01.nif -Decoding meshes\x\ex_dae_ruin_04_skew_02.nif -Decoding meshes\x\ex_dae_ruin_02_skew_02.nif -Decoding meshes\x\ex_dae_b_bo_frontskirt.nif -Decoding meshes\x\ex_dae_boethiah_small.nif -Decoding meshes\x\ex_dae_malacath_small.nif -Decoding meshes\x\ex_dae_molagbal_small.nif -Decoding meshes\x\ex_dae_malacath_attack.nif -Decoding meshes\x\ex_dae_malacath_stand.nif -Decoding meshes\x\ex_dae_ruin_platform_01.nif -Decoding meshes\x\ex_dae_buttress_ruin_01.nif -Decoding meshes\x\ex_dae_sheogorath_small.nif -Decoding meshes\x\ex_dae_pillar_02_ruin_02.nif -Decoding meshes\x\ex_com_dframe_lthus.nif -Decoding meshes\x\ex_imp_govmansion_barbican.nif -Decoding meshes\x\ex_de_ship.nif -Decoding meshes\x\ex_de_oar.nif -Decoding meshes\x\ex_trellis.nif -Decoding meshes\x\ex_ruin_10.nif -Decoding meshes\x\ex_ruin_00.nif -Decoding meshes\x\ex_ruin_30.nif -Decoding meshes\x\ex_ruin_20.nif -Decoding meshes\x\ex_ruin_40.nif -Decoding meshes\x\ex_t_hook.nif -Decoding meshes\x\ex_dae_mehrunesdagon_small.nif -Decoding meshes\x\ex_dae_ruin_stair01_short.nif -Decoding meshes\w\w_dai-katana_daedric.nif -Decoding meshes\a\a_templar_m_w_bracer.nif -Decoding meshes\a\a_templar_greaves_k.nif -Decoding meshes\a\a_templar_greaves_g.nif -Decoding meshes\a\a_templar_m_boot_gnd.nif -Decoding meshes\a\a_templar_greaves_ul.nif -Decoding meshes\a\a_templar_pauldron_ua.nif -Decoding meshes\a\a_templar_pauldron_fa.nif -Decoding meshes\a\a_templar_greaves_gnd.nif -Decoding meshes\a\a_templar_m_cl_pauldron.nif -Decoding meshes\a\a_templar_m_cuirass_gnd.nif -Decoding meshes\a\a_masque_clavicus_vile.nif -Decoding meshes\w\w_battleaxe_daedric.nif -Decoding meshes\a\a_trollbone_cuirass_gnd.nif -Decoding meshes\f\furn_imp_rubble_ring.nif -Decoding meshes\f\furn_imp_stoneblock_01.nif -Decoding meshes\w\w_wakazashi_daedric.nif -Decoding meshes\a\a_imperial_greaves_g.nif -Decoding meshes\a\a_imperial_hands.1st.nif -Decoding meshes\a\a_imperial_greaves_k.nif -Decoding meshes\a\a_imperial_m_helmet.nif -Decoding meshes\a\a_imperial_a_boot01.nif -Decoding meshes\a\a_imperial_cl_pauldron.nif -Decoding meshes\a\a_imperial_ua_pauldron.nif -Decoding meshes\a\a_imperial_greaves_gnd.nif -Decoding meshes\a\a_imperial_greaves_ul.nif -Decoding meshes\a\a_imperial_a_boot_gnd.nif -Decoding meshes\a\a_imperial_m_cuirass_gnd.nif -Decoding meshes\a\a_imperial_pauldron_gnd.nif -Decoding meshes\a\a_indoril_m_boot_gnd.nif -Decoding meshes\a\a_indoril_m_fa_mail.nif -Decoding meshes\a\a_indoril_m_hands.1st.nif -Decoding meshes\a\a_indoril_m_cl_pauldron.nif -Decoding meshes\a\a_indoril_m_cuirass_gnd.nif -Decoding meshes\a\a_indoril_m_ua_pauldron.nif -Decoding meshes\a\a_indoril_m_pauldron_gnd.nif -Decoding meshes\a\a_indoril_m_gauntlet_gnd.nif -Decoding meshes\f\terrain_rocks_gl_03.nif -Decoding meshes\f\terrain_rocks_gl_02.nif -Decoding meshes\f\terrain_rocks_gl_01.nif -Decoding meshes\f\terrain_rocks_gl_04.nif -Decoding meshes\f\terrain_cairn_al_01.nif -Decoding meshes\f\terrain_cairn_al_02.nif -Decoding meshes\f\terrain_cairn_al_03.nif -Decoding meshes\f\terrain_rocks_wg_01.nif -Decoding meshes\f\terrain_rocks_wg_03.nif -Decoding meshes\f\terrain_rocks_wg_02.nif -Decoding meshes\f\terrain_rocks_wg_04.nif -Decoding meshes\f\terrain_cairn_ma_01.nif -Decoding meshes\f\terrain_cairn_ma_03.nif -Decoding meshes\f\terrain_cairn_ma_02.nif -Decoding meshes\f\terrain_rocks_ai_01.nif -Decoding meshes\f\terrain_rocks_ai_02.nif -Decoding meshes\f\terrain_rocks_ai_03.nif -Decoding meshes\f\terrain_rocks_ai_04.nif -Decoding meshes\w\w_art_longbow_shade.nif -Decoding meshes\f\furn_uni_weaponrack_01.nif -Decoding meshes\f\furn_uni_spearholder_01.nif -Decoding meshes\w\shadowlongbladeonehand.nif -Decoding meshes\w\shadowlongbladetwoclose.nif -Decoding meshes\f\furn_kneeling_stool_01.nif -Decoding meshes\m\artifact_devourer_01.nif -Decoding meshes\m\artifact_bittercup_01.nif -Decoding meshes\f\terrain_natural_bridge_01.nif -Decoding meshes\a\a_imperial_a_gauntlet_gnd.nif -Decoding meshes\c\c_templar_m_g_skirt.nif -Decoding meshes\l\light_paper_lantern_02.nif -Decoding meshes\l\light_paper_lantern_01.nif -Decoding meshes\l\light_paper_lantern_off.nif -Decoding meshes\f\flora_rm_scathecraw_02.nif -Decoding meshes\f\flora_rm_scathecraw_01.nif -Decoding meshes\b\b_v_wood elf_f_head_01.nif -Decoding meshes\b\b_v_wood elf_m_head_01.nif -Decoding meshes\m\misc_de_fishing_pole.nif -Decoding meshes\f\furn_pole_support_01.nif -Decoding meshes\w\w_art_molagbal_mace.nif -Decoding meshes\w\w_art_mehrunesrazor.nif -Decoding meshes\w\shadowmarksmancrossbow.nif -Decoding meshes\m\misc_redware_platter.nif -Decoding meshes\m\misc_redware_bowl_01.nif -Decoding meshes\m\misc_redware_pitcher.nif -Decoding meshes\f\furn_moldcave_pool00.nif -Decoding meshes\f\furn_moldcave_spout00.nif -Decoding meshes\f\furn_bonecave_pool00.nif -Decoding meshes\f\furn_bonecave_spout00.nif -Decoding meshes\f\furn_com_tapestry_02.nif -Decoding meshes\f\furn_com_cauldron_02.nif -Decoding meshes\f\furn_com_tapestry_03.nif -Decoding meshes\f\furn_com_tapestry_01.nif -Decoding meshes\f\furn_com_wincover_02.nif -Decoding meshes\f\furn_com_wincover_03.nif -Decoding meshes\f\furn_com_wincover_05.nif -Decoding meshes\f\furn_com_wincover_04.nif -Decoding meshes\f\furn_com_tapestry_05.nif -Decoding meshes\f\furn_com_cauldron_01.nif -Decoding meshes\f\furn_com_tapestry_04.nif -Decoding meshes\f\furn_com_bookshelf_01.nif -Decoding meshes\f\furn_com_coatofarms_01.nif -Decoding meshes\f\furn_com_torch_ring_02.nif -Decoding meshes\f\furn_com_coatofarms_02.nif -Decoding meshes\f\furn_com_torch_ring_01.nif -Decoding meshes\f\furn_com_bookshelf_02.nif -Decoding meshes\f\furn_com_lantern_hook.nif -Decoding meshes\f\furn_com_lantern_hook_02.nif -Decoding meshes\l\furn_de_firepit_f_01.nif -Decoding meshes\a\a_newtscale_cuirass.nif -Decoding meshes\b\b_n_imperial_m_knee.nif -Decoding meshes\b\b_n_imperial_f_knee.nif -Decoding meshes\b\b_n_imperial_f_wrist.nif -Decoding meshes\b\b_n_imperial_f_skins.nif -Decoding meshes\b\b_n_imperial_m_skins.nif -Decoding meshes\b\b_n_imperial_m_foot.nif -Decoding meshes\b\b_n_imperial_f_groin.nif -Decoding meshes\b\b_n_imperial_m_groin.nif -Decoding meshes\b\b_n_imperial_m_neck.nif -Decoding meshes\b\b_n_imperial_f_neck.nif -Decoding meshes\b\b_n_imperial_m_wrist.nif -Decoding meshes\b\b_n_imperial_m_ankle.nif -Decoding meshes\b\b_n_imperial_f_ankle.nif -Decoding meshes\b\b_n_imperial_f_foot.nif -Decoding meshes\b\b_n_imperial_m_hair_07.nif -Decoding meshes\b\b_n_imperial_m_hair_05.nif -Decoding meshes\b\b_n_imperial_m_hair_03.nif -Decoding meshes\b\b_n_imperial_m_hair_01.nif -Decoding meshes\b\b_n_imperial_m_hair_09.nif -Decoding meshes\b\b_n_imperial_f_hair_07.nif -Decoding meshes\b\b_n_imperial_f_hair_05.nif -Decoding meshes\b\b_n_imperial_f_hair_03.nif -Decoding meshes\b\b_n_imperial_f_hair_01.nif -Decoding meshes\b\b_n_imperial_m_head_02.nif -Decoding meshes\b\b_n_imperial_m_head_06.nif -Decoding meshes\b\b_n_imperial_m_head_04.nif -Decoding meshes\b\b_n_imperial_f_head_02.nif -Decoding meshes\b\b_n_imperial_f_head_06.nif -Decoding meshes\b\b_n_imperial_f_head_04.nif -Decoding meshes\b\b_n_imperial_m_forearm.nif -Decoding meshes\b\b_n_imperial_m_hair_06.nif -Decoding meshes\b\b_n_imperial_m_hair_04.nif -Decoding meshes\b\b_n_imperial_m_hair_02.nif -Decoding meshes\b\b_n_imperial_m_hair_00.nif -Decoding meshes\b\b_n_imperial_m_hair_08.nif -Decoding meshes\b\b_n_imperial_f_hair_06.nif -Decoding meshes\b\b_n_imperial_f_hair_04.nif -Decoding meshes\b\b_n_imperial_f_hair_02.nif -Decoding meshes\b\b_n_imperial_f_forearm.nif -Decoding meshes\b\b_n_imperial_m_head_03.nif -Decoding meshes\b\b_n_imperial_m_head_01.nif -Decoding meshes\b\b_n_imperial_m_head_07.nif -Decoding meshes\b\b_n_imperial_m_head_05.nif -Decoding meshes\b\b_n_imperial_f_head_03.nif -Decoding meshes\b\b_n_imperial_f_head_01.nif -Decoding meshes\b\b_n_imperial_f_head_07.nif -Decoding meshes\b\b_n_imperial_f_head_05.nif -Decoding meshes\a\a_m_imperialchain_gr_g.nif -Decoding meshes\a\a_m_imperialchain_pa_gnd.nif -Decoding meshes\a\a_m_imperialchain_c_gnd.nif -Decoding meshes\a\a_m_imperialchain_gr_ul.nif -Decoding meshes\a\a_m_imperialchain_helmet.nif -Decoding meshes\a\a_m_imperialchain_gr_gnd.nif -Decoding meshes\a\a_m_imperialchain_pa_ua.nif -Decoding meshes\b\b_n_imperial_f_upper leg.nif -Decoding meshes\b\b_n_imperial_m_upper leg.nif -Decoding meshes\b\b_n_imperial_m_hands.1st.nif -Decoding meshes\b\b_n_imperial_f_upper arm.nif -Decoding meshes\b\b_n_imperial_f_hands.1st.nif -Decoding meshes\b\b_n_imperial_m_upper arm.nif -Decoding meshes\m\pick_grandmaster_01.nif -Decoding meshes\d\door_dwrv_loaddown00.nif -Decoding meshes\f\furn_shrine_rilms_01.nif -Decoding meshes\f\furn_shrine_relms_01.nif -Decoding meshes\f\furn_shrine_felms_01.nif -Decoding meshes\f\furn_shrine_seryn_01.nif -Decoding meshes\f\furn_shrine_delyn_01.nif -Decoding meshes\f\furn_shrine_meris_01.nif -Decoding meshes\f\furn_shrine_roris_01.nif -Decoding meshes\f\furn_shrine_olms_01.nif -Decoding meshes\f\furn_shrine_vivec_01.nif -Decoding meshes\f\furn_shrine_llothis_01.nif -Decoding meshes\f\furn_shrine_aralor_01.nif -Decoding meshes\f\furn_shrine_nerevar_01.nif -Decoding meshes\f\furn_shrine_veloth_01.nif -Decoding meshes\f\furn_shrine_tribunal_01.nif -Decoding meshes\f\act_banner_tel_aruhn.nif -Decoding meshes\f\act_banner_gnaar_mok.nif -Decoding meshes\f\act_banner_tel_mora.nif -Decoding meshes\f\act_banner_ald_velothi.nif -Decoding meshes\f\act_banner_tel_branora.nif -Decoding meshes\f\act_banner_sadrith_mora.nif -Decoding meshes\a\a_m_imperialchain_cuirass.nif -Decoding meshes\l\light_velothismall_01.nif -Decoding meshes\l\light_velothi_brazier.nif -Decoding meshes\a\a_orcish_pauldron_ua.nif -Decoding meshes\a\a_orcish_pauldron_fa.nif -Decoding meshes\a\a_orcish_greaves_gnd.nif -Decoding meshes\a\a_orcish_greaves_ul.nif -Decoding meshes\a\a_orcish_cuirass_gnd.nif -Decoding meshes\a\a_orcish_bracer_gnd.nif -Decoding meshes\a\a_orcish_cl_pauldron.nif -Decoding meshes\a\a_orcish_pauldron_gnd.nif -Decoding meshes\f\flora_trama_shrub_05.nif -Decoding meshes\f\flora_trama_shrub_01.nif -Decoding meshes\f\flora_trama_shrub_06.nif -Decoding meshes\f\flora_trama_shrub_02.nif -Decoding meshes\f\flora_trama_shrub_03.nif -Decoding meshes\f\flora_trama_shrub_04.nif -Decoding meshes\f\flora_treestump_wg_01.nif -Decoding meshes\f\flora_treestump_wg_02.nif -Decoding meshes\a\a_ebony_pauldron_gnd.nif -Decoding meshes\a\a_ebony_cuirass_gnd.nif -Decoding meshes\a\a_ebony_pauldron_fa.nif -Decoding meshes\a\a_ebony_pauldron_ua.nif -Decoding meshes\a\a_ebony_pauldron_cl.nif -Decoding meshes\a\a_ebony_cl_pauldron.nif -Decoding meshes\a\a_ebony_greaves_gnd.nif -Decoding meshes\w\w_art_katana_goldbrand.nif -Decoding meshes\m\app_s_calcinator_01.nif -Decoding meshes\m\app_g_calcinator_01.nif -Decoding meshes\m\app_m_calcinator_01.nif -Decoding meshes\m\app_j_calcinator_01.nif -Decoding meshes\b\b_n_dark elf_f_skins.nif -Decoding meshes\b\b_n_dark elf_m_skins.nif -Decoding meshes\b\b_n_dark elf_m_foot.nif -Decoding meshes\b\b_n_dark elf_f_wrist.nif -Decoding meshes\b\b_n_dark elf_m_neck.nif -Decoding meshes\b\b_n_dark elf_f_neck.nif -Decoding meshes\b\b_n_dark elf_m_knee.nif -Decoding meshes\b\b_n_dark elf_f_knee.nif -Decoding meshes\b\b_n_dark elf_f_groin.nif -Decoding meshes\b\b_n_dark elf_m_groin.nif -Decoding meshes\b\b_n_dark elf_m_wrist.nif -Decoding meshes\b\b_n_dark elf_m_ankle.nif -Decoding meshes\b\b_n_dark elf_f_ankle.nif -Decoding meshes\b\b_n_dark elf_f_foot.nif -Decoding meshes\b\b_n_dark elf_m_hair_23.nif -Decoding meshes\b\b_n_dark elf_m_hair_21.nif -Decoding meshes\b\b_n_dark elf_m_hair_25.nif -Decoding meshes\b\b_n_dark elf_f_hair_23.nif -Decoding meshes\b\b_n_dark elf_f_hair_21.nif -Decoding meshes\b\b_n_dark elf_f_head_02.nif -Decoding meshes\b\b_n_dark elf_f_head_06.nif -Decoding meshes\b\b_n_dark elf_f_head_04.nif -Decoding meshes\b\b_n_dark elf_f_head_08.nif -Decoding meshes\b\b_n_dark elf_m_head_02.nif -Decoding meshes\b\b_n_dark elf_m_head_06.nif -Decoding meshes\b\b_n_dark elf_m_head_04.nif -Decoding meshes\b\b_n_dark elf_m_head_08.nif -Decoding meshes\b\b_n_dark elf_m_head_11.nif -Decoding meshes\b\b_n_dark elf_m_head_13.nif -Decoding meshes\b\b_n_dark elf_m_head_15.nif -Decoding meshes\b\b_n_dark elf_m_head_17.nif -Decoding meshes\b\b_n_dark elf_f_hair_09.nif -Decoding meshes\b\b_n_dark elf_f_hair_05.nif -Decoding meshes\b\b_n_dark elf_f_hair_07.nif -Decoding meshes\b\b_n_dark elf_f_hair_01.nif -Decoding meshes\b\b_n_dark elf_f_hair_03.nif -Decoding meshes\b\b_n_dark elf_m_hair_09.nif -Decoding meshes\b\b_n_dark elf_m_hair_05.nif -Decoding meshes\b\b_n_dark elf_m_hair_07.nif -Decoding meshes\b\b_n_dark elf_m_hair_01.nif -Decoding meshes\b\b_n_dark elf_m_hair_03.nif -Decoding meshes\b\b_n_dark elf_f_hair_16.nif -Decoding meshes\b\b_n_dark elf_f_hair_14.nif -Decoding meshes\b\b_n_dark elf_f_hair_12.nif -Decoding meshes\b\b_n_dark elf_f_hair_10.nif -Decoding meshes\b\b_n_dark elf_f_hair_18.nif -Decoding meshes\b\b_n_dark elf_m_hair_16.nif -Decoding meshes\b\b_n_dark elf_m_hair_14.nif -Decoding meshes\b\b_n_dark elf_m_hair_12.nif -Decoding meshes\b\b_n_dark elf_m_hair_10.nif -Decoding meshes\b\b_n_dark elf_m_hair_18.nif -Decoding meshes\b\b_n_dark elf_m_forearm.nif -Decoding meshes\b\b_n_dark elf_m_hair_22.nif -Decoding meshes\b\b_n_dark elf_m_hair_20.nif -Decoding meshes\b\b_n_dark elf_m_hair_26.nif -Decoding meshes\b\b_n_dark elf_m_hair_24.nif -Decoding meshes\b\b_n_dark elf_f_hair_22.nif -Decoding meshes\b\b_n_dark elf_f_hair_20.nif -Decoding meshes\b\b_n_dark elf_f_hair_24.nif -Decoding meshes\b\b_n_dark elf_f_head_03.nif -Decoding meshes\b\b_n_dark elf_f_head_01.nif -Decoding meshes\b\b_n_dark elf_f_head_07.nif -Decoding meshes\b\b_n_dark elf_f_head_05.nif -Decoding meshes\b\b_n_dark elf_f_head_09.nif -Decoding meshes\b\b_n_dark elf_m_head_03.nif -Decoding meshes\b\b_n_dark elf_m_head_01.nif -Decoding meshes\b\b_n_dark elf_m_head_07.nif -Decoding meshes\b\b_n_dark elf_m_head_05.nif -Decoding meshes\b\b_n_dark elf_m_head_09.nif -Decoding meshes\b\b_n_dark elf_f_head_10.nif -Decoding meshes\b\b_n_dark elf_m_head_10.nif -Decoding meshes\b\b_n_dark elf_m_head_12.nif -Decoding meshes\b\b_n_dark elf_m_head_14.nif -Decoding meshes\b\b_n_dark elf_m_head_16.nif -Decoding meshes\b\b_n_dark elf_f_hair_08.nif -Decoding meshes\b\b_n_dark elf_f_hair_04.nif -Decoding meshes\b\b_n_dark elf_f_hair_06.nif -Decoding meshes\b\b_n_dark elf_f_hair_02.nif -Decoding meshes\b\b_n_dark elf_m_hair_08.nif -Decoding meshes\b\b_n_dark elf_m_hair_04.nif -Decoding meshes\b\b_n_dark elf_m_hair_06.nif -Decoding meshes\b\b_n_dark elf_m_hair_02.nif -Decoding meshes\b\b_n_dark elf_f_forearm.nif -Decoding meshes\b\b_n_dark elf_f_hair_17.nif -Decoding meshes\b\b_n_dark elf_f_hair_15.nif -Decoding meshes\b\b_n_dark elf_f_hair_13.nif -Decoding meshes\b\b_n_dark elf_f_hair_11.nif -Decoding meshes\b\b_n_dark elf_f_hair_19.nif -Decoding meshes\b\b_n_dark elf_m_hair_17.nif -Decoding meshes\b\b_n_dark elf_m_hair_15.nif -Decoding meshes\b\b_n_dark elf_m_hair_13.nif -Decoding meshes\b\b_n_dark elf_m_hair_11.nif -Decoding meshes\b\b_n_dark elf_m_hair_19.nif -Decoding meshes\b\b_n_dark elf_m_upper arm.nif -Decoding meshes\b\b_n_dark elf_f_upper leg.nif -Decoding meshes\b\b_n_dark elf_m_upper leg.nif -Decoding meshes\b\b_n_dark elf_f_hands.1st.nif -Decoding meshes\b\b_n_dark elf_m_hands.1st.nif -Decoding meshes\b\b_n_dark elf_f_upper arm.nif -Decoding meshes\a\a_steel_pauldron_gnd.nif -Decoding meshes\a\a_steel_cuirass_gnd.nif -Decoding meshes\a\a_steel_pauldron_fa.nif -Decoding meshes\a\a_steel_pauldron_ua.nif -Decoding meshes\a\a_steel_pauldron_cl.nif -Decoding meshes\a\a_steel_gauntlet_gnd.nif -Decoding meshes\a\a_steel_greaves_gnd.nif -Decoding meshes\a\a_ringmail_cuirass_gnd.nif -Decoding meshes\b\b_n_nord_m_hands.1st.nif -Decoding meshes\b\b_n_nord_f_upper leg.nif -Decoding meshes\b\b_n_nord_f_hands.1st.nif -Decoding meshes\b\b_n_nord_m_upper leg.nif -Decoding meshes\b\b_n_nord_m_upper arm.nif -Decoding meshes\b\b_n_nord_f_upper arm.nif -Decoding meshes\a\towershield_telvanni.nif -Decoding meshes\a\towershield_bonemold.nif -Decoding meshes\a\towershield_daedric.nif -Decoding meshes\a\towershield_redoranm.nif -Decoding meshes\a\towershield_trollbone.nif -Decoding meshes\a\towershield_dragonscale.nif -Decoding meshes\a\towershield_netch_leather.nif -Decoding meshes\l\light_torch_small_01.nif -Decoding meshes\m\misc_candle_blue_01.nif -Decoding meshes\m\misc_candle_ivory_01.nif -Decoding meshes\m\misc_candle_green_01.nif -Decoding meshes\m\misc_paper_plain_01.nif -Decoding meshes\w\w_shortsword_chitin.nif -Decoding meshes\w\w_shortsword_daedric.nif -Decoding meshes\w\w_shortsword_imperial.nif -Decoding meshes\a\a_boots_heavy_leather.nif -Decoding meshes\w\w_nordic_broadsword.nif -Decoding meshes\b\b_v_redguard_m_head_01.nif -Decoding meshes\b\b_v_redguard_f_head_01.nif -Decoding meshes\c\c_art_ring_vampiric.nif -Decoding meshes\c\c_art_ring_phynaster.nif -Decoding meshes\c\c_art_ring_denstagmer.nif -Decoding meshes\c\c_art_ring_surrounding.nif -Decoding meshes\m\furn_com_coatofarms_01.nif -Decoding meshes\m\probe_secretmaster_01.nif -Decoding meshes\b\b_v_breton_m_head_01.nif -Decoding meshes\b\b_v_breton_f_head_01.nif -Decoding meshes\w\w_broadsword_leafblade.nif -Decoding meshes\w\w_broadsword_imperial.nif -Decoding meshes\f\xfurn_banner_tavern_01.nif -Decoding meshes\f\xfurn_banner_temple_03.nif -Decoding meshes\f\xfurn_banner_temple_01.nif -Decoding meshes\f\xfurn_bannerd_goods_01.nif -Decoding meshes\f\xfurn_banner_hlaalu_01.nif -Decoding meshes\f\xfurn_banner_dagoth_01.nif -Decoding meshes\f\xfurn_banner_temple_02.nif -Decoding meshes\f\xfurn_bannerd_welcome_01.nif -Decoding meshes\f\xfurn_bannerd_wa_shop_01.nif -Decoding meshes\f\xfurn_bannerd_alchemy_01.nif -Decoding meshes\f\xfurn_bannerd_danger_01.nif -Decoding meshes\m\misc_bowl_redware_01.nif -Decoding meshes\m\misc_bowl_redware_03.nif -Decoding meshes\m\misc_bowl_redware_02.nif -Decoding meshes\m\misc_bowl_bugdesign_01.nif -Decoding meshes\m\misc_bowl_glass_peach_01.nif -Decoding meshes\m\misc_com_tankard_01.nif -Decoding meshes\m\misc_com_wood_cup_02.nif -Decoding meshes\m\misc_com_wood_cup_03.nif -Decoding meshes\m\misc_com_wood_knife.nif -Decoding meshes\m\misc_com_wood_cup_01.nif -Decoding meshes\m\misc_com_iron_ladle.nif -Decoding meshes\m\misc_com_wood_cup_04.nif -Decoding meshes\m\misc_com_wood_spoon_01.nif -Decoding meshes\m\misc_com_wood_bowl_03.nif -Decoding meshes\m\misc_com_wood_bowl_01.nif -Decoding meshes\m\misc_com_bucket_metal.nif -Decoding meshes\m\misc_com_wood_spoon_02.nif -Decoding meshes\m\misc_com_wood_bowl_02.nif -Decoding meshes\m\misc_com_wood_bowl_05.nif -Decoding meshes\m\misc_com_wood_bowl_04.nif -Decoding meshes\m\misc_com_metal_plate_04.nif -Decoding meshes\m\misc_com_silverware_fork.nif -Decoding meshes\m\misc_com_metal_goblet_02.nif -Decoding meshes\m\misc_com_metal_goblet_01.nif -Decoding meshes\m\misc_com_metal_plate_03.nif -Decoding meshes\m\misc_com_metal_plate_07.nif -Decoding meshes\m\misc_com_metal_plate_05.nif -Decoding meshes\f\furn_redoran_flag_in.nif -Decoding meshes\f\furn_redoran_flag_01.nif -Decoding meshes\f\furn_redoran_hearth_02.nif -Decoding meshes\f\furn_redoran_shelf_03.nif -Decoding meshes\f\furn_redoran_hearth_01.nif -Decoding meshes\f\furn_redoran_shelf2_01.nif -Decoding meshes\f\furn_redoran_shelf1_01.nif -Decoding meshes\m\misc_mortarpestle_01.nif -Decoding meshes\m\misc_mortarpestle_s_01.nif -Decoding meshes\m\misc_mortarpestle_g_01.nif -Decoding meshes\m\misc_mortarpestle_a_01.nif -Decoding meshes\m\misc_mortarpestle_m_01.nif -Decoding meshes\f\furn_velothi_altar_01.nif -Decoding meshes\m\misc_soulgem_lesser.nif -Decoding meshes\m\misc_soulgem_common.nif -Decoding meshes\m\misc_soulgem_greater.nif -Decoding meshes\m\misc_potion_fresh_01.nif -Decoding meshes\m\misc_potion_cheap_01.nif -Decoding meshes\m\misc_pot_mottled_01.nif -Decoding meshes\m\misc_pot_redware_02.nif -Decoding meshes\m\misc_pot_redware_03.nif -Decoding meshes\m\misc_pot_redware_01.nif -Decoding meshes\m\misc_pot_redware_04.nif -Decoding meshes\m\misc_potion_bargain_01.nif -Decoding meshes\m\misc_potion_quality_01.nif -Decoding meshes\m\misc_potion_exclusive_01.nif -Decoding meshes\m\misc_potion_standard_01.nif -Decoding meshes\m\misc_pot_glass_peach_01.nif -Decoding meshes\m\misc_pot_glass_peach_02.nif -Decoding meshes\f\furn_de_winerack_01.nif -Decoding meshes\f\furn_de_bookshelf_01.nif -Decoding meshes\f\furn_de_practice_mat.nif -Decoding meshes\f\furn_de_signpost_04.nif -Decoding meshes\f\furn_de_signpost_01.nif -Decoding meshes\f\furn_de_signpost_03.nif -Decoding meshes\f\furn_de_signpost_02.nif -Decoding meshes\f\furn_de_tapestry_10.nif -Decoding meshes\f\furn_de_tapestry_01.nif -Decoding meshes\f\furn_de_tapestry_11.nif -Decoding meshes\f\furn_de_tapestry_02.nif -Decoding meshes\f\furn_de_tapestry_12.nif -Decoding meshes\f\furn_de_tapestry_03.nif -Decoding meshes\f\furn_de_tapestry_13.nif -Decoding meshes\f\furn_de_tapestry_04.nif -Decoding meshes\f\furn_de_tapestry_05.nif -Decoding meshes\f\furn_de_tapestry_06.nif -Decoding meshes\f\furn_de_tapestry_07.nif -Decoding meshes\f\furn_de_tapestry_08.nif -Decoding meshes\f\furn_de_tapestry_09.nif -Decoding meshes\f\furn_de_ex_table_02.nif -Decoding meshes\f\furn_de_ex_table_03.nif -Decoding meshes\f\furn_de_bookshelf_02.nif -Decoding meshes\f\furn_de_firepit_f_01.nif -Decoding meshes\f\furn_de_ex_bench_01.nif -Decoding meshes\f\furn_de_ex_stool_02.nif -Decoding meshes\f\furn_de_tapestry_m_01.nif -Decoding meshes\f\furn_de_banner_book_01.nif -Decoding meshes\f\furn_de_banner_book_in.nif -Decoding meshes\f\furn_de_banner_pawn_01.nif -Decoding meshes\f\furn_de_shack_basket_01.nif -Decoding meshes\f\furn_de_shack_basket_02.nif -Decoding meshes\m\misc_com_pitcher_metal_01.nif -Decoding meshes\m\misc_com_silverware_spoon.nif -Decoding meshes\m\misc_com_silverware_knife.nif -Decoding meshes\f\furn_de_banner_telvani_in.nif -Decoding meshes\f\furn_de_banner_telvani_01.nif -Decoding meshes\f\xfurn_bannerd_clothing_01.nif -Decoding meshes\m\misc_bowl_orange_green_01.nif -Decoding meshes\m\misc_bowl_glass_yellow_01.nif -Decoding meshes\o\flora_red_lichen_01.nif -Decoding meshes\o\flora_red_lichen_02.nif -Decoding meshes\o\flora_red_lichen_03.nif -Decoding meshes\m\misc_glass_yellow_01.nif -Decoding meshes\m\misc_glass_green_01.nif -Decoding meshes\r\guar.nif -Decoding meshes\r\xazura.nif -Decoding meshes\r\shalk.nif -Decoding meshes\r\azura.nif -Decoding meshes\r\dreugh.nif -Decoding meshes\r\hunger.nif -Decoding meshes\r\xguar.nif -Decoding meshes\r\xshalk.nif -Decoding meshes\raindrop.nif -Decoding meshes\r\rust rat.nif -Decoding meshes\r\skeleton.nif -Decoding meshes\r\xdagothr.nif -Decoding meshes\r\xdreugh.nif -Decoding meshes\r\xbyagram.nif -Decoding meshes\r\xdremora.nif -Decoding meshes\r\xhunger.nif -Decoding meshes\r\daedroth.nif -Decoding meshes\r\dremora.nif -Decoding meshes\r\dagothr.nif -Decoding meshes\r\bonelord.nif -Decoding meshes\r\byagram.nif -Decoding meshes\r\ashghoul.nif -Decoding meshes\r\ashslave.nif -Decoding meshes\r\nixhound.nif -Decoding meshes\rainsplash.nif -Decoding meshes\r\bonewalker.nif -Decoding meshes\r\lordvivec.nif -Decoding meshes\r\xnixhound.nif -Decoding meshes\r\xminescrib.nif -Decoding meshes\r\xlordvivec.nif -Decoding meshes\r\cliffracer.nif -Decoding meshes\r\clannfear.nif -Decoding meshes\r\minescrib.nif -Decoding meshes\right_arrow.nif -Decoding meshes\r\netch_bull.nif -Decoding meshes\r\xdaedroth.nif -Decoding meshes\r\xduskyalit.nif -Decoding meshes\r\xclannfear.nif -Decoding meshes\r\xbabelfish.nif -Decoding meshes\r\xbonelord.nif -Decoding meshes\r\xashzombie.nif -Decoding meshes\r\xashghoul.nif -Decoding meshes\r\xashslave.nif -Decoding meshes\r\babelfish.nif -Decoding meshes\r\guar_white.nif -Decoding meshes\r\duskyalit.nif -Decoding meshes\r\xskeleton.nif -Decoding meshes\r\ashvampire.nif -Decoding meshes\r\ashzombie.nif -Decoding meshes\r\xrust rat.nif -Decoding meshes\b\b_v_argonian_m_head_01.nif -Decoding meshes\b\b_v_argonian_f_head_01.nif -Decoding meshes\b\b_v_high elf_f_head_01.nif -Decoding meshes\b\b_v_high elf_m_head_01.nif -Decoding meshes\o\flora_stoneflower_02.nif -Decoding meshes\o\flora_stoneflower_01.nif -Decoding meshes\a\a_art_shield_breaker.nif -Decoding meshes\slider_bar.nif -Decoding meshes\smoke_green.nif -Decoding meshes\sky_night_01.nif -Decoding meshes\w\w_art_blade_crescent.nif -Decoding meshes\torchfire.nif -Decoding meshes\w\shadowblunttwoclose.nif -Decoding meshes\c\artifact_bloodring_01.nif -Decoding meshes\c\artifact_belt_hfire_01.nif -Decoding meshes\c\artifact_ring_soul_01.nif -Decoding meshes\c\artifact_amulet_hring_01.nif -Decoding meshes\c\artifact_amulet_hheal_01.nif -Decoding meshes\c\artifact_amulet_hfire_01.nif -Decoding meshes\a\a_art_towershield_eleidon.nif -Decoding meshes\c\artifact_amulet_hthrum_01.nif -Decoding meshes\c\artifact_amulet_htrime_01.nif -Decoding meshes\a\a_glass_pauldron_gnd.nif -Decoding meshes\a\a_glass_cuirass_gnd.nif -Decoding meshes\a\a_glass_pauldron_fa.nif -Decoding meshes\a\a_glass_pauldron_ua.nif -Decoding meshes\a\a_glass_cl_pauldron.nif -Decoding meshes\a\a_glass_greaves_gnd.nif -Decoding meshes\a\a_glass_cl_pauldron_gnd.nif -Decoding meshes\b\b_v_khajiit_m_head_01.nif -Decoding meshes\b\b_v_khajiit_f_head_01.nif -Decoding meshes\f\furn_rail_straight_00.nif -Decoding meshes\w\w_art_claymore_umbra.nif -Decoding meshes\w\w_art_cleaverstfelms.nif -Decoding meshes\w\w_art_crosierstlloth.nif -Decoding meshes\w\w_art_claymore_chrys.nif -Decoding meshes\w\w_art_claymore_iceblade.nif -Decoding meshes\f\furn_lavacave_pool00.nif -Decoding meshes\f\furn_lavacave_spout00.nif -Decoding meshes\f\furn_banner_hlaalu_01.nif -Decoding meshes\f\furn_banner_tavern_in.nif -Decoding meshes\f\furn_banner_temple_01.nif -Decoding meshes\f\furn_bannerd_danger_in.nif -Decoding meshes\f\furn_banner_tavern_01.nif -Decoding meshes\f\furn_banner_temple_04.nif -Decoding meshes\f\furn_bannerd_goods_in.nif -Decoding meshes\f\furn_bannerd_danger_01.nif -Decoding meshes\f\furn_banner_temple_03.nif -Decoding meshes\f\furn_bannerd_goods_01.nif -Decoding meshes\f\furn_banner_dagoth_01.nif -Decoding meshes\f\furn_banner_temple_02.nif -Decoding meshes\f\furn_bannerd_alchemy_01.nif -Decoding meshes\f\furn_bannerd_wa_shop_01.nif -Decoding meshes\f\furn_bannerd_welcome_01.nif -Decoding meshes\f\furn_bannerd_clothing_01.nif -Decoding meshes\f\furn_bannerd_wa_shop_in.nif -Decoding meshes\f\furn_bannerd_alchemy_in.nif -Decoding meshes\f\furn_bannerd_clothing_in.nif -Decoding meshes\f\furn_banner_temple_01_in.nif -Decoding meshes\f\furn_banner_temple_02_in.nif -Decoding meshes\f\furn_banner_temple_03_in.nif -Decoding meshes\f\furn_dae_rubble_01c.nif -Decoding meshes\f\furn_dae_rubble_01b.nif -Decoding meshes\f\furn_dae_rubble_03b.nif -Decoding meshes\f\furn_dae_rubble_01a.nif -Decoding meshes\f\furn_dae_rubble_03a.nif -Decoding meshes\f\furn_dae_rubble_04a.nif -Decoding meshes\f\furn_dae_rubble_pointy.nif -Decoding meshes\upper_arrow.nif -Decoding meshes\a\a_studdedleather_c_gnd.nif -Decoding meshes\a\a_studdedleather_cuirass.nif -Decoding meshes\w\w_dwemer_shortsword.nif -Decoding meshes\m\misc_chest_small_02.nif -Decoding meshes\m\misc_chest_small_01.nif -Decoding meshes\m\text_octavo_open_08.nif -Decoding meshes\m\text_octavo_open_04.nif -Decoding meshes\m\text_octavo_open_06.nif -Decoding meshes\m\text_octavo_open_07.nif -Decoding meshes\m\text_octavo_open_01.nif -Decoding meshes\m\text_octavo_open_02.nif -Decoding meshes\m\text_octavo_open_03.nif -Decoding meshes\f\xex_ashl_e_banner_r.nif -Decoding meshes\f\xex_ashl_a_banner_r.nif -Decoding meshes\f\xex_ashl_z_banner_r.nif -Decoding meshes\f\xex_ashl_u_banner_r.nif -Decoding meshes\m\text_scroll_open_01.nif -Decoding meshes\m\text_scroll_open_02.nif -Decoding meshes\m\text_scroll_open_03.nif -Decoding meshes\a\a_silver_cuirass_duke.nif -Decoding meshes\a\a_daedric_greaves_g.nif -Decoding meshes\a\a_daedric_greaves_ul.nif -Decoding meshes\a\a_daedric_hands.1st.nif -Decoding meshes\a\a_daedric_fountain_h.nif -Decoding meshes\a\a_daedric_boots_gnd.nif -Decoding meshes\a\a_daedric_pauldron_gnd.nif -Decoding meshes\a\a_daedric_pauldron_ua.nif -Decoding meshes\a\a_daedric_greaves_gnd.nif -Decoding meshes\a\a_daedric_pauldron_cl.nif -Decoding meshes\a\a_daedric_cuirass_gnd.nif -Decoding meshes\a\a_daedric_terrifying_h.nif -Decoding meshes\a\a_daedric_gauntlet_gnd.nif -Decoding meshes\f\xfurn_de_banner_book_01.nif -Decoding meshes\f\xfurn_de_banner_pawn_01.nif -Decoding meshes\o\flora_willow_flower_02.nif -Decoding meshes\o\flora_willow_flower_01.nif -Decoding meshes\f\furn_screen_guar_01.nif -Decoding meshes\a\a_art_wraithguard.1st.nif -Decoding meshes\a\a_art_wraithguard_gnd.nif -Decoding meshes\w\w_mace.nif -Decoding meshes\w\w_bolt01.nif -Decoding meshes\w\w_club00.nif -Decoding meshes\w\w_spear.nif -Decoding meshes\w\w_tanto.nif -Decoding meshes\w\w_saber.nif -Decoding meshes\m\misc_vivec_ashmask_01.nif -Decoding meshes\m\misc_silverware_bowl.nif -Decoding meshes\m\misc_silverware_cup.nif -Decoding meshes\m\misc_silverware_cup_01.nif -Decoding meshes\m\misc_silverware_pitcher.nif -Decoding meshes\m\misc_silverware_plate_01.nif -Decoding meshes\m\misc_silverware_plate_03.nif -Decoding meshes\m\misc_silverware_plate_02.nif -Decoding meshes\w\w_staff00.nif -Decoding meshes\w\w_crossbow.nif -Decoding meshes\w\w_longbow.nif -Decoding meshes\w\w_claymore.nif -Decoding meshes\w\w_n_katana.nif -Decoding meshes\w\w_de_fork.nif -Decoding meshes\w\w_arrow01.nif -Decoding meshes\f\xfurn_de_banner_telvani_01.nif -Decoding meshes\d\ex_de_ship_trapdoor.nif -Decoding meshes\d\in_de_shipdoor_toplevel.nif -Decoding meshes\x\ex_t_tower_seedling.nif -Decoding meshes\x\ex_t_tower_strght_lrg.nif -Decoding meshes\x\ex_stronghold_fort00.nif -Decoding meshes\x\ex_strongruin_fort01.nif -Decoding meshes\x\ex_stronghold_door10.nif -Decoding meshes\x\ex_stronghold_wall00.nif -Decoding meshes\x\ex_stronghold_wall02.nif -Decoding meshes\x\ex_stronghold_fort02.nif -Decoding meshes\x\ex_strongruin_fort05.nif -Decoding meshes\x\ex_stronghold_dome00.nif -Decoding meshes\x\ex_strongruin_fort02.nif -Decoding meshes\x\ex_strongruin_fort00.nif -Decoding meshes\x\ex_stronghold_wall03.nif -Decoding meshes\x\ex_stronghold_fort03.nif -Decoding meshes\x\ex_stronghold_fort05.nif -Decoding meshes\x\ex_stronghold_fort01.nif -Decoding meshes\x\ex_stronghold_wall01.nif -Decoding meshes\x\ex_strongruin_dome00.nif -Decoding meshes\x\ex_strongruin_fort03.nif -Decoding meshes\x\ex_stronghold_pylon01.nif -Decoding meshes\x\ex_stronghold_enter00.nif -Decoding meshes\x\ex_stronghold_window00.nif -Decoding meshes\x\ex_stronghold_pylon02.nif -Decoding meshes\x\ex_strong_roofstack00.nif -Decoding meshes\x\ex_stronghold_pylon00.nif -Decoding meshes\x\ex_strongruin_enter00.nif -Decoding meshes\x\ex_t_root_bridge_01.nif -Decoding meshes\x\ex_t_root_spikes_01.nif -Decoding meshes\x\ex_t_root_spikes_02.nif -Decoding meshes\x\ex_t_rock_coastal_03.nif -Decoding meshes\x\ex_t_rock_coastal_01.nif -Decoding meshes\x\ex_t_rock_coastal_02.nif -Decoding meshes\x\ex_t_root_lendsplit.nif -Decoding meshes\x\ex_strongholdruin_wall00.nif -Decoding meshes\x\ex_strongholdruin_wall01.nif -Decoding meshes\x\ex_strongholdruin_wall02.nif -Decoding meshes\x\ex_strongholdruin_wall03.nif -Decoding meshes\x\ex_strongruin_smdwell00.nif -Decoding meshes\x\ex_stronghold_smdwell00.nif -Decoding meshes\x\ex_stronghold_sandpit00.nif -Decoding meshes\x\in_t_housepod_2flr_stair.nif -Decoding meshes\d\in_impsmall_door_01.nif -Decoding meshes\d\in_impsmall_d_cave_01.nif -Decoding meshes\d\in_impsmall_d_hidden_01.nif -Decoding meshes\d\in_impsmall_loaddoor_01.nif -Decoding meshes\d\in_impsmall_door_jail_01.nif -Decoding meshes\d\in_impsmall_door_jail_02.nif -Decoding meshes\x\ex_redoran_tower_01.nif -Decoding meshes\x\ex_redoran_window_01.nif -Decoding meshes\x\ex_redoran_window_02.nif -Decoding meshes\x\ex_redoran_tavern_01.nif -Decoding meshes\x\ex_redoran_steps_01.nif -Decoding meshes\x\ex_redoran_steps_02.nif -Decoding meshes\x\ex_redoran_awning_01.nif -Decoding meshes\x\ex_redoran_barracks_01.nif -Decoding meshes\x\ex_redoran_building_02.nif -Decoding meshes\x\ex_redoran_building_03.nif -Decoding meshes\x\ex_redoran_building_01.nif -Decoding meshes\x\ex_t_doorway_sphere_01.nif -Decoding meshes\x\ex_redoran_building_01a.nif -Decoding meshes\x\ex_velothi_window_01.nif -Decoding meshes\x\ex_velothi_temple_02.nif -Decoding meshes\x\ex_velothi_temple_01.nif -Decoding meshes\x\ex_velothi_tower_01.nif -Decoding meshes\x\ex_velothi_triwin_01.nif -Decoding meshes\x\ex_velothi_hilltent_01.nif -Decoding meshes\x\ex_velothi_entrance_03.nif -Decoding meshes\x\ex_velothi_entrance_01.nif -Decoding meshes\x\ex_velothi_entrance_02.nif -Decoding meshes\x\ex_velothi_tower_01_a.nif -Decoding meshes\x\ex_velothi_striderport_01.nif -Decoding meshes\x\ex_strongruin_fort05_half.nif -Decoding meshes\x\ex_redoran_striderport_01.nif -Decoding meshes\c\amulet_extravagant_1.nif -Decoding meshes\c\amulet_thongofzainab.nif -Decoding meshes\c\amulet_extravagant_2.nif -Decoding meshes\c\amulet_teeth_urshilaku.nif -Decoding meshes\x\ex_common_skywalk_01.nif -Decoding meshes\x\ex_common_window_01.nif -Decoding meshes\x\ex_common_window_02.nif -Decoding meshes\x\ex_common_window_03.nif -Decoding meshes\x\ex_common_plat_cent.nif -Decoding meshes\x\ex_common_plat_rail.nif -Decoding meshes\x\ex_common_plat_corn.nif -Decoding meshes\x\ex_common_tavern_01.nif -Decoding meshes\x\ex_common_lighthouse.nif -Decoding meshes\x\ex_common_balcony_01.nif -Decoding meshes\x\ex_common_dormer_round.nif -Decoding meshes\x\ex_common_house_addon.nif -Decoding meshes\x\ex_common_building_01.nif -Decoding meshes\x\ex_common_building_03.nif -Decoding meshes\x\ex_common_tower_thatch.nif -Decoding meshes\x\ex_common_chimney_tall.nif -Decoding meshes\x\ex_common_entrance_02.nif -Decoding meshes\x\ex_common_building_02.nif -Decoding meshes\x\ex_common_entrance_01.nif -Decoding meshes\x\ex_common_dormer_square.nif -Decoding meshes\x\ex_common_house_tall_01.nif -Decoding meshes\x\ex_common_awning_wood_01.nif -Decoding meshes\x\ex_common_house_tall_02.nif -Decoding meshes\x\ex_common_trellis_withvine.nif -Decoding meshes\x\ex_common_house_mixedroofs.nif -Decoding meshes\x\ex_t_playertower_sprout.nif -Decoding meshes\f\furn_t_fireplace_01.nif -Decoding meshes\f\furn_c_t_dibella_01.nif -Decoding meshes\f\furn_c_t_stendarr_01.nif -Decoding meshes\f\furn_c_t_akatosh_01.nif -Decoding meshes\f\furn_c_t_warrior_01.nif -Decoding meshes\f\furn_c_t_kynareth_01.nif -Decoding meshes\f\furn_c_t_julianos_01.nif -Decoding meshes\f\furn_c_t_zenithar_01.nif -Decoding meshes\f\furn_c_t_apprentice_01.nif -Decoding meshes\f\active_blight_medium.nif -Decoding meshes\f\active_triolith_01a.nif -Decoding meshes\f\active_sign_c_inn_02.nif -Decoding meshes\f\active_com_bar_door.nif -Decoding meshes\f\active_blight_large.nif -Decoding meshes\f\active_blight_small.nif -Decoding meshes\f\active_sign_c_goods_02.nif -Decoding meshes\f\active_sign_c_pwan_01.nif -Decoding meshes\f\active_sign_c_arms_01.nif -Decoding meshes\f\active_sign_c_goods_01.nif -Decoding meshes\f\active_sign_c_arms_02.nif -Decoding meshes\f\active_sign_c_alchemy_01.nif -Decoding meshes\f\active_sign_c_guildm_01.nif -Decoding meshes\f\active_sign_c_guildf_01.nif -Decoding meshes\f\active_sign_c_clothing_01.nif -Decoding meshes\c\c_ring_extravagant_1.nif -Decoding meshes\c\c_ring_extravagant_2.nif -Decoding meshes\x\ex_v_vivecstatue_01.nif -Decoding meshes\x\ex_v_vivecstatue_02.nif -Decoding meshes\x\ex_v_sign_stdeyln_01.nif -Decoding meshes\x\ex_v_sign_stolms_01.nif -Decoding meshes\x\ex_v_sign_hlaalu_01.nif -Decoding meshes\x\ex_v_sign_redoran_01.nif -Decoding meshes\x\ex_v_sign_telvanni_01.nif -Decoding meshes\x\ex_lighthouse_stone.nif -Decoding meshes\x\ex_c_chimney_tall_02.nif -Decoding meshes\x\ex_gg_gateswitch_01.nif -Decoding meshes\x\ex_gg_gatetriolith_01.nif -Decoding meshes\i\in_dwrv_tower_int00.nif -Decoding meshes\w\w_6th_hammer.nif -Decoding meshes\x\ex_cavern_padlock00.nif -Decoding meshes\x\ex_cave_coastrock00.nif -Decoding meshes\x\ex_cave_entrance_10.nif -Decoding meshes\x\ex_vivec_w_slope_01.nif -Decoding meshes\x\ex_vivec_ent_telt_01.nif -Decoding meshes\x\ex_vivec_buttress_01.nif -Decoding meshes\x\ex_vivec_p_water_01.nif -Decoding meshes\x\ex_vivec_b_gap_t_01.nif -Decoding meshes\x\ex_vivec_b_gap_b_01.nif -Decoding meshes\x\ex_vivec_b_gap_b_02.nif -Decoding meshes\x\ex_vivec_b_wb_gap_01.nif -Decoding meshes\x\ex_vivec_wspout_d_02.nif -Decoding meshes\x\ex_vivec_bridgew_01.nif -Decoding meshes\x\ex_vivec_waterfall_03.nif -Decoding meshes\x\ex_vivec_waterfall_05.nif -Decoding meshes\x\ex_vivec_prisonmoon_01.nif -Decoding meshes\x\ex_vivec_waterspout_02.nif -Decoding meshes\x\ex_vivec_waterfall_01.nif -Decoding meshes\x\ex_vivec_bridgewgap_01.nif -Decoding meshes\x\ex_vivec_waterspout_05.nif -Decoding meshes\x\ex_vivec_waterspout_01.nif -Decoding meshes\x\ex_vivec_waterspout_03.nif -Decoding meshes\x\ex_waterfall_mist_01.nif -Decoding meshes\x\ex_waterfall_mist_s_01.nif -Decoding meshes\x\ex_ropebridge_512_01.nif -Decoding meshes\x\ex_ropebridge_1024_01.nif -Decoding meshes\x\ex_ropebridge_2048_01.nif -Decoding meshes\x\ex_ropebridge_stake_01.nif -Decoding meshes\i\in_t_stairs_strt_256.nif -Decoding meshes\i\in_c_stone_room_side.nif -Decoding meshes\i\in_c_stone_room_corner.nif -Decoding meshes\i\in_c_stone_room_center.nif -Decoding meshes\i\in_c_stone_room_entry.nif -Decoding meshes\i\in_c_stone_stair_short.nif -Decoding meshes\i\in_c_stone_hall_small.nif -Decoding meshes\i\in_t_stairs_strt_wiz_256.nif -Decoding meshes\i\in_c_stair_rich_tall_01.nif -Decoding meshes\i\in_c_stair_rich_pend_01.nif -Decoding meshes\i\in_c_stair_plain_tall_01.nif -Decoding meshes\i\in_c_stair_plain_tall_02.nif -Decoding meshes\i\in_c_stair_rich_tall_02.nif -Decoding meshes\i\in_c_stair_rich_pend_02.nif -Decoding meshes\i\in_c_stone_room_c_con_01.nif -Decoding meshes\m\repair_journeyman_01.nif -Decoding meshes\m\repair_secretmaster_01.nif -Decoding meshes\m\repair_grandmaster_01.nif -Decoding meshes\x\ex_t_menhir_crystal.nif -Decoding meshes\i\in_c_stair_thatch_pend_01.nif -Decoding meshes\i\in_c_stair_thatch_pend_02.nif -Decoding meshes\i\in_c_stair_thatch_tall_01.nif -Decoding meshes\i\in_c_stair_thatch_tall_02.nif -Decoding meshes\i\in_c_stairs_rich_ptall_02.nif -Decoding meshes\i\in_c_stairs_rich_ptall_01.nif -Decoding meshes\x\ex_de_docks_centerb.nif -Decoding meshes\x\ex_de_docks_centers.nif -Decoding meshes\x\ex_de_docks_centersb.nif -Decoding meshes\x\ex_de_docks_steps_01.nif -Decoding meshes\x\ex_de_docks_pilings.nif -Decoding meshes\x\ex_de_docks_pilingb.nif -Decoding meshes\x\ex_de_docks_cornerb_01.nif -Decoding meshes\x\ex_de_docks_corners_03.nif -Decoding meshes\x\ex_de_docks_corners_01.nif -Decoding meshes\x\ex_de_docks_corner_02.nif -Decoding meshes\x\ex_de_docks_cornerb_02.nif -Decoding meshes\x\ex_de_docks_corners_02.nif -Decoding meshes\x\ex_de_docks_corner_01.nif -Decoding meshes\x\ex_de_docks_piling_01.nif -Decoding meshes\x\ex_de_docks_cornersb_02.nif -Decoding meshes\x\ex_de_docks_cornersb_01.nif -Decoding meshes\x\ex_de_docks_cornersb_03.nif -Decoding meshes\x\ex_nord_doorrocks_01.nif -Decoding meshes\x\ex_nord_houseshed_01.nif -Decoding meshes\x\ex_daed_wall_512_01.nif -Decoding meshes\x\ex_daed_pillar_claw_01.nif -Decoding meshes\x\ex_bc_cave_entrance.nif -Decoding meshes\x\ex_ac_cave_entrance_01.nif -Decoding meshes\x\ex_bc_cave_entrance_01.nif -Decoding meshes\x\ex_ma_cave_entrance.nif -Decoding meshes\x\ex_ma_cave_entrance_01.nif -Decoding meshes\x\ex_wg_cave_entrance_01.nif -Decoding meshes\x\ex_de_cave_entrance_01.nif -Decoding meshes\x\ex_ai_cave_entrance_01.nif -Decoding meshes\x\ex_rm_cave_entrance_01.nif -Decoding meshes\x\ex_gl_cave_entrance_01.nif -Decoding meshes\x\ex_al_cave_entrance_01.nif -Decoding meshes\x\ex_ma_cave_entrance_lava.nif -Decoding meshes\i\in_ar_shellbottom_01.nif -Decoding meshes\i\in_de_shack_trapdoor.nif -Decoding meshes\i\in_de_shipwreckll_lg.nif -Decoding meshes\i\in_de_shipwreck_top.nif -Decoding meshes\i\in_de_shipwreckul_lg.nif -Decoding meshes\i\in_de_ship_upperlevel.nif -Decoding meshes\i\in_de_ship_lowerlevel.nif -Decoding meshes\i\in_de_shack_trapdoor_01.nif -Decoding meshes\i\in_t_ls_hall_connect.nif -Decoding meshes\i\in_t_ls_hall_connect_01.nif -Decoding meshes\n\potion_local_brew_01.nif -Decoding meshes\n\potion_t_bug_musk_01.nif -Decoding meshes\n\potion_local_liquor_01.nif -Decoding meshes\n\potion_cyro_brandy_01.nif -Decoding meshes\n\potion_cyro_whiskey_01.nif -Decoding meshes\n\potion_comberry_wine_01.nif -Decoding meshes\i\in_impsmall_hall_02.nif -Decoding meshes\i\in_impsmall_hall_03.nif -Decoding meshes\i\in_impsmall_hall_01.nif -Decoding meshes\i\in_impsmall_wall_01.nif -Decoding meshes\i\in_impsmall_3way_01.nif -Decoding meshes\i\in_impsmall_4way_01.nif -Decoding meshes\i\in_impsmall_door_01.nif -Decoding meshes\i\in_impsmall_r_3way_01.nif -Decoding meshes\i\in_impsmall_r_entr_04.nif -Decoding meshes\i\in_impsmall_r_entr_01.nif -Decoding meshes\i\in_impsmall_r_entr_02.nif -Decoding meshes\i\in_impsmall_corner_01.nif -Decoding meshes\i\in_impsmall_shutter_01.nif -Decoding meshes\i\in_impsmall_dj_cave_01.nif -Decoding meshes\i\in_impsmall_endcap_01.nif -Decoding meshes\i\in_impsmall_spiral_01.nif -Decoding meshes\i\in_impsmall_doorjam_01.nif -Decoding meshes\i\in_impsmall_stairs_01.nif -Decoding meshes\i\in_impsmall_r_entr_03.nif -Decoding meshes\i\in_impsmall_r_corner_02.nif -Decoding meshes\i\in_impsmall_trapdoor_01a.nif -Decoding meshes\i\in_impsmall_r_pillar_01.nif -Decoding meshes\i\in_impsmall_dj_hidden_01.nif -Decoding meshes\i\in_impsmall_r_corner_01.nif -Decoding meshes\i\in_impsmall_r_corner_03.nif -Decoding meshes\i\in_impsmall_trapdoor_01.nif -Decoding meshes\i\in_impsmall_r_center_01.nif -Decoding meshes\n\potion_comberry_brandy_01.nif -Decoding meshes\i\in_impsmall_spiral_end_01.nif -Decoding meshes\i\in_impsmall_spiral_bot_01.nif -Decoding meshes\x\ex_lavacave_spout10.nif -Decoding meshes\x\ex_v_palace_steps_01.nif -Decoding meshes\x\ex_v_ban_redoran_01.nif -Decoding meshes\x\ex_v_ban_telvanni_01.nif -Decoding meshes\x\ex_v_ban_stdeyln_01.nif -Decoding meshes\x\ex_v_ban_serving_01.nif -Decoding meshes\x\ex_v_ban_comfort_01.nif -Decoding meshes\x\ex_v_ban_tribunal_01.nif -Decoding meshes\x\ex_hlaalu_bridge_06.nif -Decoding meshes\x\ex_hlaalu_bridge_07.nif -Decoding meshes\x\ex_hlaalu_bridge_04.nif -Decoding meshes\x\ex_hlaalu_bridge_05.nif -Decoding meshes\x\ex_hlaalu_bridge_02.nif -Decoding meshes\x\ex_hlaalu_bridge_03.nif -Decoding meshes\x\ex_hlaalu_bridge_10.nif -Decoding meshes\x\ex_hlaalu_bridge_01.nif -Decoding meshes\x\ex_hlaalu_wall_up_01.nif -Decoding meshes\x\ex_hlaalu_dsteps_01.nif -Decoding meshes\x\ex_hlaalu_dsteps_02.nif -Decoding meshes\x\ex_hlaalu_dsteps_03.nif -Decoding meshes\x\ex_hlaalu_wall_up_02.nif -Decoding meshes\x\ex_hlaalu_balcony_02.nif -Decoding meshes\x\ex_hlaalu_balcony_01.nif -Decoding meshes\x\ex_hlaalu_buttress_04.nif -Decoding meshes\x\ex_hlaalu_wall_end_01.nif -Decoding meshes\x\ex_hlaalu_buttress_05.nif -Decoding meshes\x\ex_hlaalu_wall_gate_03.nif -Decoding meshes\x\ex_hlaalu_wall_gate_01.nif -Decoding meshes\x\ex_hlaalu_buttress_03.nif -Decoding meshes\x\ex_hlaalu_wall_gate_04.nif -Decoding meshes\x\ex_hlaalu_wall_gate_02.nif -Decoding meshes\x\ex_hlaalu_buttress_01.nif -Decoding meshes\x\ex_hlaalu_wall_curve_01.nif -Decoding meshes\x\ex_hlaalu_striderport_01.nif -Decoding meshes\x\ex_holamayan_cover_01.nif -Decoding meshes\i\in_t_council_beams_02.nif -Decoding meshes\i\in_c_connect_stair_short.nif -Decoding meshes\i\in_redoran_ladder_01.nif -Decoding meshes\i\in_redoran_ashpit_02.nif -Decoding meshes\i\in_redoran_s_3way_01.nif -Decoding meshes\i\in_redoran_s_4way_01.nif -Decoding meshes\i\in_redoran_tower_01.nif -Decoding meshes\i\in_redoran_l_join_01.nif -Decoding meshes\i\in_redoran_s_hall_01.nif -Decoding meshes\i\in_redoran_window_01.nif -Decoding meshes\i\in_redoran_tavern_01.nif -Decoding meshes\i\in_redoran_l_hall_01.nif -Decoding meshes\i\in_redoran_steps_02.nif -Decoding meshes\i\in_redoran_steps_03.nif -Decoding meshes\i\in_redoran_l_cap_01.nif -Decoding meshes\i\in_redoran_l_3way_01.nif -Decoding meshes\i\in_redoran_l_4way_01.nif -Decoding meshes\i\in_redoran_s_hall_02.nif -Decoding meshes\i\in_redoran_s_cap_01.nif -Decoding meshes\i\in_redoran_ashpit_01.nif -Decoding meshes\i\in_redoran_barracks_01.nif -Decoding meshes\i\in_redoran_window2_01.nif -Decoding meshes\i\in_redoran_hut_jamb_01.nif -Decoding meshes\i\in_redoran_s_corner_01.nif -Decoding meshes\i\in_redoran_l_corner_01.nif -Decoding meshes\i\in_redoran_woodrail_01.nif -Decoding meshes\i\in_t_doorjamb_hall_small.nif -Decoding meshes\i\in_redoran_staircase_03.nif -Decoding meshes\i\in_redoran_hut_bfloor_02.nif -Decoding meshes\i\in_redoran_hut_bfloor_01.nif -Decoding meshes\i\in_redoran_hut_tfloor_01.nif -Decoding meshes\i\in_redoran_l_doorjamb_01.nif -Decoding meshes\i\in_redoran_staircase_02.nif -Decoding meshes\i\in_redoran_staircase_04.nif -Decoding meshes\i\in_dagoth_scaffold00.nif -Decoding meshes\i\in_t_housepod_stairs.nif -Decoding meshes\i\in_t_housepod_01_hall.nif -Decoding meshes\i\in_t_housepod_pole_01.nif -Decoding meshes\i\in_t_housepod_pole_02.nif -Decoding meshes\i\in_t_housepod_pole_04.nif -Decoding meshes\i\in_t_housepod_pole_03.nif -Decoding meshes\i\in_t_housepod_2ndfloor.nif -Decoding meshes\i\in_t_housepod_2flr_stair.nif -Decoding meshes\i\in_t_housepod_01_hall_02.nif -Decoding meshes\i\in_t_housepod_djamb_exit.nif -Decoding meshes\i\in_velothismall_ws_01.nif -Decoding meshes\i\in_velothilarge_con_01.nif -Decoding meshes\i\in_velothismall_cap_02.nif -Decoding meshes\i\in_velothilarge_cap_01.nif -Decoding meshes\i\in_velothismall_pit_01.nif -Decoding meshes\i\in_velothismall_mid_01.nif -Decoding meshes\i\in_velothi_platform_01.nif -Decoding meshes\i\in_velothismall_dj_01.nif -Decoding meshes\i\in_velothismall_cap_01.nif -Decoding meshes\i\in_velothismall_pit_02.nif -Decoding meshes\i\in_velothismall_pitd_02.nif -Decoding meshes\i\in_velothismall_pitd_04.nif -Decoding meshes\i\in_velothismall_pitd_06.nif -Decoding meshes\i\in_velothi_s_pitstep_01.nif -Decoding meshes\i\in_velothi_s_stairsl_01.nif -Decoding meshes\i\in_velothismall_hall_04.nif -Decoding meshes\i\in_velothismall_rail_01.nif -Decoding meshes\i\in_velothismall_ramp_01.nif -Decoding meshes\i\in_velothismall_ramp_03.nif -Decoding meshes\i\in_velothismall_room_11.nif -Decoding meshes\i\in_velothismall_r8_ramp.nif -Decoding meshes\i\in_velothilarge_ramp_01.nif -Decoding meshes\i\in_velothismall_room_08.nif -Decoding meshes\i\in_velothismall_room_02.nif -Decoding meshes\i\in_velothismall_room_06.nif -Decoding meshes\i\in_velothismall_room_04.nif -Decoding meshes\i\in_velothismall_4way_01.nif -Decoding meshes\i\in_velothismall_3way_01.nif -Decoding meshes\i\in_velothi_s_ceiling_01.nif -Decoding meshes\i\in_velothi_s_ramplong_01.nif -Decoding meshes\i\in_velothi_s_ramplong_02.nif -Decoding meshes\i\in_velothismall_wall_03.nif -Decoding meshes\i\in_velothismall_wall_01.nif -Decoding meshes\i\in_velothi_s_doorjam_01.nif -Decoding meshes\i\in_velothismall_pitd_01.nif -Decoding meshes\i\in_velothismall_pitd_03.nif -Decoding meshes\i\in_velothismall_pitd_05.nif -Decoding meshes\i\in_velothi_s_pitstep_02.nif -Decoding meshes\i\in_velothismall_hall_01.nif -Decoding meshes\i\in_velothismall_hall_03.nif -Decoding meshes\i\in_velothismall_ramp_02.nif -Decoding meshes\i\in_velothilarge_4way_01.nif -Decoding meshes\i\in_velothilarge_3way_01.nif -Decoding meshes\i\in_velothismall_room_10.nif -Decoding meshes\i\in_velothismall_room_12.nif -Decoding meshes\i\in_velothismall_door_01.nif -Decoding meshes\i\in_velothilarge_hall_01.nif -Decoding meshes\i\in_velothilarge_ramp_02.nif -Decoding meshes\i\in_velothismall_room_09.nif -Decoding meshes\i\in_velothismall_room_03.nif -Decoding meshes\i\in_velothismall_room_01.nif -Decoding meshes\i\in_velothismall_room_07.nif -Decoding meshes\i\in_velothismall_room_05.nif -Decoding meshes\i\in_velothismall_3way_02.nif -Decoding meshes\i\in_velothismall_dome_01.nif -Decoding meshes\i\in_velothismall_curve_02.nif -Decoding meshes\i\in_velothismall_curve_01.nif -Decoding meshes\i\in_velothismall_wall_02.nif -Decoding meshes\i\in_velothismall_r8_dome.nif -Decoding meshes\i\in_velothi_s_halfhall_01.nif -Decoding meshes\i\in_stronghold_hall03.nif -Decoding meshes\i\in_stronghold_hall00.nif -Decoding meshes\i\in_stronghold_wall00.nif -Decoding meshes\i\in_stronghold_wall10.nif -Decoding meshes\i\in_stronghold_dome00.nif -Decoding meshes\i\in_stronghold_arch00.nif -Decoding meshes\i\in_stronghold_hall04.nif -Decoding meshes\i\in_strong_balcony10.nif -Decoding meshes\i\in_strong_balcony00.nif -Decoding meshes\i\in_strong_archway00.nif -Decoding meshes\i\in_strong_archway01.nif -Decoding meshes\i\in_strong_shutter00.nif -Decoding meshes\i\in_strongruin_hall02.nif -Decoding meshes\i\in_strong_doorjam00.nif -Decoding meshes\i\in_strong_hallpill00.nif -Decoding meshes\i\in_strongruin_hall01.nif -Decoding meshes\i\in_stronghold_hall02.nif -Decoding meshes\i\in_strong_dualpill00.nif -Decoding meshes\i\in_stronghold_hall01.nif -Decoding meshes\i\in_strongruin_hall00.nif -Decoding meshes\i\in_strongruin_wall10.nif -Decoding meshes\i\in_strongruin_wall00.nif -Decoding meshes\i\in_strongruin2_hall02.nif -Decoding meshes\i\in_stronghold_corr2_05.nif -Decoding meshes\i\in_stronghold_corr2_07.nif -Decoding meshes\i\in_stronghold_corr2_01.nif -Decoding meshes\i\in_stronghold_corr2_03.nif -Decoding meshes\i\in_stronghold_corr3_01.nif -Decoding meshes\i\in_stronghold_stairs00.nif -Decoding meshes\i\in_stronghold_hcorr00.nif -Decoding meshes\i\in_stronghold_hcorr02.nif -Decoding meshes\i\in_stronghold_lpill00.nif -Decoding meshes\i\in_strongruin_corr2_07.nif -Decoding meshes\i\in_strongruin_corr2_05.nif -Decoding meshes\i\in_strongruin_corr2_03.nif -Decoding meshes\i\in_strongruin_corr2_01.nif -Decoding meshes\i\in_strongruin_corr3_01.nif -Decoding meshes\i\in_strongruin_hcorr00.nif -Decoding meshes\i\in_stronghold_corr4_00.nif -Decoding meshes\i\in_stronghold_corr2_04.nif -Decoding meshes\i\in_stronghold_corr2_06.nif -Decoding meshes\i\in_stronghold_corr2_00.nif -Decoding meshes\i\in_stronghold_corr2_02.nif -Decoding meshes\i\in_strongruin2_ramp10.nif -Decoding meshes\i\in_stronghold_corr3_00.nif -Decoding meshes\i\in_strongruin_hcorr01.nif -Decoding meshes\i\in_strongruin_hcorr02.nif -Decoding meshes\i\in_strongruin2_hall01.nif -Decoding meshes\i\in_strongruin2_hall04.nif -Decoding meshes\i\in_strongruin2_hall03.nif -Decoding meshes\i\in_stronghold_hcorr01.nif -Decoding meshes\i\in_strongruin2_hall00.nif -Decoding meshes\i\in_strong_vaultdoor00.nif -Decoding meshes\i\in_strongruin_corr2_06.nif -Decoding meshes\i\in_strongruin_corr2_04.nif -Decoding meshes\i\in_strongruin_corr2_02.nif -Decoding meshes\i\in_strongruin_corr2_00.nif -Decoding meshes\i\in_strongruin_corr3_00.nif -Decoding meshes\i\in_strongruin_stairs00.nif -Decoding meshes\i\in_strongruin2_corr2_04.nif -Decoding meshes\i\in_strongruin2_corr2_02.nif -Decoding meshes\i\in_strongruin2_corr2_00.nif -Decoding meshes\i\in_strongruin2_corr3_02.nif -Decoding meshes\i\in_strongruin2_corr3_00.nif -Decoding meshes\i\in_strongruin2_corr1_00.nif -Decoding meshes\i\in_strongruin2_corr4_00.nif -Decoding meshes\i\in_stronghold_divider10.nif -Decoding meshes\i\in_strongruin_divider10.nif -Decoding meshes\i\in_strongruin2_shutter00.nif -Decoding meshes\i\in_strongruin_roofext00.nif -Decoding meshes\i\in_strongruin_hallpill00.nif -Decoding meshes\i\in_strongruin_collapse10.nif -Decoding meshes\i\in_strongruin2_corr2_03.nif -Decoding meshes\i\in_strongruin2_corr2_01.nif -Decoding meshes\i\in_strongruin2_corr3_01.nif -Decoding meshes\i\in_stronghold_divider00.nif -Decoding meshes\i\in_strong_portal_chamber.nif -Decoding meshes\i\in_stronghold_roofext00.nif -Decoding meshes\i\in_strongruin2_balcony00.nif -Decoding meshes\i\in_strongruin_divider00.nif -Decoding meshes\i\in_strongruin2_balcony10.nif -Decoding meshes\i\in_strongruin2_dualpill00.nif -Decoding meshes\i\in_strongruin2_hallpill00.nif -Decoding meshes\i\in_strongruin2_collapse00.nif -Decoding meshes\i\in_velothismall_pittw_b_01.nif -Decoding meshes\i\in_velothismall_pittd_b_01.nif -Decoding meshes\i\in_velothismall_pitct_b_01.nif -Decoding meshes\i\in_velothismall_pitcb_b_01.nif -Decoding meshes\i\in_velothismall_pitbd_b_01.nif -Decoding meshes\i\in_velothilarge_corner_01.nif -Decoding meshes\i\in_velothismall_pitmw_b_01.nif -Decoding meshes\i\in_velothismall_pittop_02.nif -Decoding meshes\i\in_velothismall_pittop_01.nif -Decoding meshes\i\in_velothismall_fresco_01.nif -Decoding meshes\i\in_velothismall_fresco_02.nif -Decoding meshes\i\in_velothismall_pitc_b_01.nif -Decoding meshes\i\in_velothismall_pitbot_02.nif -Decoding meshes\i\in_velothismall_pitbot_01.nif -Decoding meshes\i\in_velothismall_pitb_b_01.nif -Decoding meshes\i\in_velothismall_column_01.nif -Decoding meshes\i\in_velothismall_column_03.nif -Decoding meshes\i\in_velothismall_column_02.nif -Decoding meshes\i\in_velothilarge_stairs_01.nif -Decoding meshes\i\in_velothismall_pitd_b_02.nif -Decoding meshes\i\in_velothismall_pitd_b_03.nif -Decoding meshes\i\in_velothismall_pitd_b_01.nif -Decoding meshes\i\in_velothismall_corner_01.nif -Decoding meshes\i\in_velothismall_stairs_01.nif -Decoding meshes\i\in_velothismall_pitmid_02.nif -Decoding meshes\i\in_velothismall_pitmid_01.nif -Decoding meshes\i\in_t_monor01_cap_topright.nif -Decoding meshes\d\ex_v_palace_grate_02.nif -Decoding meshes\d\ex_v_palace_grate_01.nif -Decoding meshes\i\in_ashl_tent_banner_08.nif -Decoding meshes\i\in_ashl_tent_banner_04.nif -Decoding meshes\i\in_ashl_tent_banner_06.nif -Decoding meshes\i\in_ashl_tent_banner_02.nif -Decoding meshes\i\in_ashl_tent_banner_16.nif -Decoding meshes\i\in_ashl_tent_banner_14.nif -Decoding meshes\i\in_ashl_tent_banner_12.nif -Decoding meshes\i\in_ashl_tent_banner_10.nif -Decoding meshes\i\in_ashl_tent_banner_09.nif -Decoding meshes\i\in_ashl_tent_banner_05.nif -Decoding meshes\i\in_ashl_tent_banner_07.nif -Decoding meshes\i\in_ashl_tent_banner_01.nif -Decoding meshes\i\in_ashl_tent_banner_03.nif -Decoding meshes\i\in_ashl_tent_banner_15.nif -Decoding meshes\i\in_ashl_tent_banner_13.nif -Decoding meshes\i\in_ashl_tent_banner_11.nif -Decoding meshes\i\in_c_plain_room_side.nif -Decoding meshes\i\in_c_plain_room_center.nif -Decoding meshes\i\in_c_plain_room_corner.nif -Decoding meshes\i\in_c_plain_room_entry.nif -Decoding meshes\i\in_c_plain_hall_small.nif -Decoding meshes\i\in_c_plain_stair_short.nif -Decoding meshes\i\in_c_plain_r_cwin_bay_02.nif -Decoding meshes\i\in_c_plain_r_cwin_bay_01.nif -Decoding meshes\i\in_c_plain_r_swin_bay_01.nif -Decoding meshes\i\in_c_plain_room_cwin_02.nif -Decoding meshes\i\in_c_plain_r_cwin_rec_02.nif -Decoding meshes\i\in_c_plain_r_cwin_rec_03.nif -Decoding meshes\i\in_c_plain_r_cwin_rec_01.nif -Decoding meshes\i\in_c_plain_r_cwin_rec_04.nif -Decoding meshes\i\in_c_plain_r_swin_rec_01.nif -Decoding meshes\i\in_c_plain_r_cwin_tri_01.nif -Decoding meshes\i\in_c_plain_r_cwin_tri_02.nif -Decoding meshes\i\in_c_plain_r_swin_tri_01.nif -Decoding meshes\i\in_c_plain_room_swin_01.nif -Decoding meshes\i\in_c_plain_room_cwin_01.nif -Decoding meshes\d\hlaalu_loaddoor_ 02.nif -Decoding meshes\d\hlaalu_loaddoor_ 01.nif -Decoding meshes\i\in_common_lighthouse.nif -Decoding meshes\i\in_common_room_corner.nif -Decoding meshes\i\in_common_tower_thatch.nif -Decoding meshes\i\in_common_tower_thatch3.nif -Decoding meshes\i\in_common_tower_thatch2.nif -Decoding meshes\a\a_boot_hvy_leather_gnd.nif -Decoding meshes\i\in_c_djamb_rp_arched.nif -Decoding meshes\i\in_c_djamb_rich_arched.nif -Decoding meshes\i\in_c_djamb_plain_arched.nif -Decoding meshes\i\in_c_djamb_stone_square.nif -Decoding meshes\i\in_c_djamb_plain_square.nif -Decoding meshes\i\in_c_djamb_stone_arched.nif -Decoding meshes\menu_scroll_button_up.nif -Decoding meshes\menu_scroll_button_down.nif -Decoding meshes\menu_scroll_button_right.nif -Decoding meshes\menu_scroll_button_left.nif -Decoding meshes\c\c_belt_extravagant_1.nif -Decoding meshes\c\c_belt_extravagant_2.nif -Decoding meshes\i\in_c_thatch_room_pside.nif -Decoding meshes\i\in_c_thatch_room_pcorner.nif -Decoding meshes\i\in_c_thatch_room_pentry.nif -Decoding meshes\i\in_c_thatch_room_pcenter.nif -Decoding meshes\menu_small_energy_bar.nif -Decoding meshes\i\in_c_thatch_room_pcorner2.nif -Decoding meshes\i\in_c_thatch_room_pendside.nif -Decoding meshes\i\in_c_thatch_room_pendside2.nif -Decoding meshes\i\in_c_pillar_wood_tall.nif -Decoding meshes\i\in_c_rich_room_pside.nif -Decoding meshes\i\in_c_rich_room_side.nif -Decoding meshes\i\in_c_rich_room_entry.nif -Decoding meshes\i\in_c_rich_room_center.nif -Decoding meshes\i\in_c_rich_room_pcenter.nif -Decoding meshes\i\in_c_rich_room_pcorner.nif -Decoding meshes\i\in_c_rich_room_pentry.nif -Decoding meshes\i\in_c_rich_room_corner.nif -Decoding meshes\i\in_c_rich_r_swin_bay_01.nif -Decoding meshes\i\in_c_rich_r_cwin_bay_01.nif -Decoding meshes\i\in_c_rich_rp_cwin_bay_04.nif -Decoding meshes\i\in_c_rich_rp_cwin_bay_03.nif -Decoding meshes\i\in_c_rich_rp_cwin_bay_02.nif -Decoding meshes\i\in_c_rich_rp_cwin_bay_01.nif -Decoding meshes\i\in_c_rich_r_cwin_tri_02.nif -Decoding meshes\i\in_c_rich_r_cwin_rec_01.nif -Decoding meshes\i\in_c_rich_r_cwin_rec_03.nif -Decoding meshes\i\in_c_rich_r_swin_rec_01.nif -Decoding meshes\i\in_c_rich_room_pcorner2.nif -Decoding meshes\i\in_c_rich_r_cwin_bay_02.nif -Decoding meshes\i\in_c_rich_room_pendside2.nif -Decoding meshes\i\in_c_rich_r_swin_tri_01.nif -Decoding meshes\i\in_c_rich_r_cwin_tri_01.nif -Decoding meshes\i\in_c_rich_room_pendside.nif -Decoding meshes\i\in_c_rich_r_cwin_rec_02.nif -Decoding meshes\i\in_c_rich_r_cwin_rec_04.nif -Decoding meshes\x\ex_redwall_corner_02.nif -Decoding meshes\x\ex_redwall_corner_01.nif -Decoding meshes\x\ex_redwall_corner_03.nif -Decoding meshes\x\ex_redwall_straight_02.nif -Decoding meshes\x\ex_redwall_straight_01.nif -Decoding meshes\x\ex_dwrv_ruin_tower00.nif -Decoding meshes\x\ex_dwrv_steamstack00.nif -Decoding meshes\x\ex_dwrv_pipefitting00.nif -Decoding meshes\i\in_moldcave_sroom4_05.nif -Decoding meshes\i\in_moldcave_lroom3_02.nif -Decoding meshes\i\in_moldcave_lroom4_02.nif -Decoding meshes\i\in_moldcave_nat_exit00.nif -Decoding meshes\i\in_moldcave_sroom4_01.nif -Decoding meshes\i\in_moldcave_sroom3_01.nif -Decoding meshes\i\in_moldcave_lroom4_03.nif -Decoding meshes\i\in_moldcave_lroom4_01.nif -Decoding meshes\i\in_moldcave_lroom3_01.nif -Decoding meshes\i\in_moldcave_sroom4_04.nif -Decoding meshes\i\in_moldcave_lroom2_00.nif -Decoding meshes\i\in_moldcave_lroom3_00.nif -Decoding meshes\i\in_moldcave_lroom4_05.nif -Decoding meshes\i\in_moldcave_sroom4_03.nif -Decoding meshes\i\in_moldcave_doorway00.nif -Decoding meshes\i\in_moldcave_sroom3_02.nif -Decoding meshes\i\in_moldcave_sroom4_02.nif -Decoding meshes\i\in_moldcave_sroom2_00.nif -Decoding meshes\i\in_moldcave_sroom3_00.nif -Decoding meshes\i\in_moldcave_lroom4_04.nif -Decoding meshes\i\in_nord_ladder_01_a.nif -Decoding meshes\i\in_nord_fireplace_01.nif -Decoding meshes\i\in_nord_rafterboards_01.nif -Decoding meshes\x\ex_t_stair_90_l_short.nif -Decoding meshes\x\ex_t_stair_90_r_short.nif -Decoding meshes\i\in_bonecave_sroom4_05.nif -Decoding meshes\i\in_bonecave_lroom3_02.nif -Decoding meshes\i\in_bonecave_lroom4_02.nif -Decoding meshes\i\in_bonecave_sroom4_01.nif -Decoding meshes\i\in_bonecave_sroom3_01.nif -Decoding meshes\i\in_bonecave_lroom4_03.nif -Decoding meshes\i\in_bonecave_lroom4_01.nif -Decoding meshes\i\in_bonecave_lroom3_01.nif -Decoding meshes\i\in_bonecave_sroom4_04.nif -Decoding meshes\i\in_bonecave_lroom2_00.nif -Decoding meshes\i\in_bonecave_lroom3_00.nif -Decoding meshes\i\in_bonecave_lroom4_05.nif -Decoding meshes\i\in_bonecave_sroom4_03.nif -Decoding meshes\i\in_bonecave_doorway00.nif -Decoding meshes\i\in_bonecave_sroom3_02.nif -Decoding meshes\i\in_bonecave_sroom4_02.nif -Decoding meshes\i\in_bonecave_sroom2_00.nif -Decoding meshes\i\in_bonecave_sroom3_00.nif -Decoding meshes\i\in_bonecave_lroom4_04.nif -Decoding meshes\x\in_c_stair_plain_tall_01.nif -Decoding meshes\x\in_c_stair_plain_tall_02.nif -Decoding meshes\i\in_sewer_collapse00.nif -Decoding meshes\i\in_sewer_anchorlock00.nif -Decoding meshes\i\in_vivec_waterspout_01.nif -Decoding meshes\i\in_cavern_wallmount00.nif -Decoding meshes\x\ex_de_shack_plank_03.nif -Decoding meshes\x\ex_de_shack_plank_04.nif -Decoding meshes\x\ex_de_shack_plank_02.nif -Decoding meshes\x\ex_de_ship_oarright.nif -Decoding meshes\x\ex_de_shack_plank_01.nif -Decoding meshes\x\ex_de_shack_steps_01.nif -Decoding meshes\x\ex_de_shack_awning_01.nif -Decoding meshes\x\ex_de_shack_awning_06.nif -Decoding meshes\x\ex_de_shack_awning_03.nif -Decoding meshes\x\ex_de_shack_awning_04.nif -Decoding meshes\x\ex_de_shack_awning_02.nif -Decoding meshes\x\ex_de_shack_awning_05.nif -Decoding meshes\d\in_redoran_hut_door_01.nif -Decoding meshes\d\in_t_door_small_load.nif -Decoding meshes\d\in_c_door_wood_square.nif -Decoding meshes\d\in_t_housepod_door_exit.nif -Decoding meshes\d\in_t_housepod_djamb_exit.nif -Decoding meshes\d\in_velothismall_ndoor_01.nif -Decoding meshes\x\ex_drystonewall_d_01.nif -Decoding meshes\x\ex_drystonewall_c_01.nif -Decoding meshes\x\ex_drystonewall_s_01.nif -Decoding meshes\x\ex_drystonewall_end_01.nif -Decoding meshes\d\in_strong_vaultdoor00.nif -Decoding meshes\d\ex_redoran_hut_01_a.nif -Decoding meshes\d\ex_t_door_sphere_01.nif -Decoding meshes\d\ex_t_door_slavepod_01.nif -Decoding meshes\d\ex_t_door_stone_large.nif -Decoding meshes\d\ex_redoran_barracks_01_a.nif -Decoding meshes\d\ex_velothi_loaddoor_02.nif -Decoding meshes\d\ex_velothi_loaddoor_01.nif -Decoding meshes\d\ex_velothi_entrance_03_a.nif -Decoding meshes\d\ex_velothi_entrance_01_a.nif -Decoding meshes\x\ex_gnisis_roadmarker_01.nif -Decoding meshes\d\in_redoran_barrackdoor_01.nif -Decoding meshes\i\in_mudcave_sroom4_05.nif -Decoding meshes\i\in_mudcave_lroom3_02.nif -Decoding meshes\i\in_mudcave_lroom4_02.nif -Decoding meshes\i\in_mudcave_sroom4_01.nif -Decoding meshes\i\in_mudcave_sroom3_01.nif -Decoding meshes\i\in_mudcave_lroom4_03.nif -Decoding meshes\i\in_mudcave_lroom4_01.nif -Decoding meshes\i\in_mudcave_lroom3_01.nif -Decoding meshes\i\in_mudcave_sroom4_04.nif -Decoding meshes\i\in_mudcave_lroom2_00.nif -Decoding meshes\i\in_mudcave_lroom3_00.nif -Decoding meshes\i\in_mudcave_lroom4_05.nif -Decoding meshes\i\in_mudcave_sroom4_03.nif -Decoding meshes\i\in_mudcave_doorway00.nif -Decoding meshes\i\in_mudcave_sroom3_02.nif -Decoding meshes\i\in_mudcave_sroom4_02.nif -Decoding meshes\i\in_mudcave_sroom2_00.nif -Decoding meshes\i\in_mudcave_sroom3_00.nif -Decoding meshes\i\in_mudcave_lroom4_04.nif -Decoding meshes\i\in_mudcave_nat_exit00.nif -Decoding meshes\x\ex_terrain_woodstep_01.nif -Decoding meshes\x\ex_t_bridge_lcurved.nif -Decoding meshes\n\ingred_dreugh_wax_01.nif -Decoding meshes\n\ingred_ectoplasm_01.nif -Decoding meshes\n\ingred_fire_salts_01.nif -Decoding meshes\n\ingred_whickwheat_01.nif -Decoding meshes\n\ingred_ash_salts_01.nif -Decoding meshes\n\ingred_hound_meat_01.nif -Decoding meshes\n\ingred_scathecraw_01.nif -Decoding meshes\n\ingred_sload_soap_01.nif -Decoding meshes\n\ingred_bc_spore_pod.nif -Decoding meshes\n\ingred_scamp_skin_01.nif -Decoding meshes\n\ingred_trama_root_01.nif -Decoding meshes\n\ingred_moon_sugar_01.nif -Decoding meshes\n\ingred_guar_hide_01.nif -Decoding meshes\n\ingred_alit_hide_01.nif -Decoding meshes\n\ingred_gold_kanet_01.nif -Decoding meshes\n\ingred_chokeweed_01.nif -Decoding meshes\n\ingred_void_salts_01.nif -Decoding meshes\n\ingred_blonemeal_01.nif -Decoding meshes\n\ingred_fire_petal_01.nif -Decoding meshes\n\ingred_raw_glass_01.nif -Decoding meshes\n\ingred_red_lichen_01.nif -Decoding meshes\n\ingred_gravedust_01.nif -Decoding meshes\n\ingred_hackle-lo_01.nif -Decoding meshes\n\ingred_frost_salts_01.nif -Decoding meshes\n\ingred_ghoul_heart_01.nif -Decoding meshes\n\ingred_green_lichen_01.nif -Decoding meshes\n\ingred_daedra_heart_01.nif -Decoding meshes\n\ingred_daedra_skin_01.nif -Decoding meshes\n\ingred_bc_coda_flower.nif -Decoding meshes\n\ingred_black_anther_01.nif -Decoding meshes\n\ingred_black_lichen_01.nif -Decoding meshes\n\ingred_bc_ampoule_pod.nif -Decoding meshes\n\ingred_bittergreen_01.nif -Decoding meshes\n\ingred_bc_hypha_facia.nif -Decoding meshes\n\ingred_marshmerrow_01.nif -Decoding meshes\n\ingred_kresh_fiber_01.nif -Decoding meshes\n\ingred_kagouti_hide_01.nif -Decoding meshes\n\ingred_kwama_cuttle_01.nif -Decoding meshes\n\ingred_vampire_dust_01.nif -Decoding meshes\n\ingred_racer_plumes_01.nif -Decoding meshes\n\ingred_shalk_resin_01.nif -Decoding meshes\n\ingred_scrib_jelly_01.nif -Decoding meshes\n\ingred_scrib_jerky_01.nif -Decoding meshes\n\ingred_scrap_metal_01.nif -Decoding meshes\n\ingred_stoneflower_01.nif -Decoding meshes\n\ingred_6th_corpusmeat_01.nif -Decoding meshes\n\ingred_6th_corpusmeat_02.nif -Decoding meshes\n\ingred_6th_corpusmeat_03.nif -Decoding meshes\n\ingred_6th_corpusmeat_04.nif -Decoding meshes\n\ingred_6th_corpusmeat_05.nif -Decoding meshes\n\ingred_6th_corpusmeat_06.nif -Decoding meshes\n\ingred_6th_corpusmeat_07.nif -Decoding meshes\n\ingred_willow_anther_01.nif -Decoding meshes\n\ingred_corkbulb_root_01.nif -Decoding meshes\n\ingred_bc_bungler's_bane.nif -Decoding meshes\n\ingred_netch_leather_01.nif -Decoding meshes\d\ex_common_door_balcony.nif -Decoding meshes\n\ingred_corprus_weeping_01.nif -Decoding meshes\x\ex_gg_portcullis_01.nif -Decoding meshes\a\a_iron_gauntlet_gnd.nif -Decoding meshes\a\a_iron_pauldron_gnd.nif -Decoding meshes\d\in_de_llshipdoor_large.nif -Decoding meshes\i\in_hlaalu_room_door3.nif -Decoding meshes\i\in_hlaalu_hall_rail.nif -Decoding meshes\i\in_hlaalu_room_post.nif -Decoding meshes\i\in_hlaalu_room_side.nif -Decoding meshes\i\in_hlaalu_roomt_post.nif -Decoding meshes\i\in_hlaalu_hallt_end.nif -Decoding meshes\i\in_hlaalu_hall_3way.nif -Decoding meshes\i\in_hlaalu_room_rail.nif -Decoding meshes\i\in_hlaalu_hallt_3way.nif -Decoding meshes\i\in_hlaalu_hallt_4way.nif -Decoding meshes\i\in_hlaalu_roomt_side.nif -Decoding meshes\i\in_hlaalu_hall_4way.nif -Decoding meshes\i\in_hlaalu_room_door2.nif -Decoding meshes\i\in_hlaalu_room_entry.nif -Decoding meshes\i\in_hlaalu_room_door4.nif -Decoding meshes\i\in_hlaalu_room_door1.nif -Decoding meshes\i\in_hlaalu_ashpit_02.nif -Decoding meshes\i\in_hlaalu_ashpit_01.nif -Decoding meshes\i\in_hlaalu_hallway_ramp.nif -Decoding meshes\i\in_hlaalu_hallt_3wayd.nif -Decoding meshes\i\in_hlaalu_roomt_entry.nif -Decoding meshes\i\in_hlaalu_platform_02.nif -Decoding meshes\i\in_hlaalu_roomt_sided.nif -Decoding meshes\i\in_hlaalu_loaddoor_01.nif -Decoding meshes\i\in_hlaalu_hall_stairsr.nif -Decoding meshes\i\in_hlaalu_hall_stairsl.nif -Decoding meshes\i\in_hlaalu_loaddoor_02.nif -Decoding meshes\i\in_hlaalu_room_center.nif -Decoding meshes\i\in_hlaalu_hallway_end.nif -Decoding meshes\i\in_hlaalu_hallt_center.nif -Decoding meshes\i\in_hlaalu_room_stairsl.nif -Decoding meshes\i\in_hlaalu_room_stairsr.nif -Decoding meshes\i\in_hlaalu_platform_01.nif -Decoding meshes\i\in_hlaalu_hallt_entry.nif -Decoding meshes\i\in_hlaalu_room_corner.nif -Decoding meshes\i\in_hlaalu_roomt_center.nif -Decoding meshes\i\in_hlaalu_roomt_cornd_02.nif -Decoding meshes\i\in_hlaalu_roomt_cornd_01.nif -Decoding meshes\i\in_hlaalu_room_center_01.nif -Decoding meshes\i\in_hlaalu_hallway_stairs.nif -Decoding meshes\i\in_hlaalu_hallt_centerd.nif -Decoding meshes\i\in_hlaalu_hallway_center.nif -Decoding meshes\i\in_hlaalu_hall_corner_01.nif -Decoding meshes\i\in_hlaalu_hallt_cornd_02.nif -Decoding meshes\i\in_hlaalu_hallt_cornd_01.nif -Decoding meshes\i\in_hlaalu_doorjamb_load.nif -Decoding meshes\i\in_pycave_lroom3_01.nif -Decoding meshes\i\in_pycave_sroom3_01.nif -Decoding meshes\i\in_pycave_lroom3_00.nif -Decoding meshes\i\in_pycave_sroom3_00.nif -Decoding meshes\i\in_pycave_lroom3_02.nif -Decoding meshes\i\in_pycave_sroom3_02.nif -Decoding meshes\i\in_pycave_doorway00.nif -Decoding meshes\i\in_pycave_lroom2_00.nif -Decoding meshes\i\in_pycave_sroom2_00.nif -Decoding meshes\i\in_pycave_nat_exit00.nif -Decoding meshes\i\in_pycave_sroom4_04.nif -Decoding meshes\i\in_pycave_lroom4_04.nif -Decoding meshes\i\in_pycave_sroom4_05.nif -Decoding meshes\i\in_pycave_lroom4_05.nif -Decoding meshes\i\in_pycave_sroom4_01.nif -Decoding meshes\i\in_pycave_lroom4_01.nif -Decoding meshes\i\in_pycave_sroom4_02.nif -Decoding meshes\i\in_pycave_lroom4_02.nif -Decoding meshes\i\in_pycave_sroom4_03.nif -Decoding meshes\i\in_pycave_lroom4_03.nif -Decoding meshes\i\in_t_cave_conect_wiz.nif -Decoding meshes\i\in_t_cave_conect_plain.nif -Decoding meshes\i\in_t_cave_conect_endcap.nif -Decoding meshes\a\shield_netch_leather.nif -Decoding meshes\a\shield_nordic_leather.nif -Decoding meshes\i\in_t_manor01_leftcap.nif -Decoding meshes\i\in_t_manor02_backcap.nif -Decoding meshes\i\in_t_manor01_backcap.nif -Decoding meshes\i\in_t_manor_floor_01.nif -Decoding meshes\i\in_t_manor_stairs_01.nif -Decoding meshes\i\in_t_manor02_leftcap.nif -Decoding meshes\i\in_t_manor01_rightcap.nif -Decoding meshes\i\in_t_manor02_cap_right.nif -Decoding meshes\i\in_t_manor01_frontcap.nif -Decoding meshes\i\in_t_manor01_entry_top.nif -Decoding meshes\i\in_t_manor02_rightcap.nif -Decoding meshes\i\in_t_manor02_floor_01.nif -Decoding meshes\i\in_t_manor01_floor_01.nif -Decoding meshes\i\in_t_manor_djamb_exit.nif -Decoding meshes\i\in_t_manor01_cap_left.nif -Decoding meshes\i\in_t_manor02_cap_left.nif -Decoding meshes\i\in_t_manor02_entry_right.nif -Decoding meshes\i\in_t_manor01_entry_left.nif -Decoding meshes\i\in_t_manor02_entry_left.nif -Decoding meshes\i\in_t_railing_pole_rails.nif -Decoding meshes\i\in_lava_blacksquare.nif -Decoding meshes\i\in_lavacave_sroom4_05.nif -Decoding meshes\i\in_lavacave_lroom3_02.nif -Decoding meshes\i\in_lavacave_lroom4_02.nif -Decoding meshes\i\in_lavacave_nat_exit00.nif -Decoding meshes\i\in_lavacave_sroom4_01.nif -Decoding meshes\i\in_lavacave_sroom3_01.nif -Decoding meshes\i\in_lavacave_lroom4_03.nif -Decoding meshes\i\in_lavacave_lroom4_01.nif -Decoding meshes\i\in_lavacave_lroom3_01.nif -Decoding meshes\i\in_lavacave_sroom4_04.nif -Decoding meshes\i\in_lavacave_lroom2_00.nif -Decoding meshes\i\in_lavacave_lroom3_00.nif -Decoding meshes\i\in_lavacave_lroom4_05.nif -Decoding meshes\i\in_lavacave_sroom4_03.nif -Decoding meshes\i\in_lavacave_doorway00.nif -Decoding meshes\i\in_lavacave_sroom3_02.nif -Decoding meshes\i\in_lavacave_sroom4_02.nif -Decoding meshes\i\in_lavacave_sroom2_00.nif -Decoding meshes\i\in_lavacave_sroom3_00.nif -Decoding meshes\i\in_lavacave_lroom4_04.nif -Decoding meshes\i\in_t_manor02_entry_right01.nif -Decoding meshes\i\in_t_manor01_cap_topright.nif -Decoding meshes\i\in_hlaalu_roomt_corner_01.nif -Decoding meshes\i\in_hlaalu_roomt_corner_02.nif -Decoding meshes\i\in_hlaalu_roomt_corner_03.nif -Decoding meshes\i\in_hlaalu_hallt_corner_01.nif -Decoding meshes\r\xg_centurionspider.nif -Decoding meshes\x\ex_gg_fence_s_h_01.nif -Decoding meshes\x\ex_gg_particles_01.nif -Decoding meshes\x\ex_de_docks_3wayb.nif -Decoding meshes\x\ex_de_docks_4wayb.nif -Decoding meshes\x\ex_de_docks_3ways.nif -Decoding meshes\x\ex_de_shack_steps.nif -Decoding meshes\x\ex_de_docks_cleat.nif -Decoding meshes\x\ex_de_docks_center.nif -Decoding meshes\x\ex_de_ship_oarleft.nif -Decoding meshes\x\ex_de_scaffold_03.nif -Decoding meshes\x\ex_de_scaffold_02.nif -Decoding meshes\x\ex_de_scaffold_01.nif -Decoding meshes\x\ex_de_docks_3waysb.nif -Decoding meshes\x\ex_de_docks_steps.nif -Decoding meshes\x\ex_de_docks_piling.nif -Decoding meshes\x\ex_cave_stoneyb00.nif -Decoding meshes\x\ex_vivec_sbase_01.nif -Decoding meshes\x\ex_vivec_bridge_06.nif -Decoding meshes\x\ex_vivec_bridge_07.nif -Decoding meshes\x\ex_vivec_bridge_04.nif -Decoding meshes\x\ex_vivec_bridge_02.nif -Decoding meshes\x\ex_vivec_bridge_01.nif -Decoding meshes\x\ex_vivec_bt_tb_01.nif -Decoding meshes\x\ex_vivec_palace_01.nif -Decoding meshes\x\ex_vivec_ent_t_01.nif -Decoding meshes\x\ex_vivec_ent_t_02.nif -Decoding meshes\x\ex_vivec_ent_b_01.nif -Decoding meshes\x\ex_t_stair_spiral.nif -Decoding meshes\x\ex_t_stair_rcurve.nif -Decoding meshes\x\ex_t_stair_lcurve.nif -Decoding meshes\x\ex_v_sign_hallj_01.nif -Decoding meshes\x\ex_v_sign_arena_01.nif -Decoding meshes\x\ex_v_sign_hallw_01.nif -Decoding meshes\x\ex_dwrv_boulder20.nif -Decoding meshes\x\ex_dwrv_boulder30.nif -Decoding meshes\x\ex_dwrv_boulder00.nif -Decoding meshes\x\ex_dwrv_boulder10.nif -Decoding meshes\x\ex_nord_chimney_01.nif -Decoding meshes\x\in_c_doorframe_01.nif -Decoding meshes\x\ex_t_root_arch_01.nif -Decoding meshes\x\ex_imp_towerb_top.nif -Decoding meshes\x\ex_imp_statue_base.nif -Decoding meshes\x\ex_imp_mooring_01.nif -Decoding meshes\x\ex_imp_entrance_01.nif -Decoding meshes\x\ex_imp_wallent_01.nif -Decoding meshes\x\ex_imp_wallent_02.nif -Decoding meshes\x\ex_t_gateway_great.nif -Decoding meshes\x\ex_v_foundation_01.nif -Decoding meshes\x\ex_v_foundation_02.nif -Decoding meshes\x\ex_v_foundation_03.nif -Decoding meshes\x\ex_dae_b_bo_cape2.nif -Decoding meshes\x\ex_dae_b_bo_cape3.nif -Decoding meshes\x\ex_dae_b_bo_cape1.nif -Decoding meshes\x\ex_dae_wall_256_09.nif -Decoding meshes\x\ex_dae_wall_256_08.nif -Decoding meshes\x\ex_dae_wall_256_01.nif -Decoding meshes\x\ex_dae_wall_256_10.nif -Decoding meshes\x\ex_dae_wall_256_03.nif -Decoding meshes\x\ex_dae_wall_256_13.nif -Decoding meshes\x\ex_dae_wall_256_02.nif -Decoding meshes\x\ex_dae_wall_256_05.nif -Decoding meshes\x\ex_dae_wall_256_04.nif -Decoding meshes\x\ex_dae_wall_256_07.nif -Decoding meshes\x\ex_dae_wall_256_06.nif -Decoding meshes\x\ex_dae_wall_512_09.nif -Decoding meshes\x\ex_dae_wall_512_08.nif -Decoding meshes\x\ex_dae_wall_512_05.nif -Decoding meshes\x\ex_dae_wall_512_04.nif -Decoding meshes\x\ex_dae_wall_512_07.nif -Decoding meshes\x\ex_dae_wall_512_06.nif -Decoding meshes\x\ex_dae_wall_512_01.nif -Decoding meshes\x\ex_dae_wall_512_10.nif -Decoding meshes\x\ex_dae_wall_512_03.nif -Decoding meshes\x\ex_dae_wall_512_02.nif -Decoding meshes\x\ex_dae_azura_small.nif -Decoding meshes\x\ex_dae_sheogorath.nif -Decoding meshes\x\ex_dae_b_bo_ubody.nif -Decoding meshes\x\ex_dae_b_bo_lbody.nif -Decoding meshes\x\ex_redoran_hut_02.nif -Decoding meshes\x\ex_redoran_hut_01.nif -Decoding meshes\x\ex_redwall_post_01.nif -Decoding meshes\x\ex_redwall_arch_01.nif -Decoding meshes\x\ex_t_clawgrowth_02.nif -Decoding meshes\x\ex_t_clawgrowth_01.nif -Decoding meshes\x\ex_v_ban_vivec_01.nif -Decoding meshes\x\ex_v_ban_vivec_02.nif -Decoding meshes\x\ex_v_ban_arena_01.nif -Decoding meshes\x\ex_v_ban_faith_01.nif -Decoding meshes\x\ex_v_ban_stolms_01.nif -Decoding meshes\x\ex_v_ban_count_01.nif -Decoding meshes\x\ex_v_ban_hlaalu_01.nif -Decoding meshes\x\ex_v_ban_child_01.nif -Decoding meshes\x\ex_v_ban_speak_01.nif -Decoding meshes\x\ex_scave2_enter10.nif -Decoding meshes\x\ex_hlaalu_pole_01.nif -Decoding meshes\x\ex_hlaalu_pole_02.nif -Decoding meshes\x\ex_hlaalu_wall_01.nif -Decoding meshes\x\ex_hlaalu_wall_02.nif -Decoding meshes\x\ex_hlaalu_canal_05.nif -Decoding meshes\x\ex_hlaalu_canal_04.nif -Decoding meshes\x\ex_hlaalu_canal_07.nif -Decoding meshes\x\ex_hlaalu_canal_06.nif -Decoding meshes\x\ex_hlaalu_canal_11.nif -Decoding meshes\x\ex_hlaalu_canal_01.nif -Decoding meshes\x\ex_hlaalu_canal_10.nif -Decoding meshes\x\ex_hlaalu_canal_13.nif -Decoding meshes\x\ex_hlaalu_canal_03.nif -Decoding meshes\x\ex_hlaalu_canal_12.nif -Decoding meshes\x\ex_hlaalu_canal_02.nif -Decoding meshes\x\ex_hlaalu_canal_09.nif -Decoding meshes\x\ex_hlaalu_canal_08.nif -Decoding meshes\x\ex_hlaalu_steps_02.nif -Decoding meshes\x\ex_hlaalu_steps_03.nif -Decoding meshes\x\ex_hlaalu_steps_01.nif -Decoding meshes\x\ex_hlaalu_steps_06.nif -Decoding meshes\x\ex_hlaalu_steps_07.nif -Decoding meshes\x\ex_hlaalu_steps_04.nif -Decoding meshes\x\ex_hlaalu_steps_05.nif -Decoding meshes\x\ex_common_trellis.nif -Decoding meshes\x\ex_common_plat_lrg.nif -Decoding meshes\x\ex_common_plat_end.nif -Decoding meshes\x\ex_velothi_dome_01.nif -Decoding meshes\x\ex_siltstrider_01.nif -Decoding meshes\x\ex_siltstrider_02.nif -Decoding meshes\x\ex_siltstrider_03.nif -Decoding meshes\x\ex_siltstrider_04.nif -Decoding meshes\x\ex_siltstrider_05.nif -Decoding meshes\x\ex_ruin_boulder01.nif -Decoding meshes\x\ex_ruin_boulder00.nif -Decoding meshes\x\ex_ruin_boulder03.nif -Decoding meshes\x\ex_ruin_boulder02.nif -Decoding meshes\x\ex_ruin_boulder05.nif -Decoding meshes\x\ex_ruin_boulder04.nif -Decoding meshes\x\ex_ashl_banner_01.nif -Decoding meshes\x\ex_t_housestem_02.nif -Decoding meshes\x\ex_t_housestem_03.nif -Decoding meshes\x\ex_t_housestem_01.nif -Decoding meshes\x\ex_ashl_door_01.nif -Decoding meshes\x\ex_ashl_tent_03.nif -Decoding meshes\x\ex_ashl_tent_01.nif -Decoding meshes\x\ex_ashl_door_02.nif -Decoding meshes\x\ex_ashl_tent_04.nif -Decoding meshes\x\ex_ashl_tent_02.nif -Decoding meshes\x\ex_trellis_vine.nif -Decoding meshes\x\ex_dwrv_walker20.nif -Decoding meshes\x\ex_dwrv_walker10.nif -Decoding meshes\x\ex_dwrv_walker00.nif -Decoding meshes\x\ex_dwrv_observ00.nif -Decoding meshes\x\ex_dwrv_block10.nif -Decoding meshes\x\ex_dwrv_block20.nif -Decoding meshes\x\ex_dwrv_header00.nif -Decoding meshes\x\ex_dwrv_statue00.nif -Decoding meshes\x\ex_dwrv_enter00.nif -Decoding meshes\x\ex_dwrv_bridge10.nif -Decoding meshes\x\ex_dwrv_bridge00.nif -Decoding meshes\x\ex_dwrv_block00.nif -Decoding meshes\x\ex_dwrv_alcove10.nif -Decoding meshes\x\ex_dwrv_cosmo00.nif -Decoding meshes\x\ex_dwrv_block30.nif -Decoding meshes\x\ex_dwrv_alcove00.nif -Decoding meshes\x\ex_ruin_rubble20.nif -Decoding meshes\x\ex_ruin_rubble00.nif -Decoding meshes\x\ex_ruin_rubble10.nif -Decoding meshes\x\ex_dae_pillar_02.nif -Decoding meshes\x\ex_dae_pillar_03.nif -Decoding meshes\x\ex_dae_pillar_01.nif -Decoding meshes\x\ex_dae_b_bo_head.nif -Decoding meshes\x\ex_dae_boethiah.nif -Decoding meshes\x\ex_daed_ruin_01.nif -Decoding meshes\x\ex_dae_b_bo_axe.nif -Decoding meshes\x\ex_dae_molagbal.nif -Decoding meshes\x\ex_barnacles_01.nif -Decoding meshes\x\ex_barnacles_03.nif -Decoding meshes\x\ex_barnacles_05.nif -Decoding meshes\x\ex_barnacles_02.nif -Decoding meshes\x\ex_barnacles_04.nif -Decoding meshes\x\ex_barnacles_06.nif -Decoding meshes\x\ex_cave_grass00.nif -Decoding meshes\x\ex_cave_scrub00.nif -Decoding meshes\x\ex_gg_fence_s_04.nif -Decoding meshes\x\ex_gg_fence_s_03.nif -Decoding meshes\x\ex_gg_fence_s_02.nif -Decoding meshes\x\ex_gg_fence_s_01.nif -Decoding meshes\x\ex_redwall_up_01.nif -Decoding meshes\x\ex_redwall_b_01.nif -Decoding meshes\x\ex_redwall_b_03.nif -Decoding meshes\x\ex_redwall_b_05.nif -Decoding meshes\x\ex_redwall_p_01.nif -Decoding meshes\x\ex_redwall_p_03.nif -Decoding meshes\x\ex_redwall_p_05.nif -Decoding meshes\x\ex_redwall_b_02.nif -Decoding meshes\x\ex_redwall_b_04.nif -Decoding meshes\x\ex_redwall_b_06.nif -Decoding meshes\x\ex_redwall_p_02.nif -Decoding meshes\x\ex_redwall_p_04.nif -Decoding meshes\x\ex_de_constr_03.nif -Decoding meshes\x\ex_de_constr_01.nif -Decoding meshes\x\ex_de_constr_05.nif -Decoding meshes\x\ex_de_railing_01.nif -Decoding meshes\x\ex_de_railing_03.nif -Decoding meshes\x\ex_de_railing_02.nif -Decoding meshes\x\ex_de_railing_05.nif -Decoding meshes\x\ex_de_railing_04.nif -Decoding meshes\x\ex_de_railing_06.nif -Decoding meshes\x\ex_de_docks_128.nif -Decoding meshes\x\ex_de_shipwreck.nif -Decoding meshes\x\ex_de_docks_3way.nif -Decoding meshes\x\ex_de_docks_4way.nif -Decoding meshes\x\ex_de_docks_endc.nif -Decoding meshes\x\ex_de_docks_ends.nif -Decoding meshes\x\ex_de_constr_02.nif -Decoding meshes\x\ex_de_constr_06.nif -Decoding meshes\x\ex_de_constr_04.nif -Decoding meshes\x\ex_de_docks_end.nif -Decoding meshes\x\ex_de_docks_gate.nif -Decoding meshes\x\ex_de_shack_door.nif -Decoding meshes\x\ex_vivec_hfq_03.nif -Decoding meshes\x\ex_vivec_hfq_01.nif -Decoding meshes\x\ex_vivec_pqs_02.nif -Decoding meshes\x\ex_vivec_b_tb_01.nif -Decoding meshes\x\ex_vivec_b_wb_01.nif -Decoding meshes\x\ex_vivec_g_r_01.nif -Decoding meshes\x\ex_vivec_hfq_04.nif -Decoding meshes\x\ex_vivec_hfq_02.nif -Decoding meshes\x\ex_vivec_pqs_01.nif -Decoding meshes\x\ex_vivec_w_c_01.nif -Decoding meshes\x\ex_vivec_w_g_01.nif -Decoding meshes\x\ex_vivec_w_e_01.nif -Decoding meshes\x\ex_vivec_telt_01.nif -Decoding meshes\x\ex_vivec_g_r_02.nif -Decoding meshes\x\ex_vivec_b_t_01.nif -Decoding meshes\x\ex_ci_doorjam_01.nif -Decoding meshes\x\ex_volcano_steam.nif -Decoding meshes\x\ex_ropebridge_01.nif -Decoding meshes\x\ex_nord_rock_01.nif -Decoding meshes\x\ex_nord_well_01.nif -Decoding meshes\x\ex_nord_door_02.nif -Decoding meshes\x\ex_nord_rock_02.nif -Decoding meshes\x\ex_nord_house_03.nif -Decoding meshes\x\ex_nord_house_02.nif -Decoding meshes\x\ex_nord_house_01.nif -Decoding meshes\x\ex_nord_house_05.nif -Decoding meshes\x\ex_nord_house_04.nif -Decoding meshes\x\ex_nord_doorf_01.nif -Decoding meshes\x\ex_holamayan_01.nif -Decoding meshes\x\ex_emp_tower_01.nif -Decoding meshes\x\ex_imp_arrowslit.nif -Decoding meshes\x\ex_imp_bridge_01.nif -Decoding meshes\x\ex_imp_bridge_02.nif -Decoding meshes\x\ex_hlaalu_fb_01.nif -Decoding meshes\x\ex_hlaalu_win_03.nif -Decoding meshes\x\ex_hlaalu_win_02.nif -Decoding meshes\x\ex_hlaalu_win_01.nif -Decoding meshes\x\ex_hlaalu_bal_02.nif -Decoding meshes\x\ex_hlaalu_bal_01.nif -Decoding meshes\x\ex_hlaalu_fb_02.nif -Decoding meshes\x\ex_t_housepod_01.nif -Decoding meshes\x\ex_t_housepod_03.nif -Decoding meshes\x\ex_t_housepod_02.nif -Decoding meshes\x\ex_t_housepod_04.nif -Decoding meshes\x\ex_t_spiralramp.nif -Decoding meshes\x\ex_t_councilhall.nif -Decoding meshes\x\ex_t_pole_hooked.nif -Decoding meshes\x\ex_t_tower_bent.nif -Decoding meshes\x\ex_t_menhir_l_01.nif -Decoding meshes\x\ex_t_doorway_01.nif -Decoding meshes\x\ex_t_root_xl_03.nif -Decoding meshes\x\ex_t_root_xl_01.nif -Decoding meshes\x\ex_t_bigroot_01.nif -Decoding meshes\x\ex_t_slavemarket.nif -Decoding meshes\x\ex_t_slavepod_01.nif -Decoding meshes\x\ex_t_platform_01.nif -Decoding meshes\x\ex_t_platform_02.nif -Decoding meshes\x\ex_t_doorway_02.nif -Decoding meshes\x\ex_t_root_xl_02.nif -Decoding meshes\x\ex_t_bigroot_02.nif -Decoding meshes\x\ex_v_ban_walk_01.nif -Decoding meshes\x\ex_v_2_floor_01.nif -Decoding meshes\x\ex_v_sign_fq_01.nif -Decoding meshes\x\ex_v_1_corner_01.nif -Decoding meshes\x\ex_v_2_corner_01.nif -Decoding meshes\x\ex_v_ban_imp_01.nif -Decoding meshes\x\ex_v_stdeyln_01.nif -Decoding meshes\x\ex_v_ban_avs_01.nif -Decoding meshes\x\ex_v_2_stairs_01.nif -Decoding meshes\x\ex_t_turret_02.nif -Decoding meshes\x\ex_t_menhir_01.nif -Decoding meshes\x\ex_t_dormer_01.nif -Decoding meshes\x\ex_t_stair_01.nif -Decoding meshes\x\ex_t_root_stem.nif -Decoding meshes\x\ex_t_awning_02.nif -Decoding meshes\x\ex_t_manor_01.nif -Decoding meshes\x\ex_t_manor_02.nif -Decoding meshes\x\ex_t_dock_main.nif -Decoding meshes\x\ex_t_brace_01.nif -Decoding meshes\x\ex_t_turret_03.nif -Decoding meshes\x\ex_t_turret_01.nif -Decoding meshes\x\ex_t_dormer_02.nif -Decoding meshes\x\ex_t_awning_01.nif -Decoding meshes\x\ex_t_rootball.nif -Decoding meshes\x\ex_t_root_hook.nif -Decoding meshes\x\ex_vivec_h_02.nif -Decoding meshes\x\ex_vivec_h_12.nif -Decoding meshes\x\ex_vivec_c_02.nif -Decoding meshes\x\ex_vivec_g_02.nif -Decoding meshes\x\ex_vivec_w_02.nif -Decoding meshes\x\ex_v_banner_02.nif -Decoding meshes\x\ex_vivec_h_14.nif -Decoding meshes\x\ex_vivec_h_15.nif -Decoding meshes\x\ex_vivec_c_03.nif -Decoding meshes\x\ex_vivec_h_03.nif -Decoding meshes\x\ex_vivec_w_03.nif -Decoding meshes\x\ex_vivec_h_06.nif -Decoding meshes\x\ex_vivec_hf_02.nif -Decoding meshes\x\ex_vivec_hf_04.nif -Decoding meshes\x\ex_vivec_h_07.nif -Decoding meshes\x\ex_vivec_pq_02.nif -Decoding meshes\x\ex_vivec_pq_04.nif -Decoding meshes\x\ex_vivec_lp_02.nif -Decoding meshes\x\ex_vivec_h_05.nif -Decoding meshes\x\ex_vivec_h_17.nif -Decoding meshes\x\ex_vivec_h_16.nif -Decoding meshes\x\ex_vivec_t_04.nif -Decoding meshes\x\ex_vivec_h_04.nif -Decoding meshes\x\ex_vivec_c_04.nif -Decoding meshes\x\ex_v_bpost_01.nif -Decoding meshes\x\ex_v_bpost_03.nif -Decoding meshes\x\ex_v_stolms_01.nif -Decoding meshes\x\ex_v_bpost_02.nif -Decoding meshes\x\ex_vivec_h_09.nif -Decoding meshes\x\ex_v_banner_01.nif -Decoding meshes\x\ex_v_banner_03.nif -Decoding meshes\x\ex_vivec_w_01.nif -Decoding meshes\x\ex_vivec_t_01.nif -Decoding meshes\x\ex_vivec_h_11.nif -Decoding meshes\x\ex_vivec_h_01.nif -Decoding meshes\x\ex_vivec_g_01.nif -Decoding meshes\x\ex_vivec_b_01.nif -Decoding meshes\x\ex_vivec_c_01.nif -Decoding meshes\x\ex_vivec_h_10.nif -Decoding meshes\x\ex_vivec_h_13.nif -Decoding meshes\x\ex_v_1_wall_01.nif -Decoding meshes\x\ex_v_2_wall_01.nif -Decoding meshes\x\ex_vivec_hf_01.nif -Decoding meshes\x\ex_vivec_hf_03.nif -Decoding meshes\x\ex_vivec_pd_01.nif -Decoding meshes\x\ex_vivec_h_08.nif -Decoding meshes\x\ex_vivec_ps_01.nif -Decoding meshes\x\ex_vivec_pq_01.nif -Decoding meshes\x\ex_vivec_pq_03.nif -Decoding meshes\x\ex_vivec_lp_01.nif -Decoding meshes\x\ex_vivec_bt_01.nif -Decoding meshes\x\ex_rope_short.nif -Decoding meshes\x\ex_ship_plank.nif -Decoding meshes\x\ex_scrapwood04.nif -Decoding meshes\x\ex_scrapwood02.nif -Decoding meshes\x\ex_ship_stair.nif -Decoding meshes\x\ex_scrapwood05.nif -Decoding meshes\x\ex_scrapwood03.nif -Decoding meshes\x\ex_scrapwood01.nif -Decoding meshes\x\ex_dwrv_ruin50.nif -Decoding meshes\x\ex_dae_ruin_04.nif -Decoding meshes\x\ex_dae_ruin_02.nif -Decoding meshes\x\ex_dwrv_ruin20.nif -Decoding meshes\x\ex_de_shack_05.nif -Decoding meshes\x\ex_de_shack_03.nif -Decoding meshes\x\ex_de_shack_01.nif -Decoding meshes\x\ex_dwrv_wall20.nif -Decoding meshes\x\ex_dwrv_wall30.nif -Decoding meshes\x\ex_dwrv_wall50.nif -Decoding meshes\x\ex_dwrv_wall60.nif -Decoding meshes\x\ex_dwrv_wall40.nif -Decoding meshes\x\ex_dwrv_wall10.nif -Decoding meshes\x\ex_dae_claw_02.nif -Decoding meshes\x\ex_dwrv_ruin10.nif -Decoding meshes\x\ex_dwrv_pipe10.nif -Decoding meshes\x\ex_dwrv_ruin40.nif -Decoding meshes\x\ex_dwrv_ruin00.nif -Decoding meshes\x\ex_dae_ruin_01.nif -Decoding meshes\x\ex_dae_ruin_03.nif -Decoding meshes\x\ex_de_shack_04.nif -Decoding meshes\x\ex_de_shack_02.nif -Decoding meshes\x\ex_dwrv_wall00.nif -Decoding meshes\x\ex_de_sn_gate.nif -Decoding meshes\x\ex_dwrv_ruin30.nif -Decoding meshes\x\ex_dwrv_ruin60.nif -Decoding meshes\x\ex_dwrv_pipe00.nif -Decoding meshes\x\ex_dwrv_ruin80.nif -Decoding meshes\x\ex_dae_claw_01.nif -Decoding meshes\x\ex_de_rowboat.nif -Decoding meshes\x\ex_dwrv_ruin70.nif -Decoding meshes\x\ex_gg_fence_02.nif -Decoding meshes\x\ex_gg_fence_04.nif -Decoding meshes\x\ex_gg_pylon_01.nif -Decoding meshes\x\ex_gondola_01.nif -Decoding meshes\x\ex_gg_fence_03.nif -Decoding meshes\x\ex_gg_fence_01.nif -Decoding meshes\x\ex_gg_pylon_02.nif -Decoding meshes\x\ex_ashl_banner.nif -Decoding meshes\x\ex_coiled_rope.nif -Decoding meshes\x\ex_cave_sand00.nif -Decoding meshes\x\ex_cave_ash10.nif -Decoding meshes\x\ex_cave_ash00.nif -Decoding meshes\x\ex_lavaspark01.nif -Decoding meshes\x\ex_lavaspark03.nif -Decoding meshes\x\ex_longboat02.nif -Decoding meshes\x\ex_lavasteam00.nif -Decoding meshes\x\ex_lavaspark02.nif -Decoding meshes\x\ex_longboatwrk.nif -Decoding meshes\x\ex_longboat01.nif -Decoding meshes\x\ex_nord_win_01.nif -Decoding meshes\x\ex_nord_win_02.nif -Decoding meshes\x\ex_hlaalu_b_04.nif -Decoding meshes\x\ex_hlaalu_b_06.nif -Decoding meshes\x\ex_hlaalu_b_02.nif -Decoding meshes\x\ex_hlaalu_b_08.nif -Decoding meshes\x\ex_hlaalu_b_18.nif -Decoding meshes\x\ex_hlaalu_b_12.nif -Decoding meshes\x\ex_hlaalu_b_10.nif -Decoding meshes\x\ex_hlaalu_b_16.nif -Decoding meshes\x\ex_hlaalu_b_14.nif -Decoding meshes\x\ex_hlaalu_b_25.nif -Decoding meshes\x\ex_hlaalu_b_27.nif -Decoding meshes\x\ex_hlaalu_b_21.nif -Decoding meshes\x\ex_hlaalu_b_23.nif -Decoding meshes\x\ex_hlaalu_b_05.nif -Decoding meshes\x\ex_hlaalu_b_07.nif -Decoding meshes\x\ex_hlaalu_b_01.nif -Decoding meshes\x\ex_hlaalu_b_03.nif -Decoding meshes\x\ex_hlaalu_b_09.nif -Decoding meshes\x\ex_hlaalu_b_19.nif -Decoding meshes\x\ex_hlaalu_b_13.nif -Decoding meshes\x\ex_hlaalu_b_11.nif -Decoding meshes\x\ex_hlaalu_b_17.nif -Decoding meshes\x\ex_hlaalu_b_15.nif -Decoding meshes\x\ex_hlaalu_b_24.nif -Decoding meshes\x\ex_hlaalu_b_26.nif -Decoding meshes\x\ex_hlaalu_b_20.nif -Decoding meshes\x\ex_hlaalu_b_22.nif -Decoding meshes\x\ex_hlaalu_b_28.nif -Decoding meshes\x\ex_imp_wall_01.nif -Decoding meshes\x\ex_imp_keep_02.nif -Decoding meshes\x\ex_imp_plat_01.nif -Decoding meshes\x\ex_imp_pool_01.nif -Decoding meshes\x\ex_imp_dock_01.nif -Decoding meshes\x\ex_imp_keep_01.nif -Decoding meshes\x\ex_imp_dock_02.nif -Decoding meshes\x\ex_t_root_02.nif -Decoding meshes\x\ex_t_root_04.nif -Decoding meshes\x\ex_dae_azura.nif -Decoding meshes\x\ex_t_root_05.nif -Decoding meshes\x\ex_gg_ent_01.nif -Decoding meshes\x\ex_t_root_06.nif -Decoding meshes\x\ex_t_tunnel.nif -Decoding meshes\x\ex_t_root_01.nif -Decoding meshes\x\ex_t_pole_01.nif -Decoding meshes\x\ex_longboat.nif -Decoding meshes\x\ex_imp_plaza.nif -Decoding meshes\x\ex_t_root_03.nif -Decoding meshes\x\ex_cave_mt00.nif -Decoding meshes\x\ex_v_dock_01.nif -Decoding meshes\x\ex_t_dock_01.nif -Decoding meshes\f\ex_ashl_e_banner_r.nif -Decoding meshes\f\ex_ashl_a_banner_r.nif -Decoding meshes\f\ex_ashl_z_banner_r.nif -Decoding meshes\f\ex_ashl_u_banner_r.nif -Decoding meshes\f\ex_ashl_z_banner.nif -Decoding meshes\f\ex_ashl_a_banner.nif -Decoding meshes\f\ex_ashl_u_banner.nif -Decoding meshes\f\ex_ashl_e_banner.nif -Decoding meshes\f\ex_ashl_banner.nif -Decoding meshes\f\ex_boulder02.nif -Decoding meshes\f\ex_boulder04.nif -Decoding meshes\f\ex_boulder00.nif -Decoding meshes\f\ex_boulder05.nif -Decoding meshes\f\ex_boulder08.nif -Decoding meshes\f\ex_boulder07.nif -Decoding meshes\f\ex_boulder06.nif -Decoding meshes\f\ex_boulder03.nif -Decoding meshes\f\ex_boulder01.nif -Decoding meshes\d\ex_vivec_grate_01.nif -Decoding meshes\d\ex_emp_tower_01_b.nif -Decoding meshes\d\ex_emp_tower_01_a.nif -Decoding meshes\d\ex_imp_loaddoor_03.nif -Decoding meshes\d\ex_imp_loaddoor_02.nif -Decoding meshes\d\ex_imp_loaddoor_01.nif -Decoding meshes\d\in_t_s_plain_door.nif -Decoding meshes\d\in_v_s_trapdoor_02.nif -Decoding meshes\d\in_v_s_trapdoor_01.nif -Decoding meshes\d\in_v_s_jaildoor_01.nif -Decoding meshes\d\ex_v_cantondoor_01.nif -Decoding meshes\d\in_vivec_grate_01.nif -Decoding meshes\d\ex_common_door_01.nif -Decoding meshes\d\in_de_cabindoor.nif -Decoding meshes\d\in_t_door_small.nif -Decoding meshes\d\in_r_trapdoor_01.nif -Decoding meshes\d\in_c_door_arched.nif -Decoding meshes\d\in_h_trapdoor_01.nif -Decoding meshes\d\in_t_l_door_01.nif -Decoding meshes\d\in_r_s_door_01.nif -Decoding meshes\d\in_dae_door_01.nif -Decoding meshes\d\in_ar_door_01.nif -Decoding meshes\d\in_ci_door_01.nif -Decoding meshes\d\in_hlaalu_door.nif -Decoding meshes\d\ex_cave_door_01.nif -Decoding meshes\d\ex_de_ship_door.nif -Decoding meshes\d\ex_nord_door_01.nif -Decoding meshes\d\ex_nord_door_02.nif -Decoding meshes\d\ex_r_trapdoor_01.nif -Decoding meshes\d\ex_h_trapdoor_01.nif -Decoding meshes\d\ex_t_door_02.nif -Decoding meshes\d\ex_t_door_01.nif -Decoding meshes\l\light_6th_candle_05.nif -Decoding meshes\l\light_6th_candle_04.nif -Decoding meshes\l\light_6th_candle_06.nif -Decoding meshes\l\light_6th_candle_01.nif -Decoding meshes\l\light_6th_candle_03.nif -Decoding meshes\l\light_6th_candle_02.nif -Decoding meshes\base_anim_female.1st.nif -Decoding meshes\base_anim_female.nif -Decoding meshes\base_animkna.1st.nif -Decoding meshes\base_anim.1st.nif -Decoding meshes\anim_dancinggirl.nif -Decoding meshes\x\ex_common_tower_thatchedroof.nif -Decoding meshes\menu_scroll_hort_bar.nif -Decoding meshes\menu_scroll_vert_bar.nif -Decoding meshes\menu_scroll_scroller.nif -Decoding meshes\menu_rightbuttondown.nif -Decoding meshes\main_menu_button.nif -Decoding meshes\menu_button_frame.nif -Decoding meshes\menu_thick_border.nif -Decoding meshes\menu_rightbuttonup.nif -Decoding meshes\menu_scroll_slider.nif -Decoding meshes\menu_messagebox.nif -Decoding meshes\menu_icon_frame.nif -Decoding meshes\menu_head_block.nif -Decoding meshes\menu_thin_border.nif -Decoding meshes\menu_scroll_bar.nif -Decoding meshes\menu_contents.nif -Decoding meshes\menu_scroll_da.nif -Decoding meshes\menu_scroll_ua.nif -Decoding meshes\i\in_vs_pitfloor_01.nif -Decoding meshes\i\in_ar_shelltop_01.nif -Decoding meshes\i\in_ar_platform_10.nif -Decoding meshes\i\in_ar_platform_04.nif -Decoding meshes\i\in_ar_platform_05.nif -Decoding meshes\i\in_ar_platform_06.nif -Decoding meshes\i\in_ar_platform_07.nif -Decoding meshes\i\in_ar_platform_01.nif -Decoding meshes\i\in_ar_platform_02.nif -Decoding meshes\i\in_ar_platform_03.nif -Decoding meshes\i\in_ar_platform_08.nif -Decoding meshes\i\in_ar_platform_09.nif -Decoding meshes\i\in_hlaalu_hall_128.nif -Decoding meshes\i\in_hlaalu_door_01.nif -Decoding meshes\i\in_hlaalu_doorjamb.nif -Decoding meshes\i\in_t_council_beams.nif -Decoding meshes\i\in_t_balconyscreen.nif -Decoding meshes\i\in_dae_doorjamb_01.nif -Decoding meshes\i\in_mudcave2_s_end.nif -Decoding meshes\i\in_mudcave_stal10.nif -Decoding meshes\i\in_mudcave_stal00.nif -Decoding meshes\i\in_mudcave_stal30.nif -Decoding meshes\i\in_mudcave_stal20.nif -Decoding meshes\i\in_mudcave_stal50.nif -Decoding meshes\i\in_mudcave_stal40.nif -Decoding meshes\i\in_mudcave_exit00.nif -Decoding meshes\i\in_mudcave_form00.nif -Decoding meshes\i\in_mudcave_form10.nif -Decoding meshes\i\in_mudcave_form20.nif -Decoding meshes\i\in_mudcave_form30.nif -Decoding meshes\i\in_redoran_hut_01.nif -Decoding meshes\i\in_c_doorframe_01.nif -Decoding meshes\i\in_dagoth_bridge00.nif -Decoding meshes\i\in_dagoth_plate10.nif -Decoding meshes\i\in_dagoth_plate00.nif -Decoding meshes\i\in_dagoth_plate20.nif -Decoding meshes\i\in_r_guild_mage_01.nif -Decoding meshes\i\in_com_traptop_01.nif -Decoding meshes\i\in_com_wincover_02.nif -Decoding meshes\i\in_com_wincover_01.nif -Decoding meshes\i\in_vsl_pitbd_b_01.nif -Decoding meshes\i\in_vsl_pitmd_b_01.nif -Decoding meshes\i\in_moldcave2_s_00.nif -Decoding meshes\i\in_moldcave2_s_01.nif -Decoding meshes\i\in_moldcave2_s_02.nif -Decoding meshes\i\in_moldcave2_s_03.nif -Decoding meshes\i\in_moldcave2_s_04.nif -Decoding meshes\i\in_moldcave2_s_05.nif -Decoding meshes\i\in_moldcave2_s_06.nif -Decoding meshes\i\in_moldcave_exit00.nif -Decoding meshes\i\in_moldcave2_s_end.nif -Decoding meshes\i\in_moldcave4_s_00.nif -Decoding meshes\i\in_moldcave_stal00.nif -Decoding meshes\i\in_moldcave_stal10.nif -Decoding meshes\i\in_moldcave_stal20.nif -Decoding meshes\i\in_moldcave_stal30.nif -Decoding meshes\i\in_moldcave_stal40.nif -Decoding meshes\i\in_moldcave_stal50.nif -Decoding meshes\i\in_moldcave_form20.nif -Decoding meshes\i\in_moldcave_form30.nif -Decoding meshes\i\in_moldcave_form00.nif -Decoding meshes\i\in_moldcave_form10.nif -Decoding meshes\i\in_velothicave_01.nif -Decoding meshes\i\in_velothicave_03.nif -Decoding meshes\i\in_velothicave_02.nif -Decoding meshes\i\in_velothicave_04.nif -Decoding meshes\i\in_t_l_room_corner.nif -Decoding meshes\i\in_t_l_room_entry.nif -Decoding meshes\i\in_t_l_hall_corner.nif -Decoding meshes\i\in_t_l_room_floor.nif -Decoding meshes\i\in_t_l_doorjamb_01.nif -Decoding meshes\i\in_v_l_int_wall_01.nif -Decoding meshes\i\in_v_l_int_wall_02.nif -Decoding meshes\i\in_v_l_int_rail_01.nif -Decoding meshes\i\in_r_l_int_wall_01.nif -Decoding meshes\i\in_r_l_int_wall_02.nif -Decoding meshes\i\in_r_l_doorjamb_02.nif -Decoding meshes\i\in_r_l_int_rail_01.nif -Decoding meshes\i\in_r_l_int_arch_01.nif -Decoding meshes\i\in_bonecave2_s_08.nif -Decoding meshes\i\in_bonecave2_s_09.nif -Decoding meshes\i\in_bonecave2_s_00.nif -Decoding meshes\i\in_bonecave2_s_01.nif -Decoding meshes\i\in_bonecave2_s_02.nif -Decoding meshes\i\in_bonecave2_s_03.nif -Decoding meshes\i\in_bonecave2_s_04.nif -Decoding meshes\i\in_bonecave2_s_05.nif -Decoding meshes\i\in_bonecave2_s_06.nif -Decoding meshes\i\in_bonecave2_s_07.nif -Decoding meshes\i\in_bonecave2_s_end.nif -Decoding meshes\i\in_bonecave4_s_00.nif -Decoding meshes\i\in_bonecave_stal00.nif -Decoding meshes\i\in_bonecave_stal10.nif -Decoding meshes\i\in_bonecave_stal20.nif -Decoding meshes\i\in_bonecave_stal30.nif -Decoding meshes\i\in_bonecave_stal40.nif -Decoding meshes\i\in_bonecave_stal50.nif -Decoding meshes\i\in_bonecave_form20.nif -Decoding meshes\i\in_bonecave_form30.nif -Decoding meshes\i\in_bonecave_form00.nif -Decoding meshes\i\in_bonecave_form10.nif -Decoding meshes\i\in_bonecave_exit00.nif -Decoding meshes\i\in_impbig_4way_01.nif -Decoding meshes\i\in_impbig_wall_01.nif -Decoding meshes\i\in_impbig_blend_01.nif -Decoding meshes\i\in_c_plain_corner.nif -Decoding meshes\i\in_c_pillar_stone.nif -Decoding meshes\i\in_c_plain_endcap.nif -Decoding meshes\i\in_t_s_room_corner.nif -Decoding meshes\i\in_t_stairs_wiz_01.nif -Decoding meshes\i\in_t_stairs_wiz_03.nif -Decoding meshes\i\in_t_stairs_wiz_02.nif -Decoding meshes\i\in_t_s_room_entry.nif -Decoding meshes\i\in_t_s_shaft_6way.nif -Decoding meshes\i\in_t_s_djamb_plain.nif -Decoding meshes\i\in_t_s_room_center.nif -Decoding meshes\i\in_v_s_int_wall_01.nif -Decoding meshes\i\in_v_s_int_wall_03.nif -Decoding meshes\i\in_v_s_int_wall_02.nif -Decoding meshes\i\in_v_s_int_wall_04.nif -Decoding meshes\i\in_v_s_domeroom_01.nif -Decoding meshes\i\in_r_s_int_wall_01.nif -Decoding meshes\i\in_r_s_int_wall_02.nif -Decoding meshes\i\in_r_s_doorjamb_01.nif -Decoding meshes\i\in_r_s_int_rail_01.nif -Decoding meshes\i\in_r_s_int_rail_02.nif -Decoding meshes\i\in_r_s_int_arch_01.nif -Decoding meshes\i\in_r_s_int_arch_02.nif -Decoding meshes\i\in_c_stone_corner.nif -Decoding meshes\i\in_c_stone_endcap.nif -Decoding meshes\i\in_dwrv_doorjam00.nif -Decoding meshes\i\in_dwrv_oilslick00.nif -Decoding meshes\i\in_strong_corr2_03.nif -Decoding meshes\i\in_strong_corr2_02.nif -Decoding meshes\i\in_strong_corr2_01.nif -Decoding meshes\i\in_strong_corr2_00.nif -Decoding meshes\i\in_strong_corr2_04.nif -Decoding meshes\i\in_strong_corr4_04.nif -Decoding meshes\i\in_strong_corr4_03.nif -Decoding meshes\i\in_strong_corr4_02.nif -Decoding meshes\i\in_strong_corr4_01.nif -Decoding meshes\i\in_strong_corr4_00.nif -Decoding meshes\i\in_strong_corr1_00.nif -Decoding meshes\i\in_strong_rubble01.nif -Decoding meshes\i\in_strong_rubble00.nif -Decoding meshes\i\in_strong_corr3_02.nif -Decoding meshes\i\in_strong_corr3_01.nif -Decoding meshes\i\in_strong_corr3_00.nif -Decoding meshes\i\in_nord_ladder_02.nif -Decoding meshes\i\in_nord_ladder_01.nif -Decoding meshes\i\in_t_railing_pole.nif -Decoding meshes\i\in_t_root_wrap_02.nif -Decoding meshes\i\in_t_root_wrap_01.nif -Decoding meshes\i\in_t_t_room_center.nif -Decoding meshes\i\in_sewer_bcorr4_00.nif -Decoding meshes\i\in_sewer_scorr4_00.nif -Decoding meshes\i\in_sewer_bcorr2_03.nif -Decoding meshes\i\in_sewer_bcorr2_02.nif -Decoding meshes\i\in_sewer_bcorr2_01.nif -Decoding meshes\i\in_sewer_bcorr2_00.nif -Decoding meshes\i\in_sewer_bcorr2_05.nif -Decoding meshes\i\in_sewer_bcorr2_04.nif -Decoding meshes\i\in_sewer_scorr2_02.nif -Decoding meshes\i\in_sewer_scorr2_01.nif -Decoding meshes\i\in_sewer_scorr2_00.nif -Decoding meshes\i\in_sewer_anchor00.nif -Decoding meshes\i\in_sewer_pillar00.nif -Decoding meshes\i\in_sewer_bcorr3_00.nif -Decoding meshes\i\in_sewer_scorr1_00.nif -Decoding meshes\i\in_sewer_bcorr1_01.nif -Decoding meshes\i\in_sewer_bcorr1_00.nif -Decoding meshes\i\in_vivec_ent_s_01.nif -Decoding meshes\i\in_cavern_stairs00.nif -Decoding meshes\i\in_cavern_roots10.nif -Decoding meshes\i\in_cavern_roots00.nif -Decoding meshes\i\in_lavacave2_s_00.nif -Decoding meshes\i\in_lavacave2_s_01.nif -Decoding meshes\i\in_lavacave2_s_02.nif -Decoding meshes\i\in_lavacave2_s_03.nif -Decoding meshes\i\in_lavacave2_s_04.nif -Decoding meshes\i\in_lavacave2_s_05.nif -Decoding meshes\i\in_lavacave2_s_06.nif -Decoding meshes\i\in_lavacave_form20.nif -Decoding meshes\i\in_lavacave_form30.nif -Decoding meshes\i\in_lavacave_form00.nif -Decoding meshes\i\in_lavacave_form10.nif -Decoding meshes\i\in_lavacave_exit00.nif -Decoding meshes\i\in_lavacave2_s_end.nif -Decoding meshes\i\in_lavacave4_s_00.nif -Decoding meshes\i\in_lavacave_stal00.nif -Decoding meshes\i\in_lavacave_stal10.nif -Decoding meshes\i\in_lavacave_stal20.nif -Decoding meshes\i\in_lavacave_stal30.nif -Decoding meshes\i\in_lavacave_stal40.nif -Decoding meshes\i\in_lavacave_stal50.nif -Decoding meshes\i\in_pycave2_s_end.nif -Decoding meshes\i\in_pycave_stal10.nif -Decoding meshes\i\in_pycave_stal00.nif -Decoding meshes\i\in_pycave2_s_05.nif -Decoding meshes\i\in_pycave2_s_03.nif -Decoding meshes\i\in_pycave2_s_01.nif -Decoding meshes\i\in_pycave_stal30.nif -Decoding meshes\i\in_pycave_stal20.nif -Decoding meshes\i\in_pycave_stal50.nif -Decoding meshes\i\in_pycave_stal40.nif -Decoding meshes\i\in_pycave_exit00.nif -Decoding meshes\i\in_pycave4_s_00.nif -Decoding meshes\i\in_pycave2_s_06.nif -Decoding meshes\i\in_pycave2_s_04.nif -Decoding meshes\i\in_pycave2_s_02.nif -Decoding meshes\i\in_pycave2_s_00.nif -Decoding meshes\i\in_pycave_form00.nif -Decoding meshes\i\in_pycave_form10.nif -Decoding meshes\i\in_pycave_form20.nif -Decoding meshes\i\in_pycave_form30.nif -Decoding meshes\i\in_dwrv_scope50.nif -Decoding meshes\i\in_dwrv_shaft10.nif -Decoding meshes\i\in_dwrv_shaft00.nif -Decoding meshes\i\in_dwrv_crank00.nif -Decoding meshes\i\in_dwrv_scope20.nif -Decoding meshes\i\in_dwrv_obsrv10.nif -Decoding meshes\i\in_dwrv_corr4_01.nif -Decoding meshes\i\in_dwrv_corr4_00.nif -Decoding meshes\i\in_dwrv_corr4_03.nif -Decoding meshes\i\in_dwrv_corr4_02.nif -Decoding meshes\i\in_dwrv_corr4_05.nif -Decoding meshes\i\in_dwrv_corr4_04.nif -Decoding meshes\i\in_dwrv_corr2_01.nif -Decoding meshes\i\in_dwrv_corr2_00.nif -Decoding meshes\i\in_dwrv_corr2_03.nif -Decoding meshes\i\in_dwrv_corr2_02.nif -Decoding meshes\i\in_dwrv_corr2_05.nif -Decoding meshes\i\in_dwrv_corr2_04.nif -Decoding meshes\i\in_dwrv_corr2_07.nif -Decoding meshes\i\in_dwrv_corr2_06.nif -Decoding meshes\i\in_dwrv_corr3_01.nif -Decoding meshes\i\in_dwrv_corr3_00.nif -Decoding meshes\i\in_dwrv_corr3_02.nif -Decoding meshes\i\in_dwrv_corr1_00.nif -Decoding meshes\i\in_dwrv_corr2_30.nif -Decoding meshes\i\in_dwrv_scope40.nif -Decoding meshes\i\in_dwrv_hall4_03.nif -Decoding meshes\i\in_dwrv_hall4_02.nif -Decoding meshes\i\in_dwrv_hall4_01.nif -Decoding meshes\i\in_dwrv_hall4_00.nif -Decoding meshes\i\in_dwrv_hall2_00.nif -Decoding meshes\i\in_dwrv_hall3_02.nif -Decoding meshes\i\in_dwrv_hall3_00.nif -Decoding meshes\i\in_dwrv_scope30.nif -Decoding meshes\i\in_dwrv_scope00.nif -Decoding meshes\i\in_dwrv_obsrv00.nif -Decoding meshes\i\in_dwrv_scope10.nif -Decoding meshes\i\in_mudcave2_s_00.nif -Decoding meshes\i\in_mudcave2_s_01.nif -Decoding meshes\i\in_mudcave2_s_02.nif -Decoding meshes\i\in_mudcave2_s_03.nif -Decoding meshes\i\in_mudcave2_s_04.nif -Decoding meshes\i\in_mudcave2_s_05.nif -Decoding meshes\i\in_mudcave2_s_06.nif -Decoding meshes\i\in_mudcave4_s_00.nif -Decoding meshes\i\in_mudboulder04.nif -Decoding meshes\i\in_mudboulder00.nif -Decoding meshes\i\in_mudboulder02.nif -Decoding meshes\i\in_mudboulder05.nif -Decoding meshes\i\in_mudboulder01.nif -Decoding meshes\i\in_mudboulder03.nif -Decoding meshes\i\in_mudcave2_end.nif -Decoding meshes\i\in_mudcave_28_1.nif -Decoding meshes\i\in_mudcave_21_1.nif -Decoding meshes\i\in_strong_ramp10.nif -Decoding meshes\i\in_strong_ramp00.nif -Decoding meshes\i\in_strong_hall02.nif -Decoding meshes\i\in_strong_hall03.nif -Decoding meshes\i\in_strong_hall00.nif -Decoding meshes\i\in_strong_hall01.nif -Decoding meshes\i\in_strong_hall06.nif -Decoding meshes\i\in_strong_hall04.nif -Decoding meshes\i\in_strong_hall05.nif -Decoding meshes\i\in_vs_pitct_b_01.nif -Decoding meshes\i\in_vs_pitd_b_02.nif -Decoding meshes\i\in_vs_pittw_b_01.nif -Decoding meshes\i\in_vs_pitmw_b_01.nif -Decoding meshes\i\in_vs_pitb_b_01.nif -Decoding meshes\i\in_vs_pitbw_b_01.nif -Decoding meshes\i\in_vs_pitceil_01.nif -Decoding meshes\i\in_vs_pitcb_b_01.nif -Decoding meshes\i\in_vs_pitd_b_01.nif -Decoding meshes\i\in_vs_pitd_b_03.nif -Decoding meshes\i\in_vs_pittd_b_01.nif -Decoding meshes\i\in_vs_pitcm_b_01.nif -Decoding meshes\i\in_vsl_pitc_b_01.nif -Decoding meshes\i\in_ashl_door_01.nif -Decoding meshes\i\in_ashl_tent_05.nif -Decoding meshes\i\in_ashl_tent_03.nif -Decoding meshes\i\in_ashl_tent_01.nif -Decoding meshes\i\in_ashl_door_02.nif -Decoding meshes\i\in_ashl_tent_04.nif -Decoding meshes\i\in_ashl_tent_02.nif -Decoding meshes\i\in_ar_bridge_10.nif -Decoding meshes\i\in_ar_bridge_09.nif -Decoding meshes\i\in_ar_bridge_01.nif -Decoding meshes\i\in_ar_bridge_03.nif -Decoding meshes\i\in_ar_bridge_05.nif -Decoding meshes\i\in_ar_bridge_07.nif -Decoding meshes\i\in_ar_bridge_08.nif -Decoding meshes\i\in_ar_bridge_02.nif -Decoding meshes\i\in_ar_bridge_04.nif -Decoding meshes\i\in_ar_bridge_06.nif -Decoding meshes\i\in_bone_rock_25.nif -Decoding meshes\i\in_bone_rock_27.nif -Decoding meshes\i\in_bone_rock_21.nif -Decoding meshes\i\in_bone_rock_23.nif -Decoding meshes\i\in_bonecave2_11.nif -Decoding meshes\i\in_bonecave2_end.nif -Decoding meshes\i\in_bone_rock_18.nif -Decoding meshes\i\in_bone_rock_12.nif -Decoding meshes\i\in_bone_rock_10.nif -Decoding meshes\i\in_bone_rock_16.nif -Decoding meshes\i\in_bone_rock_14.nif -Decoding meshes\i\in_bonecave4_00.nif -Decoding meshes\i\in_bonecave2_02.nif -Decoding meshes\i\in_bonecave2_00.nif -Decoding meshes\i\in_bonecave2_06.nif -Decoding meshes\i\in_bonecave2_04.nif -Decoding meshes\i\in_bonecave2_08.nif -Decoding meshes\i\in_bone_rock_05.nif -Decoding meshes\i\in_bone_rock_07.nif -Decoding meshes\i\in_bone_rock_01.nif -Decoding meshes\i\in_bone_rock_03.nif -Decoding meshes\i\in_bone_rock_09.nif -Decoding meshes\i\in_boneboulder01.nif -Decoding meshes\i\in_boneboulder00.nif -Decoding meshes\i\in_boneboulder03.nif -Decoding meshes\i\in_boneboulder02.nif -Decoding meshes\i\in_boneboulder05.nif -Decoding meshes\i\in_boneboulder04.nif -Decoding meshes\i\in_bonetrans_00.nif -Decoding meshes\i\in_bone_rock_24.nif -Decoding meshes\i\in_bone_rock_26.nif -Decoding meshes\i\in_bone_rock_20.nif -Decoding meshes\i\in_bone_rock_22.nif -Decoding meshes\i\in_bone_rock_28.nif -Decoding meshes\i\in_bonecave_28_1.nif -Decoding meshes\i\in_bonecave2_10.nif -Decoding meshes\i\in_bonecave2_12.nif -Decoding meshes\i\in_bonecave_21_1.nif -Decoding meshes\i\in_bone_rock_19.nif -Decoding meshes\i\in_bone_rock_13.nif -Decoding meshes\i\in_bone_rock_11.nif -Decoding meshes\i\in_bone_rock_17.nif -Decoding meshes\i\in_bone_rock_15.nif -Decoding meshes\i\in_bonecave2_03.nif -Decoding meshes\i\in_bonecave2_01.nif -Decoding meshes\i\in_bonecave2_07.nif -Decoding meshes\i\in_bonecave2_05.nif -Decoding meshes\i\in_bonecave2_09.nif -Decoding meshes\i\in_bone_rock_04.nif -Decoding meshes\i\in_bone_rock_06.nif -Decoding meshes\i\in_bone_rock_02.nif -Decoding meshes\i\in_bone_rock_08.nif -Decoding meshes\i\in_moldcave_21_1.nif -Decoding meshes\i\in_mold_rock_25.nif -Decoding meshes\i\in_mold_rock_27.nif -Decoding meshes\i\in_mold_rock_21.nif -Decoding meshes\i\in_mold_rock_23.nif -Decoding meshes\i\in_mold_rock_19.nif -Decoding meshes\i\in_mold_rock_13.nif -Decoding meshes\i\in_mold_rock_11.nif -Decoding meshes\i\in_mold_rock_17.nif -Decoding meshes\i\in_mold_rock_15.nif -Decoding meshes\i\in_moldcave2_02.nif -Decoding meshes\i\in_moldcave2_00.nif -Decoding meshes\i\in_moldcave2_06.nif -Decoding meshes\i\in_moldcave2_04.nif -Decoding meshes\i\in_moldcave2_08.nif -Decoding meshes\i\in_moldcave4_00.nif -Decoding meshes\i\in_mold_rock_07.nif -Decoding meshes\i\in_mold_rock_05.nif -Decoding meshes\i\in_mold_rock_03.nif -Decoding meshes\i\in_mold_rock_01.nif -Decoding meshes\i\in_mold_rock_09.nif -Decoding meshes\i\in_moldtrans_00.nif -Decoding meshes\i\in_moldboulder05.nif -Decoding meshes\i\in_moldboulder04.nif -Decoding meshes\i\in_moldboulder03.nif -Decoding meshes\i\in_moldboulder02.nif -Decoding meshes\i\in_moldboulder01.nif -Decoding meshes\i\in_moldboulder00.nif -Decoding meshes\i\in_mold_rock_24.nif -Decoding meshes\i\in_mold_rock_26.nif -Decoding meshes\i\in_mold_rock_20.nif -Decoding meshes\i\in_mold_rock_22.nif -Decoding meshes\i\in_mold_rock_28.nif -Decoding meshes\i\in_moldcave2_end.nif -Decoding meshes\i\in_mold_rock_18.nif -Decoding meshes\i\in_mold_rock_12.nif -Decoding meshes\i\in_mold_rock_10.nif -Decoding meshes\i\in_mold_rock_16.nif -Decoding meshes\i\in_mold_rock_14.nif -Decoding meshes\i\in_moldcave2_03.nif -Decoding meshes\i\in_moldcave2_01.nif -Decoding meshes\i\in_moldcave2_07.nif -Decoding meshes\i\in_moldcave2_05.nif -Decoding meshes\i\in_moldcave2_09.nif -Decoding meshes\i\in_moldcave_28_1.nif -Decoding meshes\i\in_mold_rock_06.nif -Decoding meshes\i\in_mold_rock_04.nif -Decoding meshes\i\in_mold_rock_02.nif -Decoding meshes\i\in_mold_rock_08.nif -Decoding meshes\i\in_nord_house_03.nif -Decoding meshes\i\in_nord_house_02.nif -Decoding meshes\i\in_nord_house_01.nif -Decoding meshes\i\in_nord_house_05.nif -Decoding meshes\i\in_nord_house_04.nif -Decoding meshes\i\in_nord_doorf_01.nif -Decoding meshes\i\in_nord_doorf_02.nif -Decoding meshes\i\in_shipwreck_top.nif -Decoding meshes\i\in_sewer_canal00.nif -Decoding meshes\i\in_sewer_raft00.nif -Decoding meshes\i\in_sewer_union00.nif -Decoding meshes\i\in_de_shipcabin.nif -Decoding meshes\i\in_de_shack_door.nif -Decoding meshes\i\in_dae_hall_l_03.nif -Decoding meshes\i\in_dae_hall_l_02.nif -Decoding meshes\i\in_dae_hall_l_01.nif -Decoding meshes\i\in_cavern_ramp10.nif -Decoding meshes\i\in_cavern_ramp00.nif -Decoding meshes\i\in_cave_plant00.nif -Decoding meshes\i\in_cavern_beam40.nif -Decoding meshes\i\in_cavern_beam00.nif -Decoding meshes\i\in_cavern_beam10.nif -Decoding meshes\i\in_cavern_beam20.nif -Decoding meshes\i\in_cavern_beam30.nif -Decoding meshes\i\in_cave_plant10.nif -Decoding meshes\i\in_cavern_rail00.nif -Decoding meshes\i\in_cavern_rail10.nif -Decoding meshes\i\in_lava_rock_23.nif -Decoding meshes\i\in_lava_rock_21.nif -Decoding meshes\i\in_lava_rock_27.nif -Decoding meshes\i\in_lava_rock_25.nif -Decoding meshes\i\in_lavacave_21_1.nif -Decoding meshes\i\in_lavatrans_00.nif -Decoding meshes\i\in_lavacave2_end.nif -Decoding meshes\i\in_lava_rock_16.nif -Decoding meshes\i\in_lava_rock_14.nif -Decoding meshes\i\in_lava_rock_12.nif -Decoding meshes\i\in_lava_rock_10.nif -Decoding meshes\i\in_lava_rock_18.nif -Decoding meshes\i\in_lavacave2_05.nif -Decoding meshes\i\in_lavacave2_07.nif -Decoding meshes\i\in_lavacave2_01.nif -Decoding meshes\i\in_lavacave2_03.nif -Decoding meshes\i\in_lavacave2_09.nif -Decoding meshes\i\in_lava_rock_09.nif -Decoding meshes\i\in_lava_rock_05.nif -Decoding meshes\i\in_lava_rock_07.nif -Decoding meshes\i\in_lava_rock_01.nif -Decoding meshes\i\in_lava_rock_03.nif -Decoding meshes\i\in_lava_rock_28.nif -Decoding meshes\i\in_lava_rock_22.nif -Decoding meshes\i\in_lava_rock_20.nif -Decoding meshes\i\in_lava_rock_26.nif -Decoding meshes\i\in_lava_rock_24.nif -Decoding meshes\i\in_lavacave_28_1.nif -Decoding meshes\i\in_lava_1024_01.nif -Decoding meshes\i\in_lavaboulder02.nif -Decoding meshes\i\in_lavaboulder03.nif -Decoding meshes\i\in_lavaboulder00.nif -Decoding meshes\i\in_lavaboulder01.nif -Decoding meshes\i\in_lavaboulder04.nif -Decoding meshes\i\in_lavaboulder05.nif -Decoding meshes\i\in_lava_rock_17.nif -Decoding meshes\i\in_lava_rock_15.nif -Decoding meshes\i\in_lava_rock_13.nif -Decoding meshes\i\in_lava_rock_11.nif -Decoding meshes\i\in_lava_rock_19.nif -Decoding meshes\i\in_lavacave4_00.nif -Decoding meshes\i\in_lavacave2_04.nif -Decoding meshes\i\in_lavacave2_06.nif -Decoding meshes\i\in_lavacave2_00.nif -Decoding meshes\i\in_lavacave2_02.nif -Decoding meshes\i\in_lavacave2_08.nif -Decoding meshes\i\in_lava_rock_08.nif -Decoding meshes\i\in_lava_rock_04.nif -Decoding meshes\i\in_lava_rock_06.nif -Decoding meshes\i\in_lava_rock_02.nif -Decoding meshes\i\in_t_crystal_02.nif -Decoding meshes\i\in_t_s_shaft_01.nif -Decoding meshes\i\in_t_l_hall_3way.nif -Decoding meshes\i\in_t_housepod_01.nif -Decoding meshes\i\in_t_housepod_02.nif -Decoding meshes\i\in_t_s_pillar_04.nif -Decoding meshes\i\in_t_s_pillar_02.nif -Decoding meshes\i\in_t_s_hall_4way.nif -Decoding meshes\i\in_t_s_hall_3way.nif -Decoding meshes\i\in_t_councilhall.nif -Decoding meshes\i\in_t_gatesymbol.nif -Decoding meshes\i\in_t_l_pillar_01.nif -Decoding meshes\i\in_t_crystal_01.nif -Decoding meshes\i\in_t_l_room_side.nif -Decoding meshes\i\in_t_platform_01.nif -Decoding meshes\i\in_t_platform_02.nif -Decoding meshes\i\in_t_s_turret_03.nif -Decoding meshes\i\in_t_s_turret_02.nif -Decoding meshes\i\in_t_sidewall_01.nif -Decoding meshes\i\in_t_sidewall_02.nif -Decoding meshes\i\in_v_roomhol_01.nif -Decoding meshes\i\in_r_building_01.nif -Decoding meshes\i\in_c_stone_4way.nif -Decoding meshes\i\in_c_stone_3way.nif -Decoding meshes\i\in_c_railing_01.nif -Decoding meshes\i\in_c_wall_plain.nif -Decoding meshes\i\in_c_plain_4way.nif -Decoding meshes\i\in_c_plain_3way.nif -Decoding meshes\i\in_c_pillar_wood.nif -Decoding meshes\i\in_t_l_hall_01.nif -Decoding meshes\i\in_t_s_hall_01.nif -Decoding meshes\i\in_t_gasket_01.nif -Decoding meshes\i\in_t_manor_01.nif -Decoding meshes\i\in_t_manor_02.nif -Decoding meshes\i\in_t_l_door_01.nif -Decoding meshes\i\in_v_palace_01.nif -Decoding meshes\i\in_v_plaza_01.nif -Decoding meshes\i\in_v_roomhf_01.nif -Decoding meshes\i\in_v_arena_01.nif -Decoding meshes\i\in_v_plaza_02.nif -Decoding meshes\i\in_pytrans_00.nif -Decoding meshes\i\in_py_rock_15.nif -Decoding meshes\i\in_pycave_28_1.nif -Decoding meshes\i\in_pycave_21_1.nif -Decoding meshes\i\in_py_rock_19.nif -Decoding meshes\i\in_py_rock_09.nif -Decoding meshes\i\in_pycave2_06.nif -Decoding meshes\i\in_py_rock_02.nif -Decoding meshes\i\in_py_rock_12.nif -Decoding meshes\i\in_py_rock_22.nif -Decoding meshes\i\in_pycave2_00.nif -Decoding meshes\i\in_pycave4_00.nif -Decoding meshes\i\in_py_rock_05.nif -Decoding meshes\i\in_py_rock_23.nif -Decoding meshes\i\in_py_rock_03.nif -Decoding meshes\i\in_pycave2_01.nif -Decoding meshes\i\in_py_rock_06.nif -Decoding meshes\i\in_pycave2_07.nif -Decoding meshes\i\in_py_rock_16.nif -Decoding meshes\i\in_py_rock_20.nif -Decoding meshes\i\in_py_rock_10.nif -Decoding meshes\i\in_pycave2_04.nif -Decoding meshes\i\in_py_rock_26.nif -Decoding meshes\i\in_py_rock_27.nif -Decoding meshes\i\in_pyboulder04.nif -Decoding meshes\i\in_pyboulder00.nif -Decoding meshes\i\in_pyboulder02.nif -Decoding meshes\i\in_py_rock_25.nif -Decoding meshes\i\in_pycave2_03.nif -Decoding meshes\i\in_py_rock_07.nif -Decoding meshes\i\in_pycave2_02.nif -Decoding meshes\i\in_py_rock_24.nif -Decoding meshes\i\in_py_rock_17.nif -Decoding meshes\i\in_prison_ship.nif -Decoding meshes\i\in_pycave2_09.nif -Decoding meshes\i\in_py_rock_14.nif -Decoding meshes\i\in_py_rock_13.nif -Decoding meshes\i\in_pycave2_05.nif -Decoding meshes\i\in_pyboulder05.nif -Decoding meshes\i\in_pyboulder01.nif -Decoding meshes\i\in_pyboulder03.nif -Decoding meshes\i\in_py_rock_28.nif -Decoding meshes\i\in_py_rock_18.nif -Decoding meshes\i\in_pycave2_end.nif -Decoding meshes\i\in_py_rock_08.nif -Decoding meshes\i\in_py_rock_04.nif -Decoding meshes\i\in_pycave2_08.nif -Decoding meshes\i\in_py_rock_11.nif -Decoding meshes\i\in_py_rock_01.nif -Decoding meshes\i\in_py_rock_21.nif -Decoding meshes\i\in_r_s_room_02.nif -Decoding meshes\i\in_r_s_room_01.nif -Decoding meshes\i\in_r_s_room_03.nif -Decoding meshes\i\in_dwrv_gear10.nif -Decoding meshes\i\in_de_shack_05.nif -Decoding meshes\i\in_de_shack_03.nif -Decoding meshes\i\in_de_shack_01.nif -Decoding meshes\i\in_dwrv_gear00.nif -Decoding meshes\i\in_dwrv_wall10.nif -Decoding meshes\i\in_dwrv_lift00.nif -Decoding meshes\i\in_de_shack_04.nif -Decoding meshes\i\in_de_shack_02.nif -Decoding meshes\i\in_dwrv_wall00.nif -Decoding meshes\i\in_dwrv_gear20.nif -Decoding meshes\i\in_dae_claw_01.nif -Decoding meshes\i\in_dwrv_gear30.nif -Decoding meshes\i\in_akulacave00.nif -Decoding meshes\i\in_akulakhan00.nif -Decoding meshes\i\in_bonecave_17.nif -Decoding meshes\i\in_bonecave_15.nif -Decoding meshes\i\in_bonecave_13.nif -Decoding meshes\i\in_bonecave_11.nif -Decoding meshes\i\in_bonecave_19.nif -Decoding meshes\i\in_bonecave_21.nif -Decoding meshes\i\in_bonecave_27.nif -Decoding meshes\i\in_bonecave_25.nif -Decoding meshes\i\in_bonecave_08.nif -Decoding meshes\i\in_bonecave_06.nif -Decoding meshes\i\in_bonecave_04.nif -Decoding meshes\i\in_bonecave_02.nif -Decoding meshes\i\in_bonecave_00.nif -Decoding meshes\i\in_bonecave_16.nif -Decoding meshes\i\in_bonecave_14.nif -Decoding meshes\i\in_bonecave_12.nif -Decoding meshes\i\in_bonecave_10.nif -Decoding meshes\i\in_bonecave_18.nif -Decoding meshes\i\in_bonecave_26.nif -Decoding meshes\i\in_bonecave_09.nif -Decoding meshes\i\in_bonecave_07.nif -Decoding meshes\i\in_bonecave_05.nif -Decoding meshes\i\in_bonecave_03.nif -Decoding meshes\i\in_bonecave_01.nif -Decoding meshes\i\in_ci_mummy_04.nif -Decoding meshes\i\in_ci_mummy_06.nif -Decoding meshes\i\in_ci_mummy_02.nif -Decoding meshes\i\in_ci_azura_01.nif -Decoding meshes\i\in_c_wall_rich.nif -Decoding meshes\i\in_ci_mummy_05.nif -Decoding meshes\i\in_ci_mummy_01.nif -Decoding meshes\i\in_ci_mummy_03.nif -Decoding meshes\i\in_lavacave_17.nif -Decoding meshes\i\in_lavacave_15.nif -Decoding meshes\i\in_lavacave_13.nif -Decoding meshes\i\in_lavacave_11.nif -Decoding meshes\i\in_lavacave_19.nif -Decoding meshes\i\in_lavalayer00.nif -Decoding meshes\i\in_lavacave_08.nif -Decoding meshes\i\in_lavacave_06.nif -Decoding meshes\i\in_lavacave_04.nif -Decoding meshes\i\in_lavacave_02.nif -Decoding meshes\i\in_lavacave_00.nif -Decoding meshes\i\in_lavacave_21.nif -Decoding meshes\i\in_lavacave_27.nif -Decoding meshes\i\in_lavacave_25.nif -Decoding meshes\i\in_lavacave_16.nif -Decoding meshes\i\in_lavacave_14.nif -Decoding meshes\i\in_lavacave_12.nif -Decoding meshes\i\in_lavacave_10.nif -Decoding meshes\i\in_lavacave_18.nif -Decoding meshes\i\in_lavacave_09.nif -Decoding meshes\i\in_lavacave_07.nif -Decoding meshes\i\in_lavacave_05.nif -Decoding meshes\i\in_lavacave_03.nif -Decoding meshes\i\in_lavacave_01.nif -Decoding meshes\i\in_lavacave_26.nif -Decoding meshes\i\in_mudcave_14.nif -Decoding meshes\i\in_mudcave_07.nif -Decoding meshes\i\in_mudcave_17.nif -Decoding meshes\i\in_mudtrans_00.nif -Decoding meshes\i\in_mudcave_27.nif -Decoding meshes\i\in_moldcave_17.nif -Decoding meshes\i\in_moldcave_15.nif -Decoding meshes\i\in_moldcave_13.nif -Decoding meshes\i\in_moldcave_11.nif -Decoding meshes\i\in_moldcave_19.nif -Decoding meshes\i\in_mudcave_10.nif -Decoding meshes\i\in_mudcave_00.nif -Decoding meshes\i\in_mudcave_03.nif -Decoding meshes\i\in_mudcave2_08.nif -Decoding meshes\i\in_mudcave2_04.nif -Decoding meshes\i\in_mudcave2_06.nif -Decoding meshes\i\in_mudcave2_00.nif -Decoding meshes\i\in_mudcave2_02.nif -Decoding meshes\i\in_mudcave4_00.nif -Decoding meshes\i\in_mud_rock_21.nif -Decoding meshes\i\in_mud_rock_23.nif -Decoding meshes\i\in_mud_rock_25.nif -Decoding meshes\i\in_mud_rock_27.nif -Decoding meshes\i\in_mudcave_04.nif -Decoding meshes\i\in_mudcave_21.nif -Decoding meshes\i\in_mudcave_11.nif -Decoding meshes\i\in_mudcave_01.nif -Decoding meshes\i\in_mudcave_18.nif -Decoding meshes\i\in_mudcave_08.nif -Decoding meshes\i\in_mud_rock_13.nif -Decoding meshes\i\in_mud_rock_11.nif -Decoding meshes\i\in_mud_rock_17.nif -Decoding meshes\i\in_mud_rock_15.nif -Decoding meshes\i\in_mud_rock_19.nif -Decoding meshes\i\in_moldcave_21.nif -Decoding meshes\i\in_moldcave_27.nif -Decoding meshes\i\in_moldcave_25.nif -Decoding meshes\i\in_moldcave_08.nif -Decoding meshes\i\in_moldcave_06.nif -Decoding meshes\i\in_moldcave_04.nif -Decoding meshes\i\in_moldcave_02.nif -Decoding meshes\i\in_moldcave_00.nif -Decoding meshes\i\in_mud_rock_07.nif -Decoding meshes\i\in_mud_rock_05.nif -Decoding meshes\i\in_mud_rock_03.nif -Decoding meshes\i\in_mud_rock_01.nif -Decoding meshes\i\in_mud_rock_09.nif -Decoding meshes\i\in_mudcave_26.nif -Decoding meshes\i\in_moldcave_16.nif -Decoding meshes\i\in_moldcave_14.nif -Decoding meshes\i\in_moldcave_12.nif -Decoding meshes\i\in_moldcave_10.nif -Decoding meshes\i\in_moldcave_18.nif -Decoding meshes\i\in_mudcave_13.nif -Decoding meshes\i\in_mudcave_06.nif -Decoding meshes\i\in_mudcave_25.nif -Decoding meshes\i\in_mudcave2_09.nif -Decoding meshes\i\in_mudcave2_05.nif -Decoding meshes\i\in_mudcave2_07.nif -Decoding meshes\i\in_mudcave2_01.nif -Decoding meshes\i\in_mudcave2_03.nif -Decoding meshes\i\in_mudcave_16.nif -Decoding meshes\i\in_mud_rock_20.nif -Decoding meshes\i\in_mud_rock_22.nif -Decoding meshes\i\in_mud_rock_24.nif -Decoding meshes\i\in_mud_rock_26.nif -Decoding meshes\i\in_mud_rock_28.nif -Decoding meshes\i\in_mudcave_15.nif -Decoding meshes\i\in_mud_rock_12.nif -Decoding meshes\i\in_mud_rock_10.nif -Decoding meshes\i\in_mud_rock_16.nif -Decoding meshes\i\in_mud_rock_14.nif -Decoding meshes\i\in_mud_rock_18.nif -Decoding meshes\i\in_moldcave_26.nif -Decoding meshes\i\in_moldcave_09.nif -Decoding meshes\i\in_moldcave_07.nif -Decoding meshes\i\in_moldcave_05.nif -Decoding meshes\i\in_moldcave_03.nif -Decoding meshes\i\in_moldcave_01.nif -Decoding meshes\i\in_mud_rock_06.nif -Decoding meshes\i\in_mud_rock_04.nif -Decoding meshes\i\in_mud_rock_02.nif -Decoding meshes\i\in_mud_rock_08.nif -Decoding meshes\i\in_mudcave_05.nif -Decoding meshes\i\in_mudcave_02.nif -Decoding meshes\i\in_mudcave_12.nif -Decoding meshes\i\in_mudcave_19.nif -Decoding meshes\i\in_mudcave_09.nif -Decoding meshes\i\in_hlaalu_door.nif -Decoding meshes\i\in_hlaalu_wall.nif -Decoding meshes\i\in_icesheet_01.nif -Decoding meshes\i\in_icesheet_02.nif -Decoding meshes\i\in_pycave_15.nif -Decoding meshes\i\in_pycave_02.nif -Decoding meshes\i\in_pycave_12.nif -Decoding meshes\i\in_lava_256a.nif -Decoding meshes\i\in_pycave_05.nif -Decoding meshes\i\in_lava_oval.nif -Decoding meshes\i\in_pycave_06.nif -Decoding meshes\i\in_pycave_03.nif -Decoding meshes\i\in_pycave_16.nif -Decoding meshes\i\in_pycave_19.nif -Decoding meshes\i\in_pycave_26.nif -Decoding meshes\i\in_pycave_09.nif -Decoding meshes\i\in_pycave_27.nif -Decoding meshes\i\in_pycave_18.nif -Decoding meshes\i\in_pycave_25.nif -Decoding meshes\i\in_pycave_08.nif -Decoding meshes\i\in_pycave_10.nif -Decoding meshes\i\in_pycave_00.nif -Decoding meshes\i\in_pycave_07.nif -Decoding meshes\i\in_pycave_17.nif -Decoding meshes\i\in_lava_256.nif -Decoding meshes\i\in_t_l_4way.nif -Decoding meshes\i\in_t_edge_01.nif -Decoding meshes\i\in_pycave_14.nif -Decoding meshes\i\in_lava_512.nif -Decoding meshes\i\in_pycave_13.nif -Decoding meshes\i\in_lava_1024.nif -Decoding meshes\i\in_pycave_11.nif -Decoding meshes\i\in_pycave_01.nif -Decoding meshes\i\in_pycave_21.nif -Decoding meshes\i\in_pycave_04.nif -Decoding meshes\i\in_c_skywalk.nif -Decoding meshes\i\in_6th_chalk30.nif -Decoding meshes\i\in_6th_chalk20.nif -Decoding meshes\i\in_6th_chalk00.nif -Decoding meshes\i\in_6th_chalk10.nif -Decoding meshes\i\tx_crystal_03.nif -Decoding meshes\i\tx_crystal_02.nif -Decoding meshes\i\ex_dae_ruin_04.nif -Decoding meshes\i\ex_dae_ruin_02.nif -Decoding meshes\a\a_imperial_f_shoe.nif -Decoding meshes\a\a_imperial_a_boot.nif -Decoding meshes\a\a_iron_cuirass_gnd.nif -Decoding meshes\a\a_iron_pauldron_fa.nif -Decoding meshes\a\a_iron_pauldron_ua.nif -Decoding meshes\a\a_iron_pauldron_cl.nif -Decoding meshes\a\a_iron_greaves_gnd.nif -Decoding meshes\a\a_iron_greaves_ul.nif -Decoding meshes\a\a_indoril_m_helmet.nif -Decoding meshes\a\a_indoril_m_f_boot.nif -Decoding meshes\a\a_indoril_m_a_boot.nif -Decoding meshes\a\a_indoril_m_skins.nif -Decoding meshes\a\shield_art_auriel.nif -Decoding meshes\marker_creature.nif -Decoding meshes\marker_travel.nif -Decoding meshes\marker_temple.nif -Decoding meshes\marker_prison.nif -Decoding meshes\marker_radius.nif -Decoding meshes\marker_divine.nif -Decoding meshes\m\skeleton_key.nif -Decoding meshes\a\shield_bonemold.nif -Decoding meshes\a\shield_dwemer.nif -Decoding meshes\a\shield_daedric.nif -Decoding meshes\a\shield_dreugh.nif -Decoding meshes\a\shield_indoril.nif -Decoding meshes\a\shield_chitin.nif -Decoding meshes\a\shield_ebony.nif -Decoding meshes\a\shield_steel.nif -Decoding meshes\a\shield_glass.nif -Decoding meshes\a\shield_iron.nif -Decoding meshes\a\a_imperial_skins.nif -Decoding meshes\a\a_imperial_skirt.nif -Decoding meshes\a\a_iron_boot_gnd.nif -Decoding meshes\a\a_iron_greaves_g.nif -Decoding meshes\a\a_iron_greaves_k.nif -Decoding meshes\a\a_iron_bracer_w.nif -Decoding meshes\a\a_iron_hands.1st.nif -Decoding meshes\a\a_iron_boot_f.nif -Decoding meshes\a\a_iron_skinned.nif -Decoding meshes\a\a_iron_boot_a.nif -Decoding meshes\a\a_iron_helm_01.nif -Decoding meshes\f\flora_bc_grass_02.nif -Decoding meshes\f\flora_bc_grass_01.nif -Decoding meshes\f\flora_comberry_01.nif -Decoding meshes\f\flora_chokeweed_01.nif -Decoding meshes\f\flora_roobrush_01.nif -Decoding meshes\f\flora_corkbulb_01.nif -Decoding meshes\n\ingred_emerald_01.nif -Decoding meshes\n\ingred_comberry_01.nif -Decoding meshes\n\ingred_ash_yam_01.nif -Decoding meshes\n\ingred_russula_01.nif -Decoding meshes\n\ingred_coprinus_01.nif -Decoding meshes\n\ingred_scuttle_01.nif -Decoding meshes\n\ingred_saltrice_01.nif -Decoding meshes\n\ingred_roobrush_01.nif -Decoding meshes\n\ingred_heather_01.nif -Decoding meshes\n\ingred_rat_meat_01.nif -Decoding meshes\n\ingred_crabmeat_01.nif -Decoding meshes\n\ingred_rawebony_01.nif -Decoding meshes\n\ingred_diamond_01.nif -Decoding meshes\f\food_kwama_egg_01.nif -Decoding meshes\f\food_kwama_egg_02.nif -Decoding meshes\n\ingred_pearl_01.nif -Decoding meshes\n\ingred_bread_01.nif -Decoding meshes\n\ingred_scales_01.nif -Decoding meshes\n\ingred_resin_01.nif -Decoding meshes\n\ingred_muck_01.nif -Decoding meshes\n\ingred_ruby_01.nif -Decoding meshes\f\flora_root_wg_07.nif -Decoding meshes\f\flora_root_wg_06.nif -Decoding meshes\f\flora_root_wg_05.nif -Decoding meshes\f\flora_root_wg_04.nif -Decoding meshes\f\flora_root_wg_03.nif -Decoding meshes\f\flora_root_wg_02.nif -Decoding meshes\f\flora_root_wg_01.nif -Decoding meshes\f\flora_root_wg_08.nif -Decoding meshes\f\flora_bc_log_02.nif -Decoding meshes\f\flora_muckpod_04.nif -Decoding meshes\f\flora_muckpod_05.nif -Decoding meshes\f\flora_muckpod_02.nif -Decoding meshes\f\flora_muckpod_03.nif -Decoding meshes\f\flora_muckpod_01.nif -Decoding meshes\f\flora_bc_moss_03.nif -Decoding meshes\f\flora_bc_moss_02.nif -Decoding meshes\f\flora_bc_moss_01.nif -Decoding meshes\f\flora_bc_moss_07.nif -Decoding meshes\f\flora_bc_moss_06.nif -Decoding meshes\f\flora_bc_moss_05.nif -Decoding meshes\f\flora_bc_moss_04.nif -Decoding meshes\f\flora_bc_moss_09.nif -Decoding meshes\f\flora_bc_moss_08.nif -Decoding meshes\f\flora_bc_moss_13.nif -Decoding meshes\f\flora_bc_moss_12.nif -Decoding meshes\f\flora_bc_moss_11.nif -Decoding meshes\f\flora_bc_moss_10.nif -Decoding meshes\f\flora_bc_moss_17.nif -Decoding meshes\f\flora_bc_moss_16.nif -Decoding meshes\f\flora_bc_moss_15.nif -Decoding meshes\f\flora_bc_moss_14.nif -Decoding meshes\f\flora_bc_moss_19.nif -Decoding meshes\f\flora_bc_moss_18.nif -Decoding meshes\f\flora_bc_moss_21.nif -Decoding meshes\f\flora_bc_moss_20.nif -Decoding meshes\f\flora_bc_tree_13.nif -Decoding meshes\f\flora_bc_tree_12.nif -Decoding meshes\f\flora_bc_tree_11.nif -Decoding meshes\f\flora_bc_tree_10.nif -Decoding meshes\f\flora_bc_tree_03.nif -Decoding meshes\f\flora_bc_tree_02.nif -Decoding meshes\f\flora_bc_tree_01.nif -Decoding meshes\f\flora_bc_tree_07.nif -Decoding meshes\f\flora_bc_tree_06.nif -Decoding meshes\f\flora_bc_tree_05.nif -Decoding meshes\f\flora_bc_tree_04.nif -Decoding meshes\f\flora_bc_tree_09.nif -Decoding meshes\f\flora_bc_tree_08.nif -Decoding meshes\f\flora_bc_vine_02.nif -Decoding meshes\f\flora_bc_vine_03.nif -Decoding meshes\f\flora_bc_vine_01.nif -Decoding meshes\f\flora_bc_vine_06.nif -Decoding meshes\f\flora_bc_vine_07.nif -Decoding meshes\f\flora_bc_vine_04.nif -Decoding meshes\f\flora_bc_vine_05.nif -Decoding meshes\f\flora_bc_fern_04.nif -Decoding meshes\f\flora_bc_fern_03.nif -Decoding meshes\f\flora_bc_fern_02.nif -Decoding meshes\f\flora_bc_fern_01.nif -Decoding meshes\f\flora_bc_log_01.nif -Decoding meshes\f\flora_tree_gl_11.nif -Decoding meshes\f\flora_tree_gl_10.nif -Decoding meshes\f\flora_tree_ac_04.nif -Decoding meshes\f\flora_tree_ac_01.nif -Decoding meshes\f\flora_tree_ac_03.nif -Decoding meshes\f\flora_tree_ac_02.nif -Decoding meshes\f\flora_tree_wg_08.nif -Decoding meshes\f\flora_tree_wg_01.nif -Decoding meshes\f\flora_tree_wg_03.nif -Decoding meshes\f\flora_tree_wg_02.nif -Decoding meshes\f\flora_tree_wg_05.nif -Decoding meshes\f\flora_tree_wg_04.nif -Decoding meshes\f\flora_tree_wg_07.nif -Decoding meshes\f\flora_tree_wg_06.nif -Decoding meshes\f\flora_tree_ai_05.nif -Decoding meshes\f\flora_tree_ai_06.nif -Decoding meshes\f\flora_tree_gl_09.nif -Decoding meshes\f\flora_tree_gl_08.nif -Decoding meshes\f\flora_tree_gl_01.nif -Decoding meshes\f\flora_tree_gl_03.nif -Decoding meshes\f\flora_tree_gl_02.nif -Decoding meshes\f\flora_tree_gl_05.nif -Decoding meshes\f\flora_tree_gl_04.nif -Decoding meshes\f\flora_tree_gl_07.nif -Decoding meshes\f\flora_tree_gl_06.nif -Decoding meshes\f\flora_heather_01.nif -Decoding meshes\f\flora_bc_knee_04.nif -Decoding meshes\f\flora_bc_knee_01.nif -Decoding meshes\f\flora_bc_knee_03.nif -Decoding meshes\f\flora_bc_knee_02.nif -Decoding meshes\f\flora_tree_01.nif -Decoding meshes\f\flora_kelp_02.nif -Decoding meshes\f\flora_grass_04.nif -Decoding meshes\f\flora_grass_02.nif -Decoding meshes\f\flora_kelp_03.nif -Decoding meshes\f\flora_tree_02.nif -Decoding meshes\f\flora_tree_04.nif -Decoding meshes\f\flora_tree_03.nif -Decoding meshes\f\flora_grass_03.nif -Decoding meshes\f\flora_grass_01.nif -Decoding meshes\f\flora_kelp_01.nif -Decoding meshes\f\flora_bush_01.nif -Decoding meshes\f\flora_kelp_04.nif -Decoding meshes\f\flora_ivy_01.nif -Decoding meshes\f\flora_ivy_02.nif -Decoding meshes\a\a_helm_colovian.nif -Decoding meshes\a\a_helm_silver.nif -Decoding meshes\f\velothi_sewer_door.nif -Decoding meshes\x\terrain_rock_ma_5a.nif -Decoding meshes\x\terrain_rock_ma_39.nif -Decoding meshes\x\terrain_rock_ma_19.nif -Decoding meshes\x\terrain_rock_ma_09.nif -Decoding meshes\x\terrain_rock_ma_79.nif -Decoding meshes\x\terrain_rock_ma_59.nif -Decoding meshes\x\terrain_rock_ma_49.nif -Decoding meshes\x\terrain_rock_ma_58.nif -Decoding meshes\x\terrain_rock_ma_53.nif -Decoding meshes\x\terrain_rock_ma_43.nif -Decoding meshes\x\terrain_rock_ma_02.nif -Decoding meshes\x\terrain_rock_ma_01.nif -Decoding meshes\x\terrain_rock_ma_51.nif -Decoding meshes\x\terrain_rock_ma_10.nif -Decoding meshes\x\terrain_rock_ma_40.nif -Decoding meshes\x\terrain_rock_ma_07.nif -Decoding meshes\x\terrain_rock_ma_67.nif -Decoding meshes\x\terrain_rock_ma_57.nif -Decoding meshes\x\terrain_rock_ma_06.nif -Decoding meshes\x\terrain_rock_ma_56.nif -Decoding meshes\x\terrain_rock_ma_25.nif -Decoding meshes\x\terrain_rock_ma_05.nif -Decoding meshes\x\terrain_rock_ma_55.nif -Decoding meshes\x\terrain_rock_ma_04.nif -Decoding meshes\x\terrain_rock_ma_54.nif -Decoding meshes\x\terrain_ashmire_01.nif -Decoding meshes\x\terrain_ma_rock_51.nif -Decoding meshes\x\terrain_ma_rock_53.nif -Decoding meshes\x\terrain_ma_rock_13.nif -Decoding meshes\x\terrain_rock_ma31.nif -Decoding meshes\x\terrain_rock_ma41.nif -Decoding meshes\x\terrain_ma_rock13.nif -Decoding meshes\x\terrain_ma_bridge.nif -Decoding meshes\x\terrain_ma_rock22.nif -Decoding meshes\x\terrain_ma_rock32.nif -Decoding meshes\x\terrain_ma_rock62.nif -Decoding meshes\x\terrain_lavapot_02.nif -Decoding meshes\x\terrain_lavapot_03.nif -Decoding meshes\x\terrain_lava_vent.nif -Decoding meshes\x\furn_sign_arms_01.nif -Decoding meshes\x\furn_tikitorch_out.nif -Decoding meshes\x\furn_com_fence_02.nif -Decoding meshes\x\furn_com_fence_03.nif -Decoding meshes\x\furn_com_fence_01.nif -Decoding meshes\editormarker_box_01.nif -Decoding meshes\x\terrain_lavapot.nif -Decoding meshes\x\terrain_lava_pot.nif -Decoding meshes\x\terrain_volcano.nif -Decoding meshes\x\terrain_ma_60.nif -Decoding meshes\x\terrain_ma_20.nif -Decoding meshes\x\terrain_ma_30.nif -Decoding meshes\x\furn_sign_inn_01.nif -Decoding meshes\x\furn_flagpole_01.nif -Decoding meshes\x\furn_signbase_01.nif -Decoding meshes\e\frost_shield.nif -Decoding meshes\l\light_com_torch_01.nif -Decoding meshes\l\light_com_torch_02.nif -Decoding meshes\l\light_tikitorch00.nif -Decoding meshes\l\light_de_candle_08.nif -Decoding meshes\l\light_de_candle_18.nif -Decoding meshes\l\light_de_candle_09.nif -Decoding meshes\l\light_de_candle_19.nif -Decoding meshes\l\light_de_candle_24.nif -Decoding meshes\l\light_de_candle_04.nif -Decoding meshes\l\light_de_candle_14.nif -Decoding meshes\l\light_de_candle_25.nif -Decoding meshes\l\light_de_candle_05.nif -Decoding meshes\l\light_de_candle_15.nif -Decoding meshes\l\light_de_candle_26.nif -Decoding meshes\l\light_de_candle_06.nif -Decoding meshes\l\light_de_candle_16.nif -Decoding meshes\l\light_de_candle_07.nif -Decoding meshes\l\light_de_candle_17.nif -Decoding meshes\l\light_de_candle_20.nif -Decoding meshes\l\light_de_candle_10.nif -Decoding meshes\l\light_de_candle_21.nif -Decoding meshes\l\light_de_candle_01.nif -Decoding meshes\l\light_de_candle_11.nif -Decoding meshes\l\light_de_candle_22.nif -Decoding meshes\l\light_de_candle_02.nif -Decoding meshes\l\light_de_candle_12.nif -Decoding meshes\l\light_de_candle_23.nif -Decoding meshes\l\light_de_candle_03.nif -Decoding meshes\l\light_de_candle_13.nif -Decoding meshes\l\light_com_lamp_01.nif -Decoding meshes\l\light_com_lamp_02.nif -Decoding meshes\l\light_redware_lamp.nif -Decoding meshes\l\light_6th_brazier.nif -Decoding meshes\l\light_fire_nosmoke.nif -Decoding meshes\l\light_dwrv_neon00.nif -Decoding meshes\d\door_dwrv_double01.nif -Decoding meshes\d\door_dwrv_double00.nif -Decoding meshes\d\door_dwrv_loadup00.nif -Decoding meshes\d\door_dwrv_inner00.nif -Decoding meshes\d\door_dwrv_load00.nif -Decoding meshes\d\door_dwrv_main00.nif -Decoding meshes\l\light_dae_censer.nif -Decoding meshes\l\light_common_01.nif -Decoding meshes\l\light_logpile10.nif -Decoding meshes\l\light_pitfire00.nif -Decoding meshes\l\light_buglamp_01.nif -Decoding meshes\l\light_de_lamp_01.nif -Decoding meshes\l\light_de_lamp_03.nif -Decoding meshes\l\light_de_lamp_02.nif -Decoding meshes\l\light_de_lamp_05.nif -Decoding meshes\l\light_de_lamp_04.nif -Decoding meshes\l\light_de_lamp_07.nif -Decoding meshes\l\light_de_lamp_06.nif -Decoding meshes\l\light_de_lamp_09.nif -Decoding meshes\l\light_de_lamp_08.nif -Decoding meshes\l\light_dae_jet.nif -Decoding meshes\l\light_stand_01.nif -Decoding meshes\l\light_tikilamp.nif -Decoding meshes\l\light_torch10.nif -Decoding meshes\l\light_sconce10.nif -Decoding meshes\l\light_sconce00.nif -Decoding meshes\l\light_torch_01.nif -Decoding meshes\b\b_n_breton_m_skins.nif -Decoding meshes\b\b_n_breton_f_skins.nif -Decoding meshes\b\b_n_breton_f_ankle.nif -Decoding meshes\b\b_n_breton_m_ankle.nif -Decoding meshes\b\b_n_breton_m_groin.nif -Decoding meshes\b\b_n_breton_f_groin.nif -Decoding meshes\b\b_n_breton_f_wrist.nif -Decoding meshes\b\b_n_breton_m_wrist.nif -Decoding meshes\b\b_n_breton_f_neck.nif -Decoding meshes\b\b_n_breton_m_neck.nif -Decoding meshes\b\b_n_breton_f_foot.nif -Decoding meshes\b\b_n_breton_m_foot.nif -Decoding meshes\b\b_n_breton_f_knee.nif -Decoding meshes\b\b_n_breton_m_knee.nif -Decoding meshes\b\b_n_orc_f_forearm.nif -Decoding meshes\b\b_n_orc_m_forearm.nif -Decoding meshes\b\b_n_orc_m_head_04.nif -Decoding meshes\b\b_n_orc_m_head_01.nif -Decoding meshes\b\b_n_orc_m_head_02.nif -Decoding meshes\b\b_n_orc_m_head_03.nif -Decoding meshes\b\b_n_orc_m_hair_04.nif -Decoding meshes\b\b_n_orc_m_hair_05.nif -Decoding meshes\b\b_n_orc_m_hair_01.nif -Decoding meshes\b\b_n_orc_m_hair_02.nif -Decoding meshes\b\b_n_orc_m_hair_03.nif -Decoding meshes\b\b_n_orc_f_head_01.nif -Decoding meshes\b\b_n_orc_f_head_02.nif -Decoding meshes\b\b_n_orc_f_head_03.nif -Decoding meshes\a\a_m_chitin_a_boot.nif -Decoding meshes\a\a_m_chitin_skinned.nif -Decoding meshes\a\a_m_chitin_forearm.nif -Decoding meshes\a\a_m_chitin_f_boots.nif -Decoding meshes\a\a_m_chitin_helmet.nif -Decoding meshes\b\b_n_khajiit_m_knee.nif -Decoding meshes\b\b_n_khajiit_f_knee.nif -Decoding meshes\b\b_n_khajiit_m_neck.nif -Decoding meshes\b\b_n_khajiit_f_neck.nif -Decoding meshes\b\b_n_nord_m_hair03.nif -Decoding meshes\b\b_n_nord_m_hair02.nif -Decoding meshes\b\b_n_nord_m_hair01.nif -Decoding meshes\b\b_n_nord_m_hair00.nif -Decoding meshes\b\b_n_nord_m_hair07.nif -Decoding meshes\b\b_n_nord_m_hair06.nif -Decoding meshes\b\b_n_nord_m_hair05.nif -Decoding meshes\b\b_n_nord_m_hair04.nif -Decoding meshes\b\b_n_nord_f_head_01.nif -Decoding meshes\b\b_n_nord_m_head_01.nif -Decoding meshes\b\b_n_nord_m_head_02.nif -Decoding meshes\b\b_n_nord_f_head_03.nif -Decoding meshes\b\b_n_nord_m_head_03.nif -Decoding meshes\b\b_n_nord_f_head_02.nif -Decoding meshes\b\b_n_nord_m_head_04.nif -Decoding meshes\b\b_n_nord_f_head_05.nif -Decoding meshes\b\b_n_nord_m_head_05.nif -Decoding meshes\b\b_n_nord_f_head_04.nif -Decoding meshes\b\b_n_nord_m_head_06.nif -Decoding meshes\b\b_n_nord_f_head_07.nif -Decoding meshes\b\b_n_nord_m_head_07.nif -Decoding meshes\b\b_n_nord_f_head_06.nif -Decoding meshes\b\b_n_nord_m_head_08.nif -Decoding meshes\b\b_n_nord_f_head_08.nif -Decoding meshes\b\b_n_nord_f_hair_03.nif -Decoding meshes\b\b_n_nord_f_hair_02.nif -Decoding meshes\b\b_n_nord_f_hair_01.nif -Decoding meshes\b\b_n_nord_f_hair_05.nif -Decoding meshes\b\b_n_nord_f_hair_04.nif -Decoding meshes\b\b_n_nord_f_forearm.nif -Decoding meshes\b\b_n_nord_m_forearm.nif -Decoding meshes\cursor_drop_ground.nif -Decoding meshes\a\a_moragtong_helm.nif -Decoding meshes\b\b_n_nord_f_foot.nif -Decoding meshes\b\b_n_nord_m_ankle.nif -Decoding meshes\b\b_n_nord_m_foot.nif -Decoding meshes\b\b_n_nord_f_ankle.nif -Decoding meshes\b\b_n_nord_m_knee.nif -Decoding meshes\b\b_n_nord_f_knee.nif -Decoding meshes\b\b_n_nord_m_skins.nif -Decoding meshes\b\b_n_nord_m_neck.nif -Decoding meshes\b\b_n_nord_f_skins.nif -Decoding meshes\b\b_n_nord_m_groin.nif -Decoding meshes\b\b_n_nord_f_neck.nif -Decoding meshes\b\b_n_nord_f_wrist.nif -Decoding meshes\b\b_n_nord_m_wrist.nif -Decoding meshes\b\b_n_nord_f_groin.nif -Decoding meshes\b\b_n_orc_m_wrist.nif -Decoding meshes\b\b_n_orc_f_groin.nif -Decoding meshes\b\b_n_orc_m_groin.nif -Decoding meshes\b\b_n_orc_f_hair03.nif -Decoding meshes\b\b_n_orc_f_hair02.nif -Decoding meshes\b\b_n_orc_f_hair01.nif -Decoding meshes\b\b_n_orc_f_hair05.nif -Decoding meshes\b\b_n_orc_f_hair04.nif -Decoding meshes\b\b_n_orc_f_wrist.nif -Decoding meshes\b\b_n_orc_m_skins.nif -Decoding meshes\b\b_n_orc_f_skins.nif -Decoding meshes\b\b_n_orc_f_ankle.nif -Decoding meshes\b\b_n_orc_m_ankle.nif -Decoding meshes\a\a_molecrab_helm.nif -Decoding meshes\b\b_n_orc_f_knee.nif -Decoding meshes\b\b_n_orc_f_neck.nif -Decoding meshes\b\b_n_orc_f_foot.nif -Decoding meshes\b\b_n_orc_m_foot.nif -Decoding meshes\b\b_n_orc_m_knee.nif -Decoding meshes\b\b_n_orc_m_neck.nif -Decoding meshes\chimney_smoke_green.nif -Decoding meshes\chimney_smoke_small.nif -Decoding meshes\chimney_smoke02.nif -Decoding meshes\a\a_orcish_greaves_g.nif -Decoding meshes\a\a_orcish_greaves_k.nif -Decoding meshes\a\a_orcish_bracer_w.nif -Decoding meshes\a\a_orcish_cuirass_c.nif -Decoding meshes\a\a_orcish_boots_gnd.nif -Decoding meshes\c\c_m_robe_common_3.nif -Decoding meshes\c\c_m_robe_common_4.nif -Decoding meshes\c\c_m_robe_common_5.nif -Decoding meshes\c\c_m_robe_extrav_1c.nif -Decoding meshes\c\c_m_robe_extrav_1b.nif -Decoding meshes\c\c_m_robe_extrav_1a.nif -Decoding meshes\c\c_m_robe_extrav_1h.nif -Decoding meshes\c\c_m_robe_extrav_1t.nif -Decoding meshes\c\c_m_robe_extrav_1r.nif -Decoding meshes\c\c_m_robe_common_01.nif -Decoding meshes\c\c_m_robe_common_02.nif -Decoding meshes\c\c_m_robe_extrav_2.nif -Decoding meshes\c\c_m_robe_extrav_1.nif -Decoding meshes\c\c_m_robe_expens_3.nif -Decoding meshes\m\pick_apprentice_01.nif -Decoding meshes\m\pick_journeyman_01.nif -Decoding meshes\m\pick_master_01.nif -Decoding meshes\a\a_orcish_helmet.nif -Decoding meshes\a\a_orcish_boots_f.nif -Decoding meshes\a\a_orcish_boots_a.nif -Decoding meshes\a\a_newstscale_c_gnd.nif -Decoding meshes\a\a_netch_m_cuirass2.nif -Decoding meshes\a\a_netch_m_boot_gnd.nif -Decoding meshes\a\a_netch_m_greave_g.nif -Decoding meshes\a\a_netch_m_skinned.nif -Decoding meshes\a\a_nordicfur_boot_f.nif -Decoding meshes\a\a_nordicfur_boot_a.nif -Decoding meshes\a\a_nordiciron_helm.nif -Decoding meshes\a\a_nordicfur_helmet.nif -Decoding meshes\a\a_nordiciron_c_gnd.nif -Decoding meshes\argonian_swimkna.nif -Decoding meshes\a\a_netch_m_helmet.nif -Decoding meshes\a\a_netch_m_boot_f.nif -Decoding meshes\a\a_netch_m_boot_a.nif -Decoding meshes\a\a_nordiciron_c.nif -Decoding meshes\a\a_apostle_boots_f.nif -Decoding meshes\o\flora_saltrice_02.nif -Decoding meshes\o\flora_saltrice_01.nif -Decoding meshes\o\flora_fire_fern_01.nif -Decoding meshes\o\flora_fire_fern_02.nif -Decoding meshes\o\flora_fire_fern_03.nif -Decoding meshes\o\flora_chokeweed_01.nif -Decoding meshes\o\flora_roobrush_01.nif -Decoding meshes\o\flora_wickwheat_03.nif -Decoding meshes\o\flora_wickwheat_02.nif -Decoding meshes\o\flora_wickwheat_01.nif -Decoding meshes\o\flora_wickwheat_04.nif -Decoding meshes\o\flora_kreshweed_03.nif -Decoding meshes\o\flora_kreshweed_02.nif -Decoding meshes\o\flora_kreshweed_01.nif -Decoding meshes\o\flora_hackle-lo_01.nif -Decoding meshes\o\flora_hackle-lo_02.nif -Decoding meshes\a\a_art_wraithguard.nif -Decoding meshes\box.nif -Decoding meshes\r\corprus_stalker.nif -Decoding meshes\o\flora_ash_yam_02.nif -Decoding meshes\o\flora_ash_yam_01.nif -Decoding meshes\r\xgreatbonewalker.nif -Decoding meshes\r\atronach_fire.nif -Decoding meshes\r\atronach_storm.nif -Decoding meshes\r\atronach_frost.nif -Decoding meshes\a\a_art_dragon_gnd.nif -Decoding meshes\w\w_wakizashi_iron.nif -Decoding meshes\w\w_waraxe_daedric.nif -Decoding meshes\w\w_warhammer_iron.nif -Decoding meshes\w\w_waraxe_glass.nif -Decoding meshes\w\w_waraxe_iron.nif -Decoding meshes\w\w_waraxe_ebony.nif -Decoding meshes\w\w_waraxe_steel.nif -Decoding meshes\w\w_wooden_staff.nif -Decoding meshes\w\w_wakizashi.nif -Decoding meshes\w\w_warhammer.nif -Decoding meshes\c\c_belt_exquisite_1.nif -Decoding meshes\c\c_belt_expensive_1.nif -Decoding meshes\c\c_belt_expensive_3.nif -Decoding meshes\c\c_belt_expensive_2.nif -Decoding meshes\o\contain_drawer_02.nif -Decoding meshes\o\contain_drawer_03.nif -Decoding meshes\o\contain_drawer_01.nif -Decoding meshes\o\contain_de_desk_01.nif -Decoding meshes\o\contain_barrel_01.nif -Decoding meshes\o\contain_couldron10.nif -Decoding meshes\o\contain_corpse20.nif -Decoding meshes\o\contain_corpse00.nif -Decoding meshes\o\contain_corpse10.nif -Decoding meshes\o\contain_chest10.nif -Decoding meshes\o\contain_barrel10.nif -Decoding meshes\o\contain_crate_01.nif -Decoding meshes\o\contain_crate_02.nif -Decoding meshes\o\contain_chest11.nif -Decoding meshes\o\contain_sack00.nif -Decoding meshes\o\contain_urn_04.nif -Decoding meshes\o\contain_urn_02.nif -Decoding meshes\o\contain_urn_05.nif -Decoding meshes\o\contain_urn_01.nif -Decoding meshes\o\contain_urn_03.nif -Decoding meshes\o\contain_pot_01.nif -Decoding meshes\r\xascendedsleeper.nif -Decoding meshes\r\xashvampire.nif -Decoding meshes\c\c_belt_common_4.nif -Decoding meshes\c\c_belt_common_2.nif -Decoding meshes\c\c_belt_common_5.nif -Decoding meshes\c\c_belt_common_1.nif -Decoding meshes\c\c_belt_common_3.nif -Decoding meshes\c\c_belt_erabin.nif -Decoding meshes\e\magic_area_conjure.nif -Decoding meshes\e\magic_cast_poison.nif -Decoding meshes\e\magic_cast_conjure.nif -Decoding meshes\e\magic_cast_restore.nif -Decoding meshes\e\magic_cast_fortify.nif -Decoding meshes\e\magic_hit_levitate.nif -Decoding meshes\e\magic_area_poison.nif -Decoding meshes\e\magic_hit_conjure.nif -Decoding meshes\a\a_cephalopod_helm.nif -Decoding meshes\r\xsphere_centurions.nif -Decoding meshes\c\c_art_ring_mentor.nif -Decoding meshes\c\c_art_ring_khajiit.nif -Decoding meshes\c\c_art_ring_warlock.nif -Decoding meshes\e\lightning_shield.nif -Decoding meshes\e\lightningbolts.nif -Decoding meshes\e\lightning_area.nif -Decoding meshes\c\c_m_shirt_expensive_1_u_gnd.nif -Decoding meshes\e\magic_area_rest.nif -Decoding meshes\e\magic_hit_frost.nif -Decoding meshes\e\magic_area_drain.nif -Decoding meshes\e\magic_hit_poison.nif -Decoding meshes\e\magic_area_myst.nif -Decoding meshes\e\magic_cast_myst.nif -Decoding meshes\e\magic_area_frost.nif -Decoding meshes\e\magic_cast_frost.nif -Decoding meshes\e\magic_hit_alter.nif -Decoding meshes\e\magic_hit_ill.nif -Decoding meshes\e\magic_hit_rest.nif -Decoding meshes\e\magic_hit_dst.nif -Decoding meshes\e\magic_reflect.nif -Decoding meshes\e\magic_area_ill.nif -Decoding meshes\e\magic_area_alt.nif -Decoding meshes\e\magic_cast_ill.nif -Decoding meshes\e\magic_cast_alt.nif -Decoding meshes\e\magic_cast_dst.nif -Decoding meshes\e\magic_area_dst.nif -Decoding meshes\e\magic_hit_myst.nif -Decoding meshes\e\magic_hit_s.nif -Decoding meshes\e\magic_cast_l.nif -Decoding meshes\e\magic_cast_s.nif -Decoding meshes\e\magic_summon.nif -Decoding meshes\m\probe_master_01.nif -Decoding meshes\c\c_art_ring_wind.nif -Decoding meshes\a\a_bonemold_boots_a.nif -Decoding meshes\a\a_bonemold_boots_f.nif -Decoding meshes\a\a_bonemold_helmet.nif -Decoding meshes\w\w_tanto_daedric.nif -Decoding meshes\w\w_tanto_iron.nif -Decoding meshes\w\w_shortbow_chitin.nif -Decoding meshes\w\w_shortsword_ebony.nif -Decoding meshes\w\w_silver_claymore.nif -Decoding meshes\c\c_glove_expensive1.nif -Decoding meshes\c\c_glove_moragtong.nif -Decoding meshes\w\w_steel_battleaxe.nif -Decoding meshes\a\a_ebony_greaves_ul.nif -Decoding meshes\a\a_ebony_greaves_k.nif -Decoding meshes\a\a_ebony_g_greaves.nif -Decoding meshes\r\cavemudcrab.nif -Decoding meshes\w\w_staff_daedric.nif -Decoding meshes\w\w_spear_daedric.nif -Decoding meshes\w\w_silver_dagger.nif -Decoding meshes\w\w_silver_waraxe.nif -Decoding meshes\w\w_shortbow_steel.nif -Decoding meshes\a\a_ebony_cuirass.nif -Decoding meshes\a\a_ebony_bracer_w.nif -Decoding meshes\a\a_ebony_boot_gnd.nif -Decoding meshes\c\c_glove_common1.nif -Decoding meshes\w\w_staff_ebony.nif -Decoding meshes\w\w_steel_arrow.nif -Decoding meshes\w\w_steel_star .nif -Decoding meshes\w\w_steel_knife.nif -Decoding meshes\w\w_staff_glass.nif -Decoding meshes\w\w_shortsword00.nif -Decoding meshes\w\w_silver_staff.nif -Decoding meshes\w\w_silver_arrow.nif -Decoding meshes\w\w_silver_star.nif -Decoding meshes\w\w_silver_spear.nif -Decoding meshes\w\w_star_ebony.nif -Decoding meshes\w\w_saber_iron.nif -Decoding meshes\w\w_spikedclub.nif -Decoding meshes\w\w_star_glass.nif -Decoding meshes\w\w_spear_iron.nif -Decoding meshes\a\a_ebony_helmet.nif -Decoding meshes\a\a_ebony_boot_a.nif -Decoding meshes\a\a_ebony_boot_f.nif -Decoding meshes\a\a_daedric_boots_f.nif -Decoding meshes\a\a_daedric_boots_a.nif -Decoding meshes\a\a_dragonscale_helm.nif -Decoding meshes\a\a_dwemer_greaves_g.nif -Decoding meshes\a\a_dwemer_boots_gnd.nif -Decoding meshes\a\a_dwemer_cuirass_c.nif -Decoding meshes\a\a_dwemer_cuir_gnd.nif -Decoding meshes\a\a_dwemer_bracer_w.nif -Decoding meshes\f\xact_banner_khull.nif -Decoding meshes\r\xkwama worker.nif -Decoding meshes\r\xkwama warior.nif -Decoding meshes\r\xkwama forager.nif -Decoding meshes\r\xkwama queen.nif -Decoding meshes\i\in_dae_hall_l_stair_curve_01.nif -Decoding meshes\i\in_dae_hall_l_staircurve_01.nif -Decoding meshes\r\xdwarvenspecter.nif -Decoding meshes\f\xact_banner_vos.nif -Decoding meshes\c\c_m_pants_expensive_1_u_gnd.nif -Decoding meshes\i\in_t_s_hallshaft_ceilingcap.nif -Decoding meshes\a\a_dustadept_helm.nif -Decoding meshes\a\a_dwemer_boots_f.nif -Decoding meshes\a\a_dwemer_boots_a.nif -Decoding meshes\a\a_dwemer_helmet.nif -Decoding meshes\a\a_dreugh_cuirass.nif -Decoding meshes\a\a_daedric_skins.nif -Decoding meshes\a\a_daedric_god_h.nif -Decoding meshes\a\a_dreugh_helm.nif -Decoding meshes\a\a_glass_bracer_gnd.nif -Decoding meshes\a\a_glass_greaves_ul.nif -Decoding meshes\a\a_glass_greaves_g.nif -Decoding meshes\a\a_glass_greaves_k.nif -Decoding meshes\a\a_glass_boots_gnd.nif -Decoding meshes\r\xsteam_centurions.nif -Decoding meshes\r\netch_betty.nif -Decoding meshes\r\xatronach_storm.nif -Decoding meshes\r\xatronach_frost.nif -Decoding meshes\r\xatronach_fire.nif -Decoding meshes\a\a_glass_cuirass.nif -Decoding meshes\a\a_glass_boots_a.nif -Decoding meshes\a\a_glass_bracer_w.nif -Decoding meshes\a\a_glass_boots_f.nif -Decoding meshes\a\a_glass_helmet.nif -Decoding meshes\a\a_fur_cuirass_gnd.nif -Decoding meshes\i\xin_akulakhan00.nif -Decoding meshes\r\xguar_withpack.nif -Decoding meshes\r\xguar_white.nif -Decoding meshes\inventory_window.nif -Decoding meshes\a\a_fur_cuirass.nif -Decoding meshes\w\w_orcish_battleaxe.nif -Decoding meshes\w\w_orcish_warhammer.nif -Decoding meshes\w\w_nordic_claymore.nif -Decoding meshes\w\w_nordic_battleaxe.nif -Decoding meshes\w\w_n_claymore.nif -Decoding meshes\i\test _cavern_i_01.nif -Decoding meshes\r\sphere_centurions.nif -Decoding meshes\n\potion_skooma_01.nif -Decoding meshes\w\w_mace_daedric.nif -Decoding meshes\w\w_mace_ebony.nif -Decoding meshes\w\w_miner_pick.nif -Decoding meshes\w\w_mace_iron.nif -Decoding meshes\w\w_longbow_daedric.nif -Decoding meshes\w\w_longsword_silver.nif -Decoding meshes\w\w_longspear_ebony.nif -Decoding meshes\w\w_longbow_bonemold.nif -Decoding meshes\w\w_longsword_ebony.nif -Decoding meshes\r\xwingedtwilight.nif -Decoding meshes\r\xsiltstrider.nif -Decoding meshes\w\w_longbow_ariel.nif -Decoding meshes\w\w_longbow_steel.nif -Decoding meshes\r\wingedtwilight.nif -Decoding meshes\o\misc_sack00.nif -Decoding meshes\o\misc_chest11.nif -Decoding meshes\r\xancestorghost.nif -Decoding meshes\w\w_katana_daedric.nif -Decoding meshes\w\w_knife_glass.nif -Decoding meshes\w\w_knife_iron.nif -Decoding meshes\i\active_port_valen.nif -Decoding meshes\i\active_port_falen.nif -Decoding meshes\i\active_port_maran.nif -Decoding meshes\i\active_port_beran.nif -Decoding meshes\i\active_port_telas.nif -Decoding meshes\i\active_port_falag.nif -Decoding meshes\i\active_port_falas.nif -Decoding meshes\i\active_dag_port10.nif -Decoding meshes\i\active_port_andra.nif -Decoding meshes\i\in_c_stair_thatch_pend_tall_01.nif -Decoding meshes\i\in_c_stair_thatch_pend_tall_02.nif -Decoding meshes\e\vfx_pattern06.nif -Decoding meshes\e\vfx_pattern07.nif -Decoding meshes\e\vfx_pattern02.nif -Decoding meshes\e\vfx_pattern04.nif -Decoding meshes\e\vfx_pattern03.nif -Decoding meshes\e\vfx_pattern05.nif -Decoding meshes\e\vfx_pattern08.nif -Decoding meshes\m\repair_master_01.nif -Decoding meshes\i\active_port_hlor.nif -Decoding meshes\i\active_port_indo.nif -Decoding meshes\i\active_port_roth.nif -Decoding meshes\r\xcorprus_stalker.nif -Decoding meshes\r\xbonewalker.nif -Decoding meshes\xbase_anim_female.nif -Decoding meshes\xbase_animkna.1st.nif -Decoding meshes\xbase_animkna.nif -Decoding meshes\xbase_anim.1st.nif -Decoding meshes\a\a_m_imperialchain_pauldron_gnd.nif -Decoding meshes\a\a_m_imperialchain_greaves_gnd.nif -Decoding meshes\a\a_m_imperialchain_pauldron_ua.nif -Decoding meshes\a\a_m_imperialchain_greaves_g.nif -Decoding meshes\a\a_m_imperialchain_greaves_ul.nif -Decoding meshes\w\w_iron_shortsword.nif -Decoding meshes\m\misc_foldedcloth00.nif -Decoding meshes\m\misc_de_tankard_01.nif -Decoding meshes\m\misc_de_goblet_09.nif -Decoding meshes\m\misc_de_goblet_08.nif -Decoding meshes\m\misc_de_goblet_01.nif -Decoding meshes\m\misc_de_goblet_03.nif -Decoding meshes\m\misc_de_goblet_02.nif -Decoding meshes\m\misc_de_goblet_05.nif -Decoding meshes\m\misc_de_goblet_04.nif -Decoding meshes\m\misc_de_goblet_07.nif -Decoding meshes\m\misc_de_goblet_06.nif -Decoding meshes\m\misc_dwrv_goblet00.nif -Decoding meshes\m\misc_dwrv_goblet10.nif -Decoding meshes\m\misc_de_pitcher_01.nif -Decoding meshes\m\misc_de_basket_01.nif -Decoding meshes\m\misc_bowl_white_01.nif -Decoding meshes\m\misc_com_bucket_01.nif -Decoding meshes\m\misc_com_pillow_01.nif -Decoding meshes\m\misc_com_basket_02.nif -Decoding meshes\m\misc_com_basket_01.nif -Decoding meshes\m\misc_com_bottle_06.nif -Decoding meshes\m\misc_com_bottle_07.nif -Decoding meshes\m\misc_com_bottle_14.nif -Decoding meshes\m\misc_com_bottle_04.nif -Decoding meshes\m\misc_com_bottle_15.nif -Decoding meshes\m\misc_com_bottle_05.nif -Decoding meshes\m\misc_com_bottle_12.nif -Decoding meshes\m\misc_com_bottle_02.nif -Decoding meshes\m\misc_com_bottle_13.nif -Decoding meshes\m\misc_com_bottle_03.nif -Decoding meshes\m\misc_com_bottle_10.nif -Decoding meshes\m\misc_com_bottle_11.nif -Decoding meshes\m\misc_com_bottle_01.nif -Decoding meshes\m\misc_com_bottle_08.nif -Decoding meshes\m\misc_com_bottle_09.nif -Decoding meshes\m\misc_com_wood_fork.nif -Decoding meshes\m\misc_com_broom_01.nif -Decoding meshes\m\misc_com_plate_06.nif -Decoding meshes\m\misc_com_plate_07.nif -Decoding meshes\m\misc_com_plate_04.nif -Decoding meshes\m\misc_com_plate_05.nif -Decoding meshes\m\misc_com_plate_02.nif -Decoding meshes\m\misc_com_plate_03.nif -Decoding meshes\m\misc_com_plate_01.nif -Decoding meshes\m\misc_com_plate_08.nif -Decoding meshes\m\misc_wheatbundle00.nif -Decoding meshes\m\misc_uni_pillow_02.nif -Decoding meshes\m\misc_redware_bowl.nif -Decoding meshes\m\misc_redware_flask.nif -Decoding meshes\m\misc_rollingpin_01.nif -Decoding meshes\m\misc_redware_vase.nif -Decoding meshes\m\misc_redware_plate.nif -Decoding meshes\m\misc_redware_lamp.nif -Decoding meshes\m\misc_soulgem_grand.nif -Decoding meshes\m\misc_soulgem_petty.nif -Decoding meshes\m\misc_pot_green_01.nif -Decoding meshes\m\misc_portal_shard.nif -Decoding meshes\f\xex_ashl_z_banner.nif -Decoding meshes\f\xex_ashl_a_banner.nif -Decoding meshes\f\xex_ashl_u_banner.nif -Decoding meshes\f\xex_ashl_e_banner.nif -Decoding meshes\l\furn_de_firepit_f.nif -Decoding meshes\r\golden saint.nif -Decoding meshes\m\misc_scrapwood01.nif -Decoding meshes\m\misc_scrapwood03.nif -Decoding meshes\m\misc_scrapwood02.nif -Decoding meshes\m\misc_scrapwood05.nif -Decoding meshes\m\misc_scrapwood04.nif -Decoding meshes\m\misc_lw_platter.nif -Decoding meshes\m\misc_dwrv_bowl00.nif -Decoding meshes\m\misc_de_drum_02.nif -Decoding meshes\m\misc_shackles00.nif -Decoding meshes\m\misc_redware_cup.nif -Decoding meshes\m\misc_ropecoil00.nif -Decoding meshes\m\misc_wickwheat00.nif -Decoding meshes\m\misc_de_bowl_01.nif -Decoding meshes\m\misc_pot_blue_01.nif -Decoding meshes\m\misc_pot_blue_02.nif -Decoding meshes\m\misc_de_lute_01.nif -Decoding meshes\m\misc_dwrv_coin00.nif -Decoding meshes\m\misc_de_drum_01.nif -Decoding meshes\m\misc_6th_goblet.nif -Decoding meshes\m\misc_dwrv_gear00.nif -Decoding meshes\m\misc_placemat_01.nif -Decoding meshes\m\misc_dwrv_mug00.nif -Decoding meshes\r\siltstrider.nif -Decoding meshes\m\misc_flask_02.nif -Decoding meshes\m\misc_hammer10.nif -Decoding meshes\m\misc_prongs00.nif -Decoding meshes\m\misc_shears_01.nif -Decoding meshes\m\misc_beaker_01.nif -Decoding meshes\m\misc_spool_01.nif -Decoding meshes\m\misc_flask_01.nif -Decoding meshes\m\misc_bellows10.nif -Decoding meshes\m\misc_flask_04.nif -Decoding meshes\m\misc_lw_flask.nif -Decoding meshes\m\misc_flask_03.nif -Decoding meshes\m\misc_lw_cup.nif -Decoding meshes\m\misc_cloth10.nif -Decoding meshes\m\misc_lw_bowl.nif -Decoding meshes\m\misc_inkwell.nif -Decoding meshes\m\misc_cloth11.nif -Decoding meshes\m\misc_skull00.nif -Decoding meshes\m\misc_skull10.nif -Decoding meshes\r\xclannfear_daddy.nif -Decoding meshes\r\xcliffracer.nif -Decoding meshes\steam_bluegreen.nif -Decoding meshes\steam_lavariver.nif -Decoding meshes\l\furn_de_chair_02.nif -Decoding meshes\r\xslaughterfish.nif -Decoding meshes\w\w_iron_claymore.nif -Decoding meshes\w\w_iron_longsword.nif -Decoding meshes\w\w_iron_dagger.nif -Decoding meshes\w\w_iron_arrow.nif -Decoding meshes\sky_moon_small.nif -Decoding meshes\sky_moon_large.nif -Decoding meshes\l\misc_candle_red_01.nif -Decoding meshes\r\lame_corprus.nif -Decoding meshes\w\w_halberd_glass.nif -Decoding meshes\w\w_halberd_steel.nif -Decoding meshes\w\w_halberd_iron.nif -Decoding meshes\c\c_shirt_aralor_fa.nif -Decoding meshes\c\c_shirt_aralor_ua.nif -Decoding meshes\c\c_shirt_aralor_gnd.nif -Decoding meshes\sky_clouds_01_no_tex.nif -Decoding meshes\w\magic_target_myst.nif -Decoding meshes\w\magic_target_rest.nif -Decoding meshes\w\magic_target_frost.nif -Decoding meshes\c\c_shoes_extrav_2_f.nif -Decoding meshes\c\c_shoes_extrav_1_f.nif -Decoding meshes\c\c_shoes_rilms_gnd.nif -Decoding meshes\e\soultraphit.nif -Decoding meshes\w\magic_target_ill.nif -Decoding meshes\w\magic_target_dst.nif -Decoding meshes\w\magic_target_alt.nif -Decoding meshes\w\magic_target.nif -Decoding meshes\c\c_shoes_common_3.nif -Decoding meshes\c\c_shoes_common_4.nif -Decoding meshes\c\c_shoes_common_5.nif -Decoding meshes\c\c_shirt_aralor_c.nif -Decoding meshes\c\c_shirt_aralor_w.nif -Decoding meshes\c\c_skirt_common_5.nif -Decoding meshes\c\c_skirt_common_2.nif -Decoding meshes\c\c_skirt_common_3.nif -Decoding meshes\w\w_glass_arrow.nif -Decoding meshes\sky_clouds_01.nif -Decoding meshes\c\c_slave_bracer.nif -Decoding meshes\c\c_shoes_rilms.nif -Decoding meshes\r\g_centurionspider.nif -Decoding meshes\c\c_ring_exquisite_1.nif -Decoding meshes\c\c_ring_expensive_1.nif -Decoding meshes\c\c_ring_expensive_3.nif -Decoding meshes\c\c_ring_expensive_2.nif -Decoding meshes\r\ancestorghost.nif -Decoding meshes\r\ascendedsleeper.nif -Decoding meshes\r\xscamp_fetch.nif -Decoding meshes\c\c_ring_common05.nif -Decoding meshes\c\c_ring_common01.nif -Decoding meshes\c\c_ring_common03.nif -Decoding meshes\c\c_ring_moonnstar.nif -Decoding meshes\c\c_ring_common04.nif -Decoding meshes\c\c_ring_common02.nif -Decoding meshes\c\c_ring_namira.nif -Decoding meshes\a\a_silver_duke_gnd.nif -Decoding meshes\a\a_silver_cuir_gnd.nif -Decoding meshes\a\a_steel_greaves_ul.nif -Decoding meshes\a\a_steel_greaves_g.nif -Decoding meshes\a\a_steel_greaves_k.nif -Decoding meshes\a\a_steel_hands.1st.nif -Decoding meshes\a\a_steel_boots_gnd.nif -Decoding meshes\a\a_shield_imperial.nif -Decoding meshes\f\active_de_bar_door.nif -Decoding meshes\f\active_akhul_steam.nif -Decoding meshes\f\active_signpost_01.nif -Decoding meshes\f\active_signpost_02.nif -Decoding meshes\f\active_de_bedroll.nif -Decoding meshes\f\act_banner_hla_oad.nif -Decoding meshes\f\act_banner_tel_fyr.nif -Decoding meshes\f\act_banner_tel_vos.nif -Decoding meshes\f\active_de_bed_19.nif -Decoding meshes\f\active_de_bed_18.nif -Decoding meshes\f\active_de_bed_13.nif -Decoding meshes\f\active_de_bed_12.nif -Decoding meshes\f\active_de_bed_11.nif -Decoding meshes\f\active_de_bed_10.nif -Decoding meshes\f\active_de_bed_17.nif -Decoding meshes\f\active_de_bed_16.nif -Decoding meshes\f\active_de_bed_15.nif -Decoding meshes\f\active_de_bed_14.nif -Decoding meshes\f\active_de_bed_09.nif -Decoding meshes\f\active_de_bed_08.nif -Decoding meshes\f\active_de_bed_03.nif -Decoding meshes\f\active_de_bed_02.nif -Decoding meshes\f\active_de_bed_01.nif -Decoding meshes\f\active_de_bed_07.nif -Decoding meshes\f\active_de_bed_06.nif -Decoding meshes\f\active_de_bed_05.nif -Decoding meshes\f\active_de_bed_04.nif -Decoding meshes\f\active_de_bed_30.nif -Decoding meshes\f\active_de_bed_29.nif -Decoding meshes\f\active_de_bed_28.nif -Decoding meshes\f\active_de_bed_23.nif -Decoding meshes\f\active_de_bed_22.nif -Decoding meshes\f\active_de_bed_21.nif -Decoding meshes\f\active_de_bed_20.nif -Decoding meshes\f\active_de_bed_27.nif -Decoding meshes\f\active_de_bed_26.nif -Decoding meshes\f\active_de_bed_25.nif -Decoding meshes\f\active_de_bed_24.nif -Decoding meshes\f\active_bubbles00.nif -Decoding meshes\f\active_button_01.nif -Decoding meshes\f\act_banner_khull.nif -Decoding meshes\f\act_banner_vos.nif -Decoding meshes\f\active_gong_01.nif -Decoding meshes\a\a_silver_cuirass.nif -Decoding meshes\w\w_ebony_arrow.nif -Decoding meshes\vfx_defaultcast.nif -Decoding meshes\vfx_defaultarea.nif -Decoding meshes\vfx_defaulthit.nif -Decoding meshes\sky_atmosphere.nif -Decoding meshes\a\a_steel_helmet.nif -Decoding meshes\a\a_steel_boot_f.nif -Decoding meshes\a\a_steel_boot_a.nif -Decoding meshes\a\a_steel_skin.nif -Decoding meshes\f\xfurn_imp_flag_01.nif -Decoding meshes\a\a_ringmail_cuirass.nif -Decoding meshes\w\w_dwemer_warhammer.nif -Decoding meshes\w\w_dwemer_claymore.nif -Decoding meshes\w\w_dwemer_battleaxe.nif -Decoding meshes\w\w_dwemer_longspear.nif -Decoding meshes\a\a_redoranmaster_h.nif -Decoding meshes\f\sound_dummy00.nif -Decoding meshes\r\clannfear_daddy.nif -Decoding meshes\r\slaughterfish.nif -Decoding meshes\r\xlame_corprus.nif -Decoding meshes\r\heart_akulakhan.nif -Decoding meshes\r\leastkagouti.nif -Decoding meshes\r\scamp_fetch.nif -Decoding meshes\r\xcavemudcrab.nif -Decoding meshes\r\dwarvenspecter.nif -Decoding meshes\r\kwama forager.nif -Decoding meshes\r\kwama worker.nif -Decoding meshes\r\kwama queen.nif -Decoding meshes\r\kwama warior.nif -Decoding meshes\r\guar_withpack.nif -Decoding meshes\w\w_dwemer_waraxe.nif -Decoding meshes\w\w_dwemer_halberd.nif -Decoding meshes\w\w_daedric_arrow.nif -Decoding meshes\w\w_dagger_dragon.nif -Decoding meshes\w\w_dagger_daedric.nif -Decoding meshes\w\w_dagger_chitin.nif -Decoding meshes\w\w_dwemer_spear.nif -Decoding meshes\w\w_dwemer_mace.nif -Decoding meshes\w\w_dreugh_staff.nif -Decoding meshes\w\w_dreugh_club.nif -Decoding meshes\w\w_dagger_glass.nif -Decoding meshes\w\w_dart_silver.nif -Decoding meshes\w\w_dart_daedric.nif -Decoding meshes\w\w_dart_ebony.nif -Decoding meshes\w\w_dart_steel.nif -Decoding meshes\w\w_daikatana.nif -Decoding meshes\b\b_v_orc_m_head_01.nif -Decoding meshes\b\b_v_orc_f_head_01.nif -Decoding meshes\w\w_crossbow_dwemer.nif -Decoding meshes\w\w_claymore_crystal.nif -Decoding meshes\w\w_claymore_daedric.nif -Decoding meshes\b\b_v_nord_f_head_01.nif -Decoding meshes\b\b_v_nord_m_head_01.nif -Decoding meshes\f\terrain_bc_scum_01.nif -Decoding meshes\f\terrain_bc_scum_03.nif -Decoding meshes\f\terrain_bc_scum_02.nif -Decoding meshes\f\terrain_rock_ac_08.nif -Decoding meshes\f\terrain_rock_bc_08.nif -Decoding meshes\f\terrain_rock_bc_18.nif -Decoding meshes\f\terrain_rock_ac_09.nif -Decoding meshes\f\terrain_rock_bc_09.nif -Decoding meshes\f\terrain_rock_ac_02.nif -Decoding meshes\f\terrain_rock_ac_12.nif -Decoding meshes\f\terrain_rock_bc_02.nif -Decoding meshes\f\terrain_rock_bc_12.nif -Decoding meshes\f\terrain_rock_ac_03.nif -Decoding meshes\f\terrain_rock_bc_03.nif -Decoding meshes\f\terrain_rock_bc_13.nif -Decoding meshes\f\terrain_rock_ac_10.nif -Decoding meshes\f\terrain_rock_bc_10.nif -Decoding meshes\f\terrain_rock_ac_01.nif -Decoding meshes\f\terrain_rock_ac_11.nif -Decoding meshes\f\terrain_rock_bc_01.nif -Decoding meshes\f\terrain_rock_bc_11.nif -Decoding meshes\f\terrain_rock_ac_06.nif -Decoding meshes\f\terrain_rock_bc_06.nif -Decoding meshes\f\terrain_rock_bc_16.nif -Decoding meshes\f\terrain_rock_ac_07.nif -Decoding meshes\f\terrain_rock_bc_07.nif -Decoding meshes\f\terrain_rock_bc_17.nif -Decoding meshes\f\terrain_rock_ac_04.nif -Decoding meshes\f\terrain_rock_bc_04.nif -Decoding meshes\f\terrain_rock_bc_14.nif -Decoding meshes\f\terrain_rock_ac_05.nif -Decoding meshes\f\terrain_rock_bc_05.nif -Decoding meshes\f\terrain_rock_bc_15.nif -Decoding meshes\f\terrain_rock_ai_09.nif -Decoding meshes\f\terrain_rock_ai_08.nif -Decoding meshes\f\terrain_rock_ai_01.nif -Decoding meshes\f\terrain_rock_ai_11.nif -Decoding meshes\f\terrain_rock_ai_10.nif -Decoding meshes\f\terrain_rock_ai_03.nif -Decoding meshes\f\terrain_rock_ai_02.nif -Decoding meshes\f\terrain_rock_ai_12.nif -Decoding meshes\f\terrain_rock_ai_05.nif -Decoding meshes\f\terrain_rock_ai_04.nif -Decoding meshes\f\terrain_rock_ai_07.nif -Decoding meshes\f\terrain_rock_ai_06.nif -Decoding meshes\f\terrain_rock_wg_09.nif -Decoding meshes\f\terrain_rock_wg_18.nif -Decoding meshes\f\terrain_rock_wg_08.nif -Decoding meshes\f\terrain_rock_wg_15.nif -Decoding meshes\f\terrain_rock_wg_05.nif -Decoding meshes\f\terrain_rock_wg_14.nif -Decoding meshes\f\terrain_rock_wg_04.nif -Decoding meshes\f\terrain_rock_wg_17.nif -Decoding meshes\f\terrain_rock_wg_07.nif -Decoding meshes\f\terrain_rock_wg_16.nif -Decoding meshes\f\terrain_rock_wg_06.nif -Decoding meshes\f\terrain_rock_wg_11.nif -Decoding meshes\f\terrain_rock_wg_01.nif -Decoding meshes\f\terrain_rock_wg_10.nif -Decoding meshes\f\terrain_rock_wg_13.nif -Decoding meshes\f\terrain_rock_wg_03.nif -Decoding meshes\f\terrain_rock_wg_12.nif -Decoding meshes\f\terrain_rock_wg_02.nif -Decoding meshes\f\terrain_rock_ma_01.nif -Decoding meshes\f\terrain_boulder_02.nif -Decoding meshes\f\terrain_boulder_03.nif -Decoding meshes\f\terrain_boulder_01.nif -Decoding meshes\f\terrain_boulder_04.nif -Decoding meshes\f\terrain_boulder_05.nif -Decoding meshes\f\terrain_rock_gl_03.nif -Decoding meshes\f\terrain_rock_gl_02.nif -Decoding meshes\f\terrain_rock_gl_12.nif -Decoding meshes\f\terrain_rock_gl_01.nif -Decoding meshes\f\terrain_rock_gl_11.nif -Decoding meshes\f\terrain_rock_gl_10.nif -Decoding meshes\f\terrain_rock_gl_07.nif -Decoding meshes\f\terrain_rock_gl_06.nif -Decoding meshes\f\terrain_rock_gl_05.nif -Decoding meshes\f\terrain_rock_gl_04.nif -Decoding meshes\f\terrain_rock_gl_09.nif -Decoding meshes\f\terrain_rock_gl_08.nif -Decoding meshes\f\terrain_rock_rm_09.nif -Decoding meshes\f\terrain_rock_rm_19.nif -Decoding meshes\f\terrain_rock_rm_08.nif -Decoding meshes\f\terrain_rock_rm_18.nif -Decoding meshes\f\terrain_rock_rm_07.nif -Decoding meshes\f\terrain_rock_rm_17.nif -Decoding meshes\f\terrain_rock_rm_06.nif -Decoding meshes\f\terrain_rock_rm_16.nif -Decoding meshes\f\terrain_rock_rm_05.nif -Decoding meshes\f\terrain_rock_rm_15.nif -Decoding meshes\f\terrain_rock_rm_04.nif -Decoding meshes\f\terrain_rock_rm_14.nif -Decoding meshes\f\terrain_rock_rm_24.nif -Decoding meshes\f\terrain_rock_rm_03.nif -Decoding meshes\f\terrain_rock_rm_13.nif -Decoding meshes\f\terrain_rock_rm_23.nif -Decoding meshes\f\terrain_rock_rm_02.nif -Decoding meshes\f\terrain_rock_rm_12.nif -Decoding meshes\f\terrain_rock_rm_22.nif -Decoding meshes\f\terrain_rock_rm_01.nif -Decoding meshes\f\terrain_rock_rm_11.nif -Decoding meshes\f\terrain_rock_rm_21.nif -Decoding meshes\f\terrain_rock_rm_10.nif -Decoding meshes\f\terrain_rock_rm_20.nif -Decoding meshes\f\furn_roped_pole_01.nif -Decoding meshes\f\furn_rail_broke00.nif -Decoding meshes\f\furn_rail_elbow_00.nif -Decoding meshes\f\furn_rail_slope_00.nif -Decoding meshes\f\furn_stickbundle00.nif -Decoding meshes\f\furn_smokestack00.nif -Decoding meshes\f\furn_pathspear_03.nif -Decoding meshes\f\furn_pathspear_02.nif -Decoding meshes\f\furn_pathspear_01.nif -Decoding meshes\f\furn_pathspear_04.nif -Decoding meshes\f\furn_pycave_pool00.nif -Decoding meshes\f\furn_wallscreen_01.nif -Decoding meshes\f\furn_wallscreen_02.nif -Decoding meshes\f\furn_triolith_01a.nif -Decoding meshes\f\furn_halfbarrel01.nif -Decoding meshes\f\furn_halfbarrel00.nif -Decoding meshes\f\furn_imp_altar_01.nif -Decoding meshes\f\furn_imp_metalring.nif -Decoding meshes\f\dwrv_mechlfrarm00.nif -Decoding meshes\f\dwrv_mechrthigh00.nif -Decoding meshes\f\dwrv_mechlthigh00.nif -Decoding meshes\f\furn_bone_stake00.nif -Decoding meshes\f\furn_bone_skull_01.nif -Decoding meshes\f\furn_bannerpost_02.nif -Decoding meshes\f\furn_bannerpost_01.nif -Decoding meshes\f\furn_c_t_shadow_01.nif -Decoding meshes\f\furn_c_t_theif_01.nif -Decoding meshes\f\furn_com_bar_door.nif -Decoding meshes\f\furn_c_t_tower_01.nif -Decoding meshes\f\furn_c_t_arkay_01.nif -Decoding meshes\f\furn_com_kegstand.nif -Decoding meshes\f\furn_clothbolt_02.nif -Decoding meshes\f\furn_clothbolt_03.nif -Decoding meshes\f\furn_clothbolt_01.nif -Decoding meshes\f\furn_com_table_01.nif -Decoding meshes\f\furn_com_table_03.nif -Decoding meshes\f\furn_com_table_02.nif -Decoding meshes\f\furn_com_table_05.nif -Decoding meshes\f\furn_com_table_04.nif -Decoding meshes\f\furn_com_stool_01.nif -Decoding meshes\f\furn_com_stool_02.nif -Decoding meshes\f\furn_com_barstool.nif -Decoding meshes\f\furn_c_t_ritual_01.nif -Decoding meshes\f\furn_c_t_wizard_01.nif -Decoding meshes\f\furn_c_t_lover_01.nif -Decoding meshes\f\furn_crate_lid_01.nif -Decoding meshes\f\furn_com_chair_01.nif -Decoding meshes\f\furn_com_chair_03.nif -Decoding meshes\f\furn_com_chair_02.nif -Decoding meshes\f\furn_com_winerack.nif -Decoding meshes\f\furn_c_t_steed_01.nif -Decoding meshes\f\furn_c_t_golem_01.nif -Decoding meshes\f\furn_com_shelf_04.nif -Decoding meshes\f\furn_com_shelf_03.nif -Decoding meshes\f\furn_com_shelf_02.nif -Decoding meshes\f\furn_com_shelf_01.nif -Decoding meshes\f\furn_crate_open_04.nif -Decoding meshes\f\furn_crate_open_05.nif -Decoding meshes\f\furn_crate_open_01.nif -Decoding meshes\f\furn_com_bench_02.nif -Decoding meshes\f\furn_com_bench_01.nif -Decoding meshes\f\furn_ashl_bugbowl.nif -Decoding meshes\f\furn_ashl_chime_03.nif -Decoding meshes\f\furn_ashl_chime_02.nif -Decoding meshes\f\furn_ashl_chime_01.nif -Decoding meshes\f\furn_ashl_chime_07.nif -Decoding meshes\f\furn_ashl_chime_06.nif -Decoding meshes\f\furn_ashl_chime_05.nif -Decoding meshes\f\furn_ashl_chime_04.nif -Decoding meshes\f\furn_ashl_chime_08.nif -Decoding meshes\f\furn_dwrv_bench10.nif -Decoding meshes\f\furn_dwrv_bench00.nif -Decoding meshes\f\furn_dae_rubble_07.nif -Decoding meshes\f\furn_dae_rubble_06.nif -Decoding meshes\f\furn_dae_rubble_05.nif -Decoding meshes\f\furn_de_ashl_post.nif -Decoding meshes\f\furn_de_shack_post.nif -Decoding meshes\f\furn_dwrv_steam_00.nif -Decoding meshes\f\furn_dwrv_dynamo00.nif -Decoding meshes\f\furn_de_bellows_01.nif -Decoding meshes\f\furn_dwrv_table10.nif -Decoding meshes\f\furn_dwrv_table00.nif -Decoding meshes\f\furn_de_firepit_01.nif -Decoding meshes\f\furn_dwrv_bucket00.nif -Decoding meshes\f\furn_dwrv_table20.nif -Decoding meshes\f\furn_de_shack_hook.nif -Decoding meshes\f\furn_dwrv_chair00.nif -Decoding meshes\f\furn_dwrv_stove00.nif -Decoding meshes\f\furn_dwrv_stove10.nif -Decoding meshes\f\furn_dwrv_tranny01.nif -Decoding meshes\f\furn_dwrv_tranny00.nif -Decoding meshes\f\furn_dwrv_stool00.nif -Decoding meshes\f\furn_dwrv_stool10.nif -Decoding meshes\x\collision01.nif -Decoding meshes\m\key_standard_01.nif -Decoding meshes\m\key_temple_01.nif -Decoding meshes\f\dwrv_mechrarm00.nif -Decoding meshes\f\dwrv_mechhead00.nif -Decoding meshes\f\dwrv_mechlhand00.nif -Decoding meshes\f\dwrv_mechlfoot00.nif -Decoding meshes\f\dwrv_mechrfoot00.nif -Decoding meshes\f\dwrv_mechrfarm00.nif -Decoding meshes\f\dwrv_mechhips00.nif -Decoding meshes\f\dwrv_mechrhand00.nif -Decoding meshes\f\dwrv_mechtorso00.nif -Decoding meshes\f\dwrv_mechlarm00.nif -Decoding meshes\f\furn_c_t_lord_01.nif -Decoding meshes\f\furn_de_winerack.nif -Decoding meshes\f\furn_de_shelf_02.nif -Decoding meshes\f\furn_de_shelf_01.nif -Decoding meshes\f\furn_overhang_09.nif -Decoding meshes\f\furn_overhang_01.nif -Decoding meshes\f\furn_overhang_03.nif -Decoding meshes\f\furn_overhang_02.nif -Decoding meshes\f\furn_overhang_05.nif -Decoding meshes\f\furn_overhang_04.nif -Decoding meshes\f\furn_overhang_07.nif -Decoding meshes\f\furn_overhang_06.nif -Decoding meshes\f\furn_spinwheel00.nif -Decoding meshes\f\furn_overhang_18.nif -Decoding meshes\f\furn_c_t_mara_01.nif -Decoding meshes\f\furn_6th_banner.nif -Decoding meshes\f\furn_de_chair_01.nif -Decoding meshes\f\furn_de_chair_02.nif -Decoding meshes\f\furn_de_chair_03.nif -Decoding meshes\f\furn_de_forge_01.nif -Decoding meshes\f\furn_pottedplant.nif -Decoding meshes\f\furn_triolith_01.nif -Decoding meshes\f\furn_coalpile00.nif -Decoding meshes\f\furn_de_table_07.nif -Decoding meshes\f\furn_de_table_06.nif -Decoding meshes\f\furn_de_table_05.nif -Decoding meshes\f\furn_de_table_04.nif -Decoding meshes\f\furn_de_table_03.nif -Decoding meshes\f\furn_de_table_02.nif -Decoding meshes\f\furn_de_table_01.nif -Decoding meshes\f\furn_de_table_09.nif -Decoding meshes\f\furn_de_table_08.nif -Decoding meshes\f\furn_cistern_01.nif -Decoding meshes\f\furn_com_bar_02.nif -Decoding meshes\f\furn_com_bar_04.nif -Decoding meshes\f\furn_com_bar_06.nif -Decoding meshes\f\furn_tapestry10.nif -Decoding meshes\f\furn_rug_big_08.nif -Decoding meshes\f\furn_rug_big_02.nif -Decoding meshes\f\furn_rug_big_04.nif -Decoding meshes\f\furn_rug_big_06.nif -Decoding meshes\f\furn_cot_rug_02.nif -Decoding meshes\f\furn_de_bench_03.nif -Decoding meshes\f\furn_de_bench_02.nif -Decoding meshes\f\furn_de_bench_01.nif -Decoding meshes\f\furn_de_bench_04.nif -Decoding meshes\f\furn_com_bed_02.nif -Decoding meshes\f\furn_com_bed_04.nif -Decoding meshes\f\furn_com_bed_06.nif -Decoding meshes\f\furn_de_loom_01.nif -Decoding meshes\f\furn_planter_01.nif -Decoding meshes\f\furn_planter_03.nif -Decoding meshes\f\furn_de_rope_05.nif -Decoding meshes\f\furn_de_rope_07.nif -Decoding meshes\f\furn_de_rope_03.nif -Decoding meshes\f\furn_c_t_lady_01.nif -Decoding meshes\f\furn_dwrv_bed00.nif -Decoding meshes\f\furn_rail_end00.nif -Decoding meshes\f\furn_woodbar_01.nif -Decoding meshes\f\furn_de_kegstand.nif -Decoding meshes\f\furn_woodpost_01.nif -Decoding meshes\f\furn_woodpole_01.nif -Decoding meshes\f\furn_tapestry00.nif -Decoding meshes\f\furn_tapestry20.nif -Decoding meshes\f\furn_de_stool_01.nif -Decoding meshes\f\furn_de_stool_02.nif -Decoding meshes\f\furn_de_firepit.nif -Decoding meshes\f\furn_bone_rib_01.nif -Decoding meshes\f\furn_bed_rug_01.nif -Decoding meshes\f\furn_imp_flag_01.nif -Decoding meshes\f\furn_com_bunk_02.nif -Decoding meshes\f\furn_com_bunk_01.nif -Decoding meshes\f\furn_de_lecturn.nif -Decoding meshes\f\furn_guarcart00.nif -Decoding meshes\f\furn_com_planter.nif -Decoding meshes\f\furn_fireplace10.nif -Decoding meshes\f\furn_signbase_02.nif -Decoding meshes\f\furn_com_bar_01.nif -Decoding meshes\f\furn_com_bar_03.nif -Decoding meshes\f\furn_com_bar_05.nif -Decoding meshes\f\furn_tapestry30.nif -Decoding meshes\f\furn_dwrv_well00.nif -Decoding meshes\f\furn_rug_big_09.nif -Decoding meshes\f\furn_rug_big_01.nif -Decoding meshes\f\furn_rug_big_03.nif -Decoding meshes\f\furn_rug_big_05.nif -Decoding meshes\f\furn_rug_big_07.nif -Decoding meshes\f\furn_cot_rug_01.nif -Decoding meshes\f\furn_cot_rug_03.nif -Decoding meshes\f\furn_com_bed_01.nif -Decoding meshes\f\furn_com_bed_03.nif -Decoding meshes\f\furn_com_bed_05.nif -Decoding meshes\f\furn_com_bed_07.nif -Decoding meshes\f\furn_planter_02.nif -Decoding meshes\f\furn_planter_04.nif -Decoding meshes\f\furn_de_rope_04.nif -Decoding meshes\f\furn_de_rope_06.nif -Decoding meshes\f\furn_netramp_01.nif -Decoding meshes\f\furn_rope1_01.nif -Decoding meshes\f\furn_rope2_01.nif -Decoding meshes\f\furn_winekeg00.nif -Decoding meshes\f\furn_bedmat_01.nif -Decoding meshes\f\furn_basin_01.nif -Decoding meshes\f\furn_basket_01.nif -Decoding meshes\f\furn_logpile10.nif -Decoding meshes\f\furn_6th_bell2.nif -Decoding meshes\f\furn_6th_bell4.nif -Decoding meshes\f\furn_6th_bell6.nif -Decoding meshes\f\furn_de_bar_02.nif -Decoding meshes\f\furn_de_bar_04.nif -Decoding meshes\f\furn_de_bar_06.nif -Decoding meshes\f\furn_stool_01.nif -Decoding meshes\f\furn_com_de_01.nif -Decoding meshes\f\furn_ashpit_02.nif -Decoding meshes\f\furn_skull_01.nif -Decoding meshes\f\furn_grill_01.nif -Decoding meshes\f\furn_table_01.nif -Decoding meshes\f\furn_chair_02.nif -Decoding meshes\f\furn_pillow_01.nif -Decoding meshes\f\furn_sconce_01.nif -Decoding meshes\f\furn_banner_01.nif -Decoding meshes\f\furn_cabinet10.nif -Decoding meshes\f\furn_firepit00.nif -Decoding meshes\f\furn_burial10.nif -Decoding meshes\f\furn_burial00.nif -Decoding meshes\f\furn_burial20.nif -Decoding meshes\f\furn_6th_bells.nif -Decoding meshes\f\furn_6th_bell1.nif -Decoding meshes\f\furn_6th_bell3.nif -Decoding meshes\f\furn_6th_bell5.nif -Decoding meshes\f\furn_de_bar_01.nif -Decoding meshes\f\furn_de_bar_03.nif -Decoding meshes\f\furn_de_bar_05.nif -Decoding meshes\f\furn_ashpit_01.nif -Decoding meshes\f\furn_bucket10.nif -Decoding meshes\f\furn_shell10.nif -Decoding meshes\f\furn_shell00.nif -Decoding meshes\f\furn_shell20.nif -Decoding meshes\f\furn_mist512.nif -Decoding meshes\f\furn_rug_04.nif -Decoding meshes\f\furn_rug_01.nif -Decoding meshes\f\furn_rug_03.nif -Decoding meshes\f\furn_rug_02.nif -Decoding meshes\f\furn_torch00.nif -Decoding meshes\f\furn_bone_01.nif -Decoding meshes\f\furn_mist256.nif -Decoding meshes\f\furn_hook_01.nif -Decoding meshes\f\furn_log_04.nif -Decoding meshes\f\furn_log_01.nif -Decoding meshes\f\furn_log_03.nif -Decoding meshes\f\furn_log_02.nif -Decoding meshes\f\furn_table10.nif -Decoding meshes\f\furn_cart00.nif -Decoding meshes\f\furn_anvil00.nif -Decoding meshes\f\furn_well00.nif -Decoding meshes\f\furn_tray_01.nif -Decoding meshes\f\furn_6th_troth_01.nif -Decoding meshes\f\furn_6th_troth_02.nif -Decoding meshes\f\furn_6th_ashaltar.nif -Decoding meshes\f\furn_6th_ashstatue.nif -Decoding meshes\f\furn_6th_ashpillar.nif -Decoding meshes\f\furn_6th_platform.nif -Decoding meshes\w\w_crossbow_steel.nif -Decoding meshes\w\w_corkbulb_arrow.nif -Decoding meshes\w\w_club_daedric.nif -Decoding meshes\w\w_chitin_arrow.nif -Decoding meshes\w\w_chitin_star.nif -Decoding meshes\w\w_chitin_club.nif -Decoding meshes\w\w_chitin_spear.nif -Decoding meshes\w\w_chitin_axe.nif -Decoding meshes\w\w_club_iron.nif -Decoding meshes\a\a_tenpaceboot_gnd.nif -Decoding meshes\a\a_templar_m_f_boot.nif -Decoding meshes\a\a_templar_m_a_boot.nif -Decoding meshes\a\a_templar_m_helmet.nif -Decoding meshes\a\a_templar_w_bracer.nif -Decoding meshes\a\a_templar_m_skins.nif -Decoding meshes\w\w_broadsword_iron.nif -Decoding meshes\w\w_broadsword_ebony.nif -Decoding meshes\m\text_folio_open_04.nif -Decoding meshes\m\text_folio_open_01.nif -Decoding meshes\m\text_folio_open_02.nif -Decoding meshes\m\text_folio_open_03.nif -Decoding meshes\m\text_paper_roll_01.nif -Decoding meshes\m\text_parchment_02.nif -Decoding meshes\m\text_parchment_01.nif -Decoding meshes\xanim_dancinggirl.nif -Decoding meshes\m\text_octavo_05.nif -Decoding meshes\m\text_octavo_07.nif -Decoding meshes\m\text_octavo_01.nif -Decoding meshes\m\text_octavo_03.nif -Decoding meshes\m\text_quarto_03.nif -Decoding meshes\m\text_quarto_01.nif -Decoding meshes\m\text_folio_01.nif -Decoding meshes\m\text_scroll_02.nif -Decoding meshes\m\text_folio_04.nif -Decoding meshes\m\text_folio_03.nif -Decoding meshes\m\text_folio_02.nif -Decoding meshes\m\text_octavo_04.nif -Decoding meshes\m\text_octavo_06.nif -Decoding meshes\m\text_octavo_02.nif -Decoding meshes\m\text_octavo_08.nif -Decoding meshes\m\text_quarto_02.nif -Decoding meshes\m\text_quarto_04.nif -Decoding meshes\m\text_scroll_01.nif -Decoding meshes\m\text_scroll_03.nif -Decoding meshes\m\text_note_02.nif -Decoding meshes\m\text_note_01.nif -Decoding meshes\w\w_bolt_bonemold.nif -Decoding meshes\w\w_bolt_corkbulb.nif -Decoding meshes\w\w_bonemold_arrow.nif -Decoding meshes\w\w_battleaxe_iron.nif -Decoding meshes\a\a_trollbone_cuir.nif -Decoding meshes\a\a_trollbone_helm.nif -Decoding meshes\w\w_bolt_silver.nif -Decoding meshes\w\w_bolt_orcish.nif -Decoding meshes\w\w_bolt_steel.nif -Decoding meshes\w\w_bolt_iron.nif -Decoding meshes\a\a_tenpaceboot.nif -Decoding meshes\w\w_art_queenofbats.nif -Decoding meshes\w\w_art_dagger_fang.nif -Decoding meshes\w\w_art_spear_mercy.nif -Decoding meshes\w\w_art_staff_magnus.nif -Decoding meshes\w\w_art_mace_scourge.nif -Decoding meshes\w\shadowbluntonehand.nif -Decoding meshes\w\shadowblunttwowide.nif -Decoding meshes\w\shadow_towershield.nif -Decoding meshes\w\shadowspeartwowide.nif -Decoding meshes\w\shadowmarksmanbow.nif -Decoding meshes\w\shadowaxetwoclose.nif -Decoding meshes\a\towershield_steel.nif -Decoding meshes\a\towershield_orcish.nif -Decoding meshes\a\towershield_ebony.nif -Decoding meshes\a\towershield_hlaluu.nif -Decoding meshes\a\towershield_glass.nif -Decoding meshes\a\towershield_chitin.nif -Decoding meshes\c\amulet_expensive_1.nif -Decoding meshes\c\amulet_expensive_3.nif -Decoding meshes\c\amulet_expensive_2.nif -Decoding meshes\c\amulet_exquisit_1.nif -Decoding meshes\a\towershield_iron.nif -Decoding meshes\c\amulet_common_4.nif -Decoding meshes\c\amulet_common_2.nif -Decoding meshes\c\amulet_madstone.nif -Decoding meshes\c\amulet_common_5.nif -Decoding meshes\c\amulet_common_1.nif -Decoding meshes\c\amulet_common_3.nif -Decoding meshes\c\amulet_usheeja.nif -Decoding meshes\w\shadowaxeonehand.nif -Decoding meshes\w\shadow_shield.nif -Decoding meshes\w\shadowshield.nif -Decoding meshes\a\a_watchmanshelm.nif -Decoding meshes\w\w_art_azurastar .nif -Decoding meshes\w\w_art_azurastar.nif -Decoding meshes\w\w_art_volendrung.nif -Decoding meshes\w\w_art_keening.nif -Decoding meshes\w\w_art_sunder.nif -Decoding meshes\x\flora_t_podbud_04.nif -Decoding meshes\x\flora_t_podbud_01.nif -Decoding meshes\x\flora_t_podbud_02.nif -Decoding meshes\x\flora_t_podbud_03.nif -Decoding meshes\r\xnetch_betty.nif -Decoding meshes\r\xnetch_bull.nif -Decoding meshes\x\flora_ashtree_03.nif -Decoding meshes\x\flora_ashtree_02.nif -Decoding meshes\x\flora_ashtree_01.nif -Decoding meshes\x\flora_ashtree_07.nif -Decoding meshes\x\flora_ashtree_06.nif -Decoding meshes\x\flora_ashtree_05.nif -Decoding meshes\x\flora_ashtree_04.nif -Decoding meshes\x\flora_ash_log_03.nif -Decoding meshes\x\flora_ash_log_02.nif -Decoding meshes\x\flora_ash_log_01.nif -Decoding meshes\x\flora_ash_log_04.nif -Decoding meshes\r\xleastkagouti.nif -Decoding meshes\e\fire_shield.nif -Decoding meshes\r\xheart_akulakhan.nif -Decoding meshes\xargonian_swimkna.nif -Decoding meshes\r\steam_centurions.nif -Decoding meshes\r\greatbonewalker.nif diff --git a/components/nif/tests/test.sh b/components/nif/tests/test.sh index 2d07708adc..95ecdbfba0 100755 --- a/components/nif/tests/test.sh +++ b/components/nif/tests/test.sh @@ -1,18 +1,15 @@ #!/bin/bash -make || exit +#Script to test all nif files (both loose, and in BSA archives) in data files directory -mkdir -p output +DATAFILESDIR="$1" -PROGS=*_test +find "$DATAFILESDIR" -iname *bsa > nifs.txt +find "$DATAFILESDIR" -iname *nif >> nifs.txt -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done +sed -e 's/.*/\"&\"/' nifs.txt > quoted_nifs.txt + +xargs --arg-file=quoted_nifs.txt ../../../niftest + +rm nifs.txt +rm quoted_nifs.txt diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 31d4e10d64..b366216de1 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -28,6 +28,8 @@ http://www.gnu.org/licenses/ . #include +#include + #include "../nif/niffile.hpp" #include "../nif/node.hpp" #include "../nif/data.hpp" @@ -74,7 +76,7 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) // of the early stages of development. Right now we WANT to catch // every error as early and intrusively as possible, as it's most // likely a sign of incomplete code rather than faulty input. - Nif::NIFFile::ptr pnif (Nif::NIFFile::create (mResourceName.substr(0, mResourceName.length()-7))); + Nif::NIFFilePtr pnif (Nif::Cache::getInstance().load(mResourceName.substr(0, mResourceName.length()-7))); Nif::NIFFile & nif = *pnif.get (); if (nif.numRoots() < 1) { @@ -279,8 +281,6 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int // anything. So don't do anything. if ((flags & 0x800) && !raycasting) { - collide = false; - bbcollide = false; return; } @@ -388,7 +388,7 @@ bool findBoundingBox (const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::V bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) { - Nif::NIFFile::ptr pnif (Nif::NIFFile::create (nifFile)); + Nif::NIFFilePtr pnif (Nif::Cache::getInstance().load(nifFile)); Nif::NIFFile & nif = *pnif.get (); if (nif.numRoots() < 1) diff --git a/components/nifbullet/test/test.cpp b/components/nifbullet/test/test.cpp deleted file mode 100644 index 261edf512a..0000000000 --- a/components/nifbullet/test/test.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "bullet_nif_loader.hpp" -#include "..\nifogre\ogre_nif_loader.hpp" -#include "..\bsa\bsa_archive.hpp" -#include "..\nifogre\ogre_nif_loader.hpp" -#include -#include -#include -#include -#include "BtOgrePG.h" -#include "BtOgreGP.h" -#include "BtOgreExtras.h" - -const char* mesh = "meshes\\x\\ex_hlaalu_b_24.nif"; - -class MyMotionState : public btMotionState { -public: - MyMotionState(const btTransform &initialpos, Ogre::SceneNode *node) { - mVisibleobj = node; - mPos1 = initialpos; - node->setPosition(initialpos.getOrigin().x(),initialpos.getOrigin().y(),initialpos.getOrigin().z()); - } - - virtual ~MyMotionState() { - } - - void setNode(Ogre::SceneNode *node) { - mVisibleobj = node; - } - - virtual void getWorldTransform(btTransform &worldTrans) const { - worldTrans = mPos1; - } - - virtual void setWorldTransform(const btTransform &worldTrans) { - if(NULL == mVisibleobj) return; // silently return before we set a node - btQuaternion rot = worldTrans.getRotation(); - mVisibleobj->setOrientation(rot.w(), rot.x(), rot.y(), rot.z()); - btVector3 pos = worldTrans.getOrigin(); - mVisibleobj->setPosition(pos.x(), pos.y(), pos.z()); - } - -protected: - Ogre::SceneNode *mVisibleobj; - btTransform mPos1; -}; - - -int main() -{ - try - { - //Ogre stuff - - Ogre::Root* pRoot = new Ogre::Root(); - pRoot->showConfigDialog(); - - BulletShapeManager* manag = new BulletShapeManager(); - - Ogre::RenderWindow* win = pRoot->initialise(true,"test"); - Ogre::SceneManager* scmg = pRoot->createSceneManager(Ogre::ST_GENERIC,"MonGestionnaireDeScene"); - Ogre::Camera* pCamera = scmg->createCamera("test"); - Ogre::Viewport* pViewport = win->addViewport(pCamera); - pCamera->setPosition(-50,0,0); - pCamera->setFarClipDistance(10000); - pCamera->setNearClipDistance(1.); - pCamera->lookAt(0,0,0); - //Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/models","FileSystem","General"); - Ogre::ResourceGroupManager::getSingleton().addResourceLocation("","FileSystem","General"); - /*Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/materials/scripts","FileSystem","General"); - Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/materials/textures","FileSystem","General"); - Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/materials/programs","FileSystem","General");*/ - - - //OIS stuff - OIS::ParamList pl; - size_t windowHnd = 0; - std::ostringstream windowHndStr; - win->getCustomAttribute("WINDOW", &windowHnd); - windowHndStr << windowHnd; - pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); - OIS::InputManager *pInputManager = OIS::InputManager::createInputSystem( pl ); - OIS::Mouse *pMouse = static_cast(pInputManager->createInputObject(OIS::OISMouse, false)); - OIS::Keyboard* pKeyboard = static_cast(pInputManager->createInputObject(OIS::OISKeyboard, false)); - unsigned int width, height, depth; - int top, left; - win->getMetrics(width, height, depth, left, top); - const OIS::MouseState &ms = pMouse->getMouseState(); - ms.width = width; - ms.height = height; - - - //Ressources stuff - Bsa::addBSA("Morrowind.bsa"); - //Ogre::ResourceGroupManager::getSingleton().createResourceGroup("general"); - - Ogre::ResourcePtr ptr = BulletShapeManager::getSingleton().getByName(mesh,"General"); - NifBullet::ManualBulletShapeLoader* ShapeLoader = new NifBullet::ManualBulletShapeLoader(); - - ShapeLoader->load(mesh,"General"); - //BulletShapeManager::getSingleton().unload(mesh); - //ShapeLoader->load(mesh,"General"); - - NIFLoader::load(mesh); - - Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); - //BulletShapeManager::getSingleton(). - BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(mesh,"General"); - BulletShapeManager::getSingleton().load(mesh,"General"); - BulletShapeManager::getSingleton().unload(mesh); - BulletShapeManager::getSingleton().load(mesh,"General"); - BulletShapeManager::getSingleton().load(mesh,"General"); - //shape->load(); - //shape->unload(); - //shape->load(); - - //Bullet init - btBroadphaseInterface* broadphase = new btDbvtBroadphase(); - - // Set up the collision configuration and dispatcher - btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(); - btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration); - - // The actual physics solver - btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver; - - // The world. - btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); - dynamicsWorld->setGravity(btVector3(0,-10,0)); - - - - //le sol? - Ogre::SceneNode *node = scmg->getRootSceneNode()->createChildSceneNode("node"); - Ogre::Entity *ent = scmg->createEntity("Mesh1",mesh); - node->attachObject(ent); - MyMotionState* mst = new MyMotionState(btTransform::getIdentity(),node); - - btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(0,mst,shape->Shape,btVector3(0,0,0)); - btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI); - dynamicsWorld->addRigidBody(groundRigidBody); - - //une balle: - Ogre::SceneNode *node2 = scmg->getRootSceneNode()->createChildSceneNode("node2"); - Ogre::Entity *ent2 = scmg->createEntity("Mesh2","ogrehead.mesh"); - node2->attachObject(ent2); - node2->setPosition(0,500,0); - btTransform iT; - iT.setIdentity(); - iT.setOrigin(btVector3(0,5000,0)); - MyMotionState* mst2 = new MyMotionState(btTransform::getIdentity(),node2); - - btSphereShape* sphereshape = new btSphereShape(10); - btRigidBody::btRigidBodyConstructionInfo sphereCI(10,mst2,sphereshape,btVector3(0,0,0)); - btRigidBody* sphere = new btRigidBody(sphereCI); - dynamicsWorld->addRigidBody(sphere); - - - //btOgre! - BtOgre::DebugDrawer* mDebugDrawer = new BtOgre::DebugDrawer(scmg->getRootSceneNode(), dynamicsWorld); - dynamicsWorld->setDebugDrawer(mDebugDrawer); - - Ogre::Timer timer; - timer.reset(); - bool cont = true; - while(cont) - { - if(timer.getMilliseconds()>30) - { - pMouse->capture(); - pKeyboard->capture(); - - Ogre::Vector3 a(0,0,0); - - if(pKeyboard->isKeyDown(OIS::KC_UP)) - { - a = a + Ogre::Vector3(0,0,-20); - } - if(pKeyboard->isKeyDown(OIS::KC_DOWN)) - { - a = a + Ogre::Vector3(0,0,20); - } - if(pKeyboard->isKeyDown(OIS::KC_ESCAPE)) - { - cont = false; - } - OIS::MouseState MS = pMouse->getMouseState(); - pCamera->yaw(-Ogre::Degree(MS.X.rel)); - pCamera->pitch(-Ogre::Degree(MS.Y.rel)); - pCamera->moveRelative(a); - - pRoot->renderOneFrame(); - mDebugDrawer->step(); - timer.reset(); - dynamicsWorld->stepSimulation(0.03); - } - } - std::cout << "cool"; - delete manag; - delete pRoot; - char a; - std::cin >> a; - } - catch(Ogre::Exception& e) - { - std::cout << e.getFullDescription(); - char a; - std::cin >> a; - } -} diff --git a/components/nifcache/nifcache.cpp b/components/nifcache/nifcache.cpp new file mode 100644 index 0000000000..342251dbc8 --- /dev/null +++ b/components/nifcache/nifcache.cpp @@ -0,0 +1,40 @@ +#include "nifcache.hpp" + +namespace Nif +{ + +Cache* Cache::sThis = 0; + +Cache& Cache::getInstance() +{ + assert (sThis); + return *sThis; +} + +Cache* Cache::getInstancePtr() +{ + return sThis; +} + +Cache::Cache() +{ + assert (!sThis); + sThis = this; +} + +NIFFilePtr Cache::load(const std::string &filename) +{ + // TODO: normalize file path to make sure we're not loading the same file twice + + LoadedMap::iterator it = mLoadedMap.find(filename); + if (it != mLoadedMap.end()) + return it->second; + else + { + NIFFilePtr file(new Nif::NIFFile(filename)); + mLoadedMap[filename] = file; + return file; + } +} + +} diff --git a/components/nifcache/nifcache.hpp b/components/nifcache/nifcache.hpp new file mode 100644 index 0000000000..173b918653 --- /dev/null +++ b/components/nifcache/nifcache.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_NIFCACHE_H +#define OPENMW_COMPONENTS_NIFCACHE_H + +#include + +#include + +#include + +namespace Nif +{ + + typedef boost::shared_ptr NIFFilePtr; + + /// @brief A basic resource manager for NIF files + class Cache + { + public: + Cache(); + + /// Queue this file for background loading. A worker thread will start loading the file. + /// To get the loaded NIFFilePtr, use the load method, which will wait until the worker thread is finished + /// and then return the loaded file. + //void loadInBackground (const std::string& file); + + /// Read and parse the given file. May retrieve from cache if this file has been used previously. + /// @note If the file is currently loading in the background, this function will block until + /// the background loading finishes, then return the background loaded file. + /// @note Returns a SharedPtr to the file and the file will stay loaded as long as the user holds on to this pointer. + /// When all external SharedPtrs to a file are released, the cache may decide to unload the file. + NIFFilePtr load (const std::string& filename); + + /// Return instance of this class. + static Cache& getInstance(); + static Cache* getInstancePtr(); + + private: + static Cache* sThis; + + Cache(const Cache&); + Cache& operator =(const Cache&); + + typedef std::map LoadedMap; + + LoadedMap mLoadedMap; + }; + +} + +#endif diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp index 317447d954..cc750ea65e 100644 --- a/components/nifogre/controller.hpp +++ b/components/nifogre/controller.hpp @@ -2,6 +2,7 @@ #define COMPONENTS_NIFOGRE_CONTROLLER_H #include +#include #include namespace NifOgre @@ -10,53 +11,55 @@ namespace NifOgre class ValueInterpolator { protected: - float interpKey(const Nif::FloatKeyList::VecType &keys, float time, float def=0.f) const + float interpKey(const Nif::FloatKeyMap::MapType &keys, float time, float def=0.f) const { if (keys.size() == 0) return def; - if(time <= keys.front().mTime) - return keys.front().mValue; + if(time <= keys.begin()->first) + return keys.begin()->second.mValue; - const Nif::FloatKey* keyArray = keys.data(); - size_t size = keys.size(); - - for (size_t i = 1; i < size; ++i) + Nif::FloatKeyMap::MapType::const_iterator it = keys.lower_bound(time); + if (it != keys.end()) { - const Nif::FloatKey* aKey = &keyArray[i]; + float aTime = it->first; + const Nif::FloatKey* aKey = &it->second; - if(aKey->mTime < time) - continue; + assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - const Nif::FloatKey* aLastKey = &keyArray[i-1]; - float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + Nif::FloatKeyMap::MapType::const_iterator last = --it; + float aLastTime = last->first; + const Nif::FloatKey* aLastKey = &last->second; + + float a = (time - aLastTime) / (aTime - aLastTime); return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); } - - return keys.back().mValue; + else + return keys.rbegin()->second.mValue; } - Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) const + Ogre::Vector3 interpKey(const Nif::Vector3KeyMap::MapType &keys, float time) const { - if(time <= keys.front().mTime) - return keys.front().mValue; + if(time <= keys.begin()->first) + return keys.begin()->second.mValue; - const Nif::Vector3Key* keyArray = keys.data(); - size_t size = keys.size(); - - for (size_t i = 1; i < size; ++i) + Nif::Vector3KeyMap::MapType::const_iterator it = keys.lower_bound(time); + if (it != keys.end()) { - const Nif::Vector3Key* aKey = &keyArray[i]; + float aTime = it->first; + const Nif::Vector3Key* aKey = &it->second; - if(aKey->mTime < time) - continue; + assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - const Nif::Vector3Key* aLastKey = &keyArray[i-1]; - float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + Nif::Vector3KeyMap::MapType::const_iterator last = --it; + float aLastTime = last->first; + const Nif::Vector3Key* aLastKey = &last->second; + + float a = (time - aLastTime) / (aTime - aLastTime); return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); } - - return keys.back().mValue; + else + return keys.rbegin()->second.mValue; } }; @@ -86,6 +89,9 @@ namespace NifOgre { if(mDeltaInput) { + if (mStopTime - mStartTime == 0.f) + return 0.f; + mDeltaCount += value*mFrequency; if(mDeltaCount < mStartTime) mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 44831c13bb..5ca58da3bd 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -1,7 +1,7 @@ #include "material.hpp" #include -#include +#include #include #include @@ -54,49 +54,6 @@ static const char *getTestMode(int mode) return "less_equal"; } - -std::string NIFMaterialLoader::findTextureName(const std::string &filename) -{ - /* Bethesda at some point converted all their BSA - * textures from tga to dds for increased load speed, but all - * texture file name references were kept as .tga. - */ - static const char path[] = "textures\\"; - static const char path2[] = "textures/"; - - std::string texname = filename; - Misc::StringUtils::toLower(texname); - - // Apparently, leading separators are allowed - while (texname.size() && (texname[0] == '/' || texname[0] == '\\')) - texname.erase(0, 1); - - if(texname.compare(0, sizeof(path)-1, path) != 0 && - texname.compare(0, sizeof(path2)-1, path2) != 0) - texname = path + texname; - - Ogre::String::size_type pos = texname.rfind('.'); - if(pos != Ogre::String::npos && texname.compare(pos, texname.length() - pos, ".dds") != 0) - { - // since we know all (GOTY edition or less) textures end - // in .dds, we change the extension - texname.replace(pos, texname.length(), ".dds"); - - // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) - // verify, and revert if false (this call succeeds quickly, but fails slowly) - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texname)) - { - texname = filename; - Misc::StringUtils::toLower(texname); - if(texname.compare(0, sizeof(path)-1, path) != 0 && - texname.compare(0, sizeof(path2)-1, path2) != 0) - texname = path + texname; - } - } - - return texname; -} - Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, @@ -146,7 +103,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Nif::NiSourceTexture *st = texprop->textures[i].texture.getPtr(); if(st->external) - texName[i] = findTextureName(st->filename); + texName[i] = Misc::ResourceHelpers::correctTexturePath(st->filename); else warn("Found internal texture, ignoring."); } diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index abe1982eb9..d485439cf2 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -29,17 +29,9 @@ class NIFMaterialLoader { std::cerr << "NIFMaterialLoader: Warn: " << msg << std::endl; } - static void fail(const std::string &msg) - { - std::cerr << "NIFMaterialLoader: Fail: "<< msg << std::endl; - abort(); - } - static std::map sMaterialMap; public: - static std::string findTextureName(const std::string &filename); - static Ogre::String getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index 8bebe05893..af73df637d 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "material.hpp" @@ -383,7 +384,7 @@ void NIFMeshLoader::loadResource(Ogre::Resource *resource) Ogre::Mesh *mesh = dynamic_cast(resource); OgreAssert(mesh, "Attempting to load a mesh into a non-mesh resource!"); - Nif::NIFFile::ptr nif = Nif::NIFFile::create(mName); + Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(mName); if(mShapeIndex >= nif->numRecords()) { Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index eed320756c..dcc34f6273 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -45,12 +45,32 @@ #include #include +#include #include +#include #include "skeleton.hpp" #include "material.hpp" #include "mesh.hpp" #include "controller.hpp" +#include "particles.hpp" + +namespace +{ + + void getAllNiNodes(const Nif::Node* node, std::vector& out) + { + const Nif::NiNode* ninode = dynamic_cast(node); + if (ninode) + { + out.push_back(ninode); + for (unsigned int i=0; ichildren.length(); ++i) + if (!ninode->children[i].empty()) + getAllNiNodes(ninode->children[i].getPtr(), out); + } + } + +} namespace NifOgre { @@ -115,6 +135,21 @@ ObjectScene::~ObjectScene() mSkelBase = NULL; } +void ObjectScene::setVisibilityFlags (unsigned int flags) +{ + for (std::vector::iterator iter (mEntities.begin()); iter!=mEntities.end(); + ++iter) + (*iter)->setVisibilityFlags (flags); + + for (std::vector::iterator iter (mParticles.begin()); + iter!=mParticles.end(); ++iter) + (*iter)->setVisibilityFlags (flags); + + for (std::vector::iterator iter (mLights.begin()); iter!=mLights.end(); + ++iter) + (*iter)->setVisibilityFlags (flags); +} + void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera) { for (std::vector::iterator it = mBillboardNodes.begin(); it != mBillboardNodes.end(); ++it) @@ -151,7 +186,7 @@ public: const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr(); if (!tex->external) std::cerr << "Warning: Found internal texture, ignoring." << std::endl; - mTextures.push_back(NIFMaterialLoader::findTextureName(tex->filename)); + mTextures.push_back(Misc::ResourceHelpers::correctTexturePath(tex->filename)); } } @@ -202,7 +237,7 @@ public: { private: Ogre::MovableObject* mMovable; - Nif::FloatKeyList mData; + Nif::FloatKeyMap mData; MaterialControllerManager* mMaterialControllerMgr; public: @@ -249,7 +284,7 @@ public: { private: Ogre::MovableObject* mMovable; - Nif::Vector3KeyList mData; + Nif::Vector3KeyMap mData; MaterialControllerManager* mMaterialControllerMgr; public: @@ -374,61 +409,65 @@ public: class Value : public NodeTargetValue, public ValueInterpolator { private: - Nif::QuaternionKeyList mRotations; - Nif::Vector3KeyList mTranslations; - Nif::FloatKeyList mScales; + const Nif::QuaternionKeyMap* mRotations; + const Nif::Vector3KeyMap* mTranslations; + const Nif::FloatKeyMap* mScales; + Nif::NIFFilePtr mNif; // Hold a SharedPtr to make sure key lists stay valid using ValueInterpolator::interpKey; - static Ogre::Quaternion interpKey(const Nif::QuaternionKeyList::VecType &keys, float time) + static Ogre::Quaternion interpKey(const Nif::QuaternionKeyMap::MapType &keys, float time) { - if(time <= keys.front().mTime) - return keys.front().mValue; + if(time <= keys.begin()->first) + return keys.begin()->second.mValue; - const Nif::QuaternionKey* keyArray = keys.data(); - size_t size = keys.size(); - - for (size_t i = 1; i < size; ++i) + Nif::QuaternionKeyMap::MapType::const_iterator it = keys.lower_bound(time); + if (it != keys.end()) { - const Nif::QuaternionKey* aKey = &keyArray[i]; + float aTime = it->first; + const Nif::QuaternionKey* aKey = &it->second; - if(aKey->mTime < time) - continue; + assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - const Nif::QuaternionKey* aLastKey = &keyArray[i-1]; - float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + Nif::QuaternionKeyMap::MapType::const_iterator last = --it; + float aLastTime = last->first; + const Nif::QuaternionKey* aLastKey = &last->second; + + float a = (time - aLastTime) / (aTime - aLastTime); return Ogre::Quaternion::nlerp(a, aLastKey->mValue, aKey->mValue); } - - return keys.back().mValue; + else + return keys.rbegin()->second.mValue; } public: - Value(Ogre::Node *target, const Nif::NiKeyframeData *data) + /// @note The NiKeyFrameData must be valid as long as this KeyframeController exists. + Value(Ogre::Node *target, const Nif::NIFFilePtr& nif, const Nif::NiKeyframeData *data) : NodeTargetValue(target) - , mRotations(data->mRotations) - , mTranslations(data->mTranslations) - , mScales(data->mScales) + , mRotations(&data->mRotations) + , mTranslations(&data->mTranslations) + , mScales(&data->mScales) + , mNif(nif) { } virtual Ogre::Quaternion getRotation(float time) const { - if(mRotations.mKeys.size() > 0) - return interpKey(mRotations.mKeys, time); + if(mRotations->mKeys.size() > 0) + return interpKey(mRotations->mKeys, time); return mNode->getOrientation(); } virtual Ogre::Vector3 getTranslation(float time) const { - if(mTranslations.mKeys.size() > 0) - return interpKey(mTranslations.mKeys, time); + if(mTranslations->mKeys.size() > 0) + return interpKey(mTranslations->mKeys, time); return mNode->getPosition(); } virtual Ogre::Vector3 getScale(float time) const { - if(mScales.mKeys.size() > 0) - return Ogre::Vector3(interpKey(mScales.mKeys, time)); + if(mScales->mKeys.size() > 0) + return Ogre::Vector3(interpKey(mScales->mKeys, time)); return mNode->getScale(); } @@ -440,12 +479,12 @@ public: virtual void setValue(Ogre::Real time) { - if(mRotations.mKeys.size() > 0) - mNode->setOrientation(interpKey(mRotations.mKeys, time)); - if(mTranslations.mKeys.size() > 0) - mNode->setPosition(interpKey(mTranslations.mKeys, time)); - if(mScales.mKeys.size() > 0) - mNode->setScale(Ogre::Vector3(interpKey(mScales.mKeys, time))); + if(mRotations->mKeys.size() > 0) + mNode->setOrientation(interpKey(mRotations->mKeys, time)); + if(mTranslations->mKeys.size() > 0) + mNode->setPosition(interpKey(mTranslations->mKeys, time)); + if(mScales->mKeys.size() > 0) + mNode->setScale(Ogre::Vector3(interpKey(mScales->mKeys, time))); } }; @@ -459,10 +498,10 @@ public: { private: Ogre::MovableObject* mMovable; - Nif::FloatKeyList mUTrans; - Nif::FloatKeyList mVTrans; - Nif::FloatKeyList mUScale; - Nif::FloatKeyList mVScale; + Nif::FloatKeyMap mUTrans; + Nif::FloatKeyMap mVTrans; + Nif::FloatKeyMap mUScale; + Nif::FloatKeyMap mVScale; MaterialControllerManager* mMaterialControllerMgr; public: @@ -762,8 +801,8 @@ class NIFObjectLoader else emitter->setEmissionRate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); - emitter->setTimeToLive(partctrl->lifetime, - partctrl->lifetime + partctrl->lifetimeRandom); + emitter->setTimeToLive(std::max(0.f, partctrl->lifetime), + std::max(0.f, partctrl->lifetime + partctrl->lifetimeRandom)); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y)); emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z)); @@ -801,18 +840,19 @@ class NIFObjectLoader const Nif::NiColorData *clrdata = cl->data.getPtr(); Ogre::ParticleAffector *affector = partsys->addAffector("ColourInterpolator"); - size_t num_colors = std::min(6, clrdata->mKeyList.mKeys.size()); - for(size_t i = 0;i < num_colors;i++) + size_t num_colors = std::min(6, clrdata->mKeyMap.mKeys.size()); + unsigned int i=0; + for (Nif::Vector4KeyMap::MapType::const_iterator it = clrdata->mKeyMap.mKeys.begin(); it != clrdata->mKeyMap.mKeys.end() && i < num_colors; ++it,++i) { Ogre::ColourValue color; - color.r = clrdata->mKeyList.mKeys[i].mValue[0]; - color.g = clrdata->mKeyList.mKeys[i].mValue[1]; - color.b = clrdata->mKeyList.mKeys[i].mValue[2]; - color.a = clrdata->mKeyList.mKeys[i].mValue[3]; + color.r = it->second.mValue[0]; + color.g = it->second.mValue[1]; + color.b = it->second.mValue[2]; + color.a = it->second.mValue[3]; affector->setParameter("colour"+Ogre::StringConverter::toString(i), Ogre::StringConverter::toString(color)); affector->setParameter("time"+Ogre::StringConverter::toString(i), - Ogre::StringConverter::toString(clrdata->mKeyList.mKeys[i].mTime)); + Ogre::StringConverter::toString(it->first)); } } else if(e->recType == Nif::RC_NiParticleRotation) @@ -882,9 +922,28 @@ class NIFObjectLoader { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - // Set the emitter bone as user data on the particle system + // Set the emitter bone(s) as user data on the particle system // so the emitters/affectors can access it easily. - partsys->getUserObjectBindings().setUserAny(Ogre::Any(trgtbone)); + std::vector bones; + if (partctrl->recType == Nif::RC_NiBSPArrayController) + { + std::vector nodes; + getAllNiNodes(partctrl->emitter.getPtr(), nodes); + if (nodes.empty()) + throw std::runtime_error("Emitter for NiBSPArrayController must be a NiNode"); + for (unsigned int i=0; imSkelBase->getSkeleton()->getBone( + NIFSkeletonLoader::lookupOgreBoneHandle(name, nodes[i]->recIndex))); + } + } + else + { + bones.push_back(trgtbone); + } + NiNodeHolder holder; + holder.mBones = bones; + partsys->getUserObjectBindings().setUserAny(Ogre::Any(holder)); createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName()); } @@ -900,8 +959,10 @@ class NIFObjectLoader scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - if (partflags&Nif::NiNode::ParticleFlag_AutoPlay) - partsys->fastForward(1, 0.1); + // Emitting state will be overwritten on frame update by the ParticleSystemController, + // but set up an initial value anyway so the user can fast-forward particle systems + // immediately after creation if desired. + partsys->setEmitting(partflags&Nif::NiNode::ParticleFlag_AutoPlay); } ctrl = ctrl->next; } @@ -913,7 +974,7 @@ class NIFObjectLoader } - static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) + static void createNodeControllers(const Nif::NIFFilePtr& nif, const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) { do { if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) @@ -947,7 +1008,7 @@ class NIFObjectLoader Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, nif, key->data.getPtr())); KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); @@ -987,7 +1048,7 @@ class NIFObjectLoader { std::string::const_iterator last = str.end(); do { - last--; + --last; } while(last != str.begin() && ::isspace(*last)); nextpos = std::distance(str.begin(), ++last); } @@ -1000,13 +1061,13 @@ class NIFObjectLoader } - static void createObjects(const std::string &name, const std::string &group, + static void createObjects(const Nif::NIFFilePtr& nif, const std::string &name, const std::string &group, Ogre::SceneNode *sceneNode, const Nif::Node *node, - ObjectScenePtr scene, int flags, int animflags, int partflags) + ObjectScenePtr scene, int flags, int animflags, int partflags, bool isRootCollisionNode=false) { // Do not create objects for the collision shape (includes all children) if(node->recType == Nif::RC_RootCollisionNode) - return; + isRootCollisionNode = true; // Marker objects: just skip the entire node branch /// \todo don't do this in the editor @@ -1038,11 +1099,7 @@ class NIFObjectLoader { const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - if (scene->mSkelBase) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); - extractTextKeys(tk, scene->mTextKeys[trgtid]); - } + extractTextKeys(tk, scene->mTextKeys); } else if(e->recType == Nif::RC_NiStringExtraData) { @@ -1061,22 +1118,25 @@ class NIFObjectLoader } if(!node->controller.empty()) - createNodeControllers(name, node->controller, scene, animflags); + createNodeControllers(nif, name, node->controller, scene, animflags); - if(node->recType == Nif::RC_NiCamera) + if (!isRootCollisionNode) { - /* Ignored */ - } + if(node->recType == Nif::RC_NiCamera) + { + /* Ignored */ + } - if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) - { - createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags); - } + if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) + { + createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags); + } - if((node->recType == Nif::RC_NiAutoNormalParticles || - node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) - { - createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags); + if((node->recType == Nif::RC_NiAutoNormalParticles || + node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) + { + createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags); + } } const Nif::NiNode *ninode = dynamic_cast(node); @@ -1086,7 +1146,7 @@ class NIFObjectLoader for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createObjects(name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags); + createObjects(nif, name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags, isRootCollisionNode); } } } @@ -1109,7 +1169,7 @@ class NIFObjectLoader public: static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0) { - Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); + Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(name); if(nif->numRoots() < 1) { nif->warn("Found no root nodes in "+name+"."); @@ -1133,13 +1193,13 @@ public: // Create a base skeleton entity if this NIF needs one createSkelBase(name, group, sceneNode->getCreator(), node, scene); } - createObjects(name, group, sceneNode, node, scene, flags, 0, 0); + createObjects(nif, name, group, sceneNode, node, scene, flags, 0, 0); } static void loadKf(Ogre::Skeleton *skel, const std::string &name, TextKeyMap &textKeys, std::vector > &ctrls) { - Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); + Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(name); if(nif->numRoots() < 1) { nif->warn("Found no root nodes in "+name+"."); @@ -1190,7 +1250,7 @@ public: Ogre::Bone *trgtbone = skel->getBone(strdata->string); Ogre::ControllerValueRealPtr srcval; - Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, nif, key->data.getPtr())); Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, false)); ctrls.push_back(Ogre::Controller(srcval, dstval, func)); @@ -1201,7 +1261,7 @@ public: ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); NIFObjectLoader::load(parentNode, scene, name, group); diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index badb6ccd3b..abadd38de5 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -69,7 +69,7 @@ struct ObjectScene { // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. float mMaxControllerLength; - std::map mTextKeys; + TextKeyMap mTextKeys; MaterialControllerManager mMaterialControllerMgr; @@ -82,6 +82,8 @@ struct ObjectScene { // Rotate nodes in mBillboardNodes so they face the given camera void rotateBillboardNodes(Ogre::Camera* camera); + + void setVisibilityFlags (unsigned int flags); }; typedef Ogre::SharedPtr ObjectScenePtr; diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index a1433a6690..316e4edc20 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -16,7 +16,7 @@ class NifEmitter : public Ogre::ParticleEmitter { public: - Ogre::Bone* mEmitterBone; + std::vector mEmitterBones; Ogre::Bone* mParticleBone; Ogre::ParticleSystem* getPartSys() { return mParent; } @@ -130,8 +130,9 @@ public: NifEmitter(Ogre::ParticleSystem *psys) : Ogre::ParticleEmitter(psys) + , mEmitterBones(Ogre::any_cast(psys->getUserObjectBindings().getUserAny()).mBones) { - mEmitterBone = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()); + assert (!mEmitterBones.empty()); Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); mParticleBone = static_cast(tag->getParent()); initDefaults("Nif"); @@ -170,8 +171,10 @@ public: Ogre::Real& timeToLive = particle->timeToLive; #endif + Ogre::Node* emitterBone = mEmitterBones.at((int)(::rand()/(RAND_MAX+1.0)*mEmitterBones.size())); + position = xOff + yOff + zOff + - mParticleBone->_getDerivedOrientation().Inverse() * (mEmitterBone->_getDerivedPosition() + mParticleBone->_getDerivedOrientation().Inverse() * (emitterBone->_getDerivedPosition() - mParticleBone->_getDerivedPosition()); // Generate complex data by reference @@ -181,7 +184,7 @@ public: Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom(); Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom(); direction = (mParticleBone->_getDerivedOrientation().Inverse() - * mEmitterBone->_getDerivedOrientation() * + * emitterBone->_getDerivedOrientation() * Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) * Ogre::Vector3::UNIT_Z; @@ -448,12 +451,14 @@ public: if(life_time-particle_time < mGrowTime) { Ogre::Real scale = (life_time-particle_time) / mGrowTime; + assert (scale >= 0); width *= scale; height *= scale; } if(particle_time < mFadeTime) { Ogre::Real scale = particle_time / mFadeTime; + assert (scale >= 0); width *= scale; height *= scale; } @@ -479,12 +484,14 @@ public: if(life_time-particle_time < mGrowTime) { Ogre::Real scale = (life_time-particle_time) / mGrowTime; + assert (scale >= 0); width *= scale; height *= scale; } if(particle_time < mFadeTime) { Ogre::Real scale = particle_time / mFadeTime; + assert (scale >= 0); width *= scale; height *= scale; } @@ -631,7 +638,9 @@ public: , mPosition(0.0f) , mDirection(0.0f) { - mEmitterBone = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()); + std::vector bones = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()).mBones; + assert (!bones.empty()); + mEmitterBone = bones[0]; Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); mParticleBone = static_cast(tag->getParent()); diff --git a/components/nifogre/particles.hpp b/components/nifogre/particles.hpp index e1f3fd282c..6efc669fe7 100644 --- a/components/nifogre/particles.hpp +++ b/components/nifogre/particles.hpp @@ -38,4 +38,13 @@ class GravityAffectorFactory : public Ogre::ParticleAffectorFactory Ogre::ParticleAffector *createAffector(Ogre::ParticleSystem *psys); }; +struct NiNodeHolder +{ + std::vector mBones; + + // Ogre::Any needs this for some reason + friend std::ostream& operator<<(std::ostream& o, const NiNodeHolder& r) + { return o; } +}; + #endif /* OENGINE_OGRE_PARTICLES_H */ diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index c96f03950f..9e12eec905 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace NifOgre @@ -83,7 +84,7 @@ void NIFSkeletonLoader::loadResource(Ogre::Resource *resource) Ogre::Skeleton *skel = dynamic_cast(resource); OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!"); - Nif::NIFFile::ptr nif(Nif::NIFFile::create(skel->getName())); + Nif::NIFFilePtr nif(Nif::Cache::getInstance().load(skel->getName())); const Nif::Node *node = static_cast(nif->getRoot(0)); try { diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 77dbcb1ee1..40712e2821 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -22,6 +22,7 @@ #include "ogreplugin.hpp" + namespace bfs = boost::filesystem; namespace @@ -44,7 +45,7 @@ namespace LogListener(const std::string &path) : file((bfs::path(path))) { - memset(buffer, sizeof(buffer), 0); + memset(buffer, 0, sizeof(buffer)); } void timestamp() @@ -82,28 +83,34 @@ namespace OgreInit #ifdef ENABLE_PLUGIN_GL , mGLPlugin(NULL) #endif - #ifdef ENABLE_PLUGIN_Direct3D9 + #ifdef ENABLE_PLUGIN_GLES2 + , mGLES2Plugin(NULL) + #endif + + #ifdef ENABLE_PLUGIN_Direct3D9 , mD3D9Plugin(NULL) #endif {} Ogre::Root* OgreInit::init(const std::string &logPath) { + + #ifndef ANDROID // Set up logging first new Ogre::LogManager; Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); - #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 // Use custom listener only on Windows log->addListener(new LogListener(logPath)); - #endif + #endif // Disable logging to cout/cerr log->setDebugOutputEnabled(false); - + #endif mRoot = new Ogre::Root("", "", ""); - #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) + #if defined(ENABLE_PLUGIN_GL) || (ENABLE_PLUGIN_GLES2) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) loadStaticPlugins(); #else loadPlugins(); @@ -133,6 +140,10 @@ namespace OgreInit delete mGLPlugin; mGLPlugin = NULL; #endif + #ifdef ENABLE_PLUGIN_GLES2 + delete mGLES2Plugin; + mGLES2Plugin = NULL; + #endif #ifdef ENABLE_PLUGIN_Direct3D9 delete mD3D9Plugin; mD3D9Plugin = NULL; @@ -157,6 +168,10 @@ namespace OgreInit mGLPlugin = new Ogre::GLPlugin(); mRoot->installPlugin(mGLPlugin); #endif + #ifdef ENABLE_PLUGIN_GLES2 + mGLES2Plugin = new Ogre::GLES2Plugin(); + mRoot->installPlugin(mGLES2Plugin); + #endif #ifdef ENABLE_PLUGIN_Direct3D9 mD3D9Plugin = new Ogre::D3D9Plugin(); mRoot->installPlugin(mD3D9Plugin); @@ -193,7 +208,7 @@ namespace OgreInit pluginDir = Ogre::macFrameworksPath(); #endif #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR_REL; + pluginDir = OGRE_PLUGIN_DIR; #endif } Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); @@ -201,7 +216,8 @@ namespace OgreInit Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); - Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot); + if (!Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot)) + throw std::runtime_error("Required Plugin_ParticleFX for Ogre not found!"); } void OgreInit::loadParticleFactories() diff --git a/components/ogreinit/ogreinit.hpp b/components/ogreinit/ogreinit.hpp index b6fe4631a8..9613421f7b 100644 --- a/components/ogreinit/ogreinit.hpp +++ b/components/ogreinit/ogreinit.hpp @@ -17,6 +17,10 @@ #ifdef ENABLE_PLUGIN_GL # include "OgreGLPlugin.h" #endif +#ifdef ENABLE_PLUGIN_GLES2 +# include "OgreGLES2Plugin.h" +#endif + #ifdef ENABLE_PLUGIN_Direct3D9 # include "OgreD3D9Plugin.h" #endif @@ -52,7 +56,6 @@ namespace OgreInit void loadPlugins(); void loadParticleFactories(); - #ifdef ENABLE_PLUGIN_CgProgramManager Ogre::CgPlugin* mCgPlugin; #endif @@ -65,6 +68,9 @@ namespace OgreInit #ifdef ENABLE_PLUGIN_GL Ogre::GLPlugin* mGLPlugin; #endif + #ifdef ENABLE_PLUGIN_GLES2 + Ogre::GLES2Plugin* mGLES2Plugin; + #endif #ifdef ENABLE_PLUGIN_Direct3D9 Ogre::D3D9Plugin* mD3D9Plugin; #endif diff --git a/components/ogreinit/ogreplugin.cpp b/components/ogreinit/ogreplugin.cpp index 6070c43a87..069b25e7b5 100644 --- a/components/ogreinit/ogreplugin.cpp +++ b/components/ogreinit/ogreplugin.cpp @@ -42,4 +42,4 @@ bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre:: } } -} \ No newline at end of file +} diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 0def0afdb7..5fc2ca3c11 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -79,7 +79,7 @@ void Manager::saveUser(const std::string& file) fout.close(); } -const std::string Manager::getString (const std::string& setting, const std::string& category) +std::string Manager::getString (const std::string& setting, const std::string& category) { if (mNewSettings.find(std::make_pair(category, setting)) != mNewSettings.end()) return mNewSettings[std::make_pair(category, setting)]; @@ -92,17 +92,17 @@ const std::string Manager::getString (const std::string& setting, const std::str return val; } -const float Manager::getFloat (const std::string& setting, const std::string& category) +float Manager::getFloat (const std::string& setting, const std::string& category) { return Ogre::StringConverter::parseReal( getString(setting, category) ); } -const int Manager::getInt (const std::string& setting, const std::string& category) +int Manager::getInt (const std::string& setting, const std::string& category) { return Ogre::StringConverter::parseInt( getString(setting, category) ); } -const bool Manager::getBool (const std::string& setting, const std::string& category) +bool Manager::getBool (const std::string& setting, const std::string& category) { return Ogre::StringConverter::parseBool( getString(setting, category) ); } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index b7c7d59a92..03b0b517ca 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -36,10 +36,10 @@ namespace Settings static const CategorySettingVector apply(); ///< returns the list of changed settings and then clears it - static const int getInt (const std::string& setting, const std::string& category); - static const float getFloat (const std::string& setting, const std::string& category); - static const std::string getString (const std::string& setting, const std::string& category); - static const bool getBool (const std::string& setting, const std::string& category); + static int getInt (const std::string& setting, const std::string& category); + static float getFloat (const std::string& setting, const std::string& category); + static std::string getString (const std::string& setting, const std::string& category); + static bool getBool (const std::string& setting, const std::string& category); static void setInt (const std::string& setting, const std::string& category, const int value); static void setFloat (const std::string& setting, const std::string& category, const float value); diff --git a/components/terrain/backgroundloader.cpp b/components/terrain/backgroundloader.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp index 8769a0d88d..62441c4200 100644 --- a/components/terrain/defaultworld.hpp +++ b/components/terrain/defaultworld.hpp @@ -70,9 +70,9 @@ namespace Terrain private: // Called from a background worker thread - Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); + virtual Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); // Called from the main thread - void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); + virtual void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); Ogre::uint16 mWorkQueueChannel; bool mVisible; diff --git a/apps/openmw/mwrender/terraingrid.cpp b/components/terrain/terraingrid.cpp similarity index 95% rename from apps/openmw/mwrender/terraingrid.cpp rename to components/terrain/terraingrid.cpp index 02899e0c5c..106f00e764 100644 --- a/apps/openmw/mwrender/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -5,12 +5,9 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" +#include "chunk.hpp" -#include - -namespace MWRender +namespace Terrain { TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) @@ -146,8 +143,10 @@ void TerrainGrid::setVisible(bool visible) Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) { - int cellX, cellY; - MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY); + float cellSize = getStorage()->getCellWorldSize(); + + int cellX = std::floor(center.x/cellSize); + int cellY = std::floor(center.y/cellSize); Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); if (it == mGrid.end()) diff --git a/apps/openmw/mwrender/terraingrid.hpp b/components/terrain/terraingrid.hpp similarity index 93% rename from apps/openmw/mwrender/terraingrid.hpp rename to components/terrain/terraingrid.hpp index 1b5250dcfe..7cbf455760 100644 --- a/apps/openmw/mwrender/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,16 +1,12 @@ -#ifndef OPENMW_MWRENDER_TERRAINGRID_H -#define OPENMW_MWRENDER_TERRAINGRID_H +#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H +#define COMPONENTS_TERRAIN_TERRAINGRID_H -#include -#include +#include "world.hpp" +#include "material.hpp" namespace Terrain { class Chunk; -} - -namespace MWRender -{ struct GridElement { diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 49fb9b5c9b..93caeb8df9 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -15,6 +15,8 @@ World::World(Ogre::SceneManager* sceneMgr, , mShaders(shaders) , mAlign(align) , mCache(storage->getCellVertices()) + , mShadows(false) + , mSplitShadows(false) { } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 59a9aff80f..c53cf62b51 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -4,6 +4,7 @@ #include #include #include +#include /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library @@ -329,8 +330,10 @@ ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) return ToUTF8::WINDOWS_1250; else if (encodingName == "win1251") return ToUTF8::WINDOWS_1251; - else + else if (encodingName == "win1252") return ToUTF8::WINDOWS_1252; + else + throw std::runtime_error(std::string("Unknown encoding '") + encodingName + std::string("', see openmw --help for available options.")); } std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) @@ -339,6 +342,8 @@ std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) return "Using Central and Eastern European font encoding."; else if (encodingName == "win1251") return "Using Cyrillic font encoding."; - else + else if (encodingName == "win1252") return "Using default (English) font encoding."; + else + throw std::runtime_error(std::string("Unknown encoding '") + encodingName + std::string("', see openmw --help for available options.")); } diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 5341240afa..51947f6f9e 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -6,6 +6,11 @@ namespace Translation { + Storage::Storage() + : mEncoder(NULL) + { + } + void Storage::loadTranslationData(const Files::Collections& dataFileCollections, const std::string& esmFileName) { diff --git a/components/translation/translation.hpp b/components/translation/translation.hpp index bca9ea255c..6a3f84ba14 100644 --- a/components/translation/translation.hpp +++ b/components/translation/translation.hpp @@ -9,6 +9,7 @@ namespace Translation class Storage { public: + Storage(); void loadTranslationData(const Files::Collections& dataFileCollections, const std::string& esmFileName); diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp new file mode 100644 index 0000000000..e1c1482714 --- /dev/null +++ b/components/widgets/box.cpp @@ -0,0 +1,407 @@ +#include "box.hpp" + +namespace Gui +{ + + void AutoSizedWidget::notifySizeChange (MyGUI::Widget* w) + { + MyGUI::Widget * parent = w->getParent(); + if (parent != 0) + { + if (mExpandDirection.isLeft()) + { + int hdiff = getRequestedSize ().width - w->getSize().width; + w->setPosition(w->getPosition() - MyGUI::IntPoint(hdiff, 0)); + } + w->setSize(getRequestedSize ()); + + while (parent != 0) + { + Box * b = dynamic_cast(parent); + if (b) + b->notifyChildrenSizeChanged(); + else + break; + parent = parent->getParent(); + } + } + } + + + MyGUI::IntSize AutoSizedTextBox::getRequestedSize() + { + return getTextSize(); + } + + void AutoSizedTextBox::setCaption(const MyGUI::UString& _value) + { + TextBox::setCaption(_value); + + notifySizeChange (this); + } + + void AutoSizedTextBox::setPropertyOverride(const std::string& _key, const std::string& _value) + { + if (_key == "ExpandDirection") + { + mExpandDirection = MyGUI::Align::parse (_value); + } + else + { + TextBox::setPropertyOverride (_key, _value); + } + } + + MyGUI::IntSize AutoSizedEditBox::getRequestedSize() + { + if (getAlign().isHStretch()) + throw std::runtime_error("AutoSizedEditBox can't have HStretch align (" + getName() + ")"); + return MyGUI::IntSize(getSize().width, getTextSize().height); + } + + void AutoSizedEditBox::setCaption(const MyGUI::UString& _value) + { + EditBox::setCaption(_value); + + notifySizeChange (this); + } + + void AutoSizedEditBox::setPropertyOverride(const std::string& _key, const std::string& _value) + { + if (_key == "ExpandDirection") + { + mExpandDirection = MyGUI::Align::parse (_value); + } + else + { + EditBox::setPropertyOverride (_key, _value); + } + } + + + MyGUI::IntSize AutoSizedButton::getRequestedSize() + { + MyGUI::IntSize padding(24, 8); + if (isUserString("TextPadding")) + padding = MyGUI::IntSize::parse(getUserString("TextPadding")); + + MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(padding.width,padding.height); + return size; + } + + void AutoSizedButton::setCaption(const MyGUI::UString& _value) + { + Button::setCaption(_value); + + notifySizeChange (this); + } + + void AutoSizedButton::setPropertyOverride(const std::string& _key, const std::string& _value) + { + if (_key == "ExpandDirection") + { + mExpandDirection = MyGUI::Align::parse (_value); + } + else + { + Button::setPropertyOverride (_key, _value); + } + } + + Box::Box() + : mSpacing(4) + , mPadding(0) + , mAutoResize(false) + { + + } + + void Box::notifyChildrenSizeChanged () + { + align(); + } + + bool Box::_setPropertyImpl(const std::string& _key, const std::string& _value) + { + if (_key == "Spacing") + mSpacing = MyGUI::utility::parseValue(_value); + else if (_key == "Padding") + mPadding = MyGUI::utility::parseValue(_value); + else if (_key == "AutoResize") + mAutoResize = MyGUI::utility::parseValue(_value); + else + return false; + + return true; + } + + void HBox::align () + { + unsigned int count = getChildCount (); + size_t h_stretched_count = 0; + int total_width = 0; + int total_height = 0; + std::vector< std::pair > sizes; + sizes.resize(count); + + for (unsigned int i = 0; i < count; ++i) + { + MyGUI::Widget* w = getChildAt(i); + bool hstretch = w->getUserString ("HStretch") == "true"; + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + h_stretched_count += hstretch; + AutoSizedWidget* aw = dynamic_cast(w); + if (aw) + { + sizes[i] = std::make_pair(aw->getRequestedSize (), hstretch); + total_width += aw->getRequestedSize ().width; + total_height = std::max(total_height, aw->getRequestedSize ().height); + } + else + { + sizes[i] = std::make_pair(w->getSize(), hstretch); + total_width += w->getSize().width; + if (!(w->getUserString("VStretch") == "true")) + total_height = std::max(total_height, w->getSize().height); + } + + if (i != count-1) + total_width += mSpacing; + } + + if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height)) + { + setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2)); + return; + } + + + int curX = 0; + for (unsigned int i = 0; i < count; ++i) + { + if (i == 0) + curX += mPadding; + + MyGUI::Widget* w = getChildAt(i); + + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + + bool vstretch = w->getUserString ("VStretch") == "true"; + int max_height = getSize().height - mPadding*2; + int height = vstretch ? max_height : sizes[i].first.height; + + MyGUI::IntCoord widgetCoord; + widgetCoord.left = curX; + widgetCoord.top = mPadding + (getSize().height-mPadding*2 - height) / 2; + int width = sizes[i].second ? sizes[i].first.width + (getSize().width-mPadding*2 - total_width)/h_stretched_count + : sizes[i].first.width; + widgetCoord.width = width; + widgetCoord.height = height; + w->setCoord(widgetCoord); + curX += width; + + if (i != count-1) + curX += mSpacing; + } + } + + void HBox::setPropertyOverride(const std::string& _key, const std::string& _value) + { + if (!Box::_setPropertyImpl (_key, _value)) + MyGUI::Widget::setPropertyOverride(_key, _value); + } + + void HBox::setSize (const MyGUI::IntSize& _value) + { + MyGUI::Widget::setSize (_value); + align(); + } + + void HBox::setCoord (const MyGUI::IntCoord& _value) + { + MyGUI::Widget::setCoord (_value); + align(); + } + + void HBox::onWidgetCreated(MyGUI::Widget* _widget) + { + align(); + } + + MyGUI::IntSize HBox::getRequestedSize () + { + MyGUI::IntSize size(0,0); + for (unsigned int i = 0; i < getChildCount (); ++i) + { + bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; + if (hidden) + continue; + + AutoSizedWidget* w = dynamic_cast(getChildAt(i)); + if (w) + { + MyGUI::IntSize requested = w->getRequestedSize (); + size.height = std::max(size.height, requested.height); + size.width = size.width + requested.width; + if (i != getChildCount()-1) + size.width += mSpacing; + } + else + { + MyGUI::IntSize requested = getChildAt(i)->getSize (); + size.height = std::max(size.height, requested.height); + + if (getChildAt(i)->getUserString("HStretch") != "true") + size.width = size.width + requested.width; + + if (i != getChildCount()-1) + size.width += mSpacing; + } + size.height += mPadding*2; + size.width += mPadding*2; + } + return size; + } + + + + + void VBox::align () + { + unsigned int count = getChildCount (); + size_t v_stretched_count = 0; + int total_height = 0; + int total_width = 0; + std::vector< std::pair > sizes; + sizes.resize(count); + for (unsigned int i = 0; i < count; ++i) + { + MyGUI::Widget* w = getChildAt(i); + + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + + bool vstretch = w->getUserString ("VStretch") == "true"; + v_stretched_count += vstretch; + AutoSizedWidget* aw = dynamic_cast(w); + if (aw) + { + sizes[i] = std::make_pair(aw->getRequestedSize (), vstretch); + total_height += aw->getRequestedSize ().height; + total_width = std::max(total_width, aw->getRequestedSize ().width); + } + else + { + sizes[i] = std::make_pair(w->getSize(), vstretch); + total_height += w->getSize().height; + + if (!(w->getUserString("HStretch") == "true")) + total_width = std::max(total_width, w->getSize().width); + } + + if (i != count-1) + total_height += mSpacing; + } + + if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height)) + { + setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2)); + return; + } + + + int curY = 0; + for (unsigned int i = 0; i < count; ++i) + { + if (i==0) + curY += mPadding; + + MyGUI::Widget* w = getChildAt(i); + + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + + bool hstretch = w->getUserString ("HStretch") == "true"; + int maxWidth = getSize().width - mPadding*2; + int width = hstretch ? maxWidth : sizes[i].first.width; + + MyGUI::IntCoord widgetCoord; + widgetCoord.top = curY; + widgetCoord.left = mPadding + (getSize().width-mPadding*2 - width) / 2; + int height = sizes[i].second ? sizes[i].first.height + (getSize().height-mPadding*2 - total_height)/v_stretched_count + : sizes[i].first.height; + widgetCoord.height = height; + widgetCoord.width = width; + w->setCoord(widgetCoord); + curY += height; + + if (i != count-1) + curY += mSpacing; + } + } + + void VBox::setPropertyOverride(const std::string& _key, const std::string& _value) + { + if (!Box::_setPropertyImpl (_key, _value)) + MyGUI::Widget::setPropertyOverride(_key, _value); + } + + void VBox::setSize (const MyGUI::IntSize& _value) + { + MyGUI::Widget::setSize (_value); + align(); + } + + void VBox::setCoord (const MyGUI::IntCoord& _value) + { + MyGUI::Widget::setCoord (_value); + align(); + } + + MyGUI::IntSize VBox::getRequestedSize () + { + MyGUI::IntSize size(0,0); + for (unsigned int i = 0; i < getChildCount (); ++i) + { + bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; + if (hidden) + continue; + + AutoSizedWidget* w = dynamic_cast(getChildAt(i)); + if (w) + { + MyGUI::IntSize requested = w->getRequestedSize (); + size.width = std::max(size.width, requested.width); + size.height = size.height + requested.height; + if (i != getChildCount()-1) + size.height += mSpacing; + } + else + { + MyGUI::IntSize requested = getChildAt(i)->getSize (); + size.width = std::max(size.width, requested.width); + + if (getChildAt(i)->getUserString("VStretch") != "true") + size.height = size.height + requested.height; + + if (i != getChildCount()-1) + size.height += mSpacing; + } + size.height += mPadding*2; + size.width += mPadding*2; + } + return size; + } + + void VBox::onWidgetCreated(MyGUI::Widget* _widget) + { + align(); + } + +} diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp new file mode 100644 index 0000000000..ccdc5784b9 --- /dev/null +++ b/components/widgets/box.hpp @@ -0,0 +1,120 @@ +#ifndef OPENMW_WIDGETS_BOX_H +#define OPENMW_WIDGETS_BOX_H + +#include +#include +#include +#include + +namespace Gui +{ + + class AutoSizedWidget + { + public: + AutoSizedWidget() : mExpandDirection(MyGUI::Align::Right) {} + + virtual MyGUI::IntSize getRequestedSize() = 0; + + protected: + void notifySizeChange(MyGUI::Widget* w); + + MyGUI::Align mExpandDirection; + }; + + class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox + { + MYGUI_RTTI_DERIVED( AutoSizedTextBox ) + + public: + virtual MyGUI::IntSize getRequestedSize(); + virtual void setCaption(const MyGUI::UString& _value); + + protected: + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + }; + + class AutoSizedEditBox : public AutoSizedWidget, public MyGUI::EditBox + { + MYGUI_RTTI_DERIVED( AutoSizedEditBox ) + + public: + virtual MyGUI::IntSize getRequestedSize(); + virtual void setCaption(const MyGUI::UString& _value); + + protected: + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + }; + + class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button + { + MYGUI_RTTI_DERIVED( AutoSizedButton ) + + public: + virtual MyGUI::IntSize getRequestedSize(); + virtual void setCaption(const MyGUI::UString& _value); + + protected: + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + }; + + /** + * @brief A container widget that automatically sizes its children + * @note the box being an AutoSizedWidget as well allows to put boxes inside a box + */ + class Box : public AutoSizedWidget + { + public: + Box(); + + void notifyChildrenSizeChanged(); + + protected: + virtual void align() = 0; + + virtual bool _setPropertyImpl(const std::string& _key, const std::string& _value); + + int mSpacing; // how much space to put between elements + + int mPadding; // outer padding + + bool mAutoResize; // auto resize the box so that it exactly fits all elements + }; + + class HBox : public Box, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED( HBox ) + + public: + virtual void setSize (const MyGUI::IntSize &_value); + virtual void setCoord (const MyGUI::IntCoord &_value); + + protected: + virtual void align(); + virtual MyGUI::IntSize getRequestedSize(); + + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + + virtual void onWidgetCreated(MyGUI::Widget* _widget); + }; + + class VBox : public Box, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED( VBox) + + public: + virtual void setSize (const MyGUI::IntSize &_value); + virtual void setCoord (const MyGUI::IntCoord &_value); + + protected: + virtual void align(); + virtual MyGUI::IntSize getRequestedSize(); + + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + + virtual void onWidgetCreated(MyGUI::Widget* _widget); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/imagebutton.cpp b/components/widgets/imagebutton.cpp similarity index 90% rename from apps/openmw/mwgui/imagebutton.cpp rename to components/widgets/imagebutton.cpp index f2565f5c00..1cd8829751 100644 --- a/apps/openmw/mwgui/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -1,8 +1,8 @@ #include "imagebutton.hpp" -#include +#include -namespace MWGui +namespace Gui { void ImageButton::setPropertyOverride(const std::string &_key, const std::string &_value) @@ -44,8 +44,8 @@ namespace MWGui MyGUI::IntSize ImageButton::getRequestedSize(bool logError) { - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName(mImageNormal); - if (texture.isNull()) + MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(mImageNormal); + if (!texture) { if (logError) std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; diff --git a/apps/openmw/mwgui/imagebutton.hpp b/components/widgets/imagebutton.hpp similarity index 98% rename from apps/openmw/mwgui/imagebutton.hpp rename to components/widgets/imagebutton.hpp index f4191a3a54..bed6a27940 100644 --- a/apps/openmw/mwgui/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -3,7 +3,7 @@ #include -namespace MWGui +namespace Gui { /** diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp new file mode 100644 index 0000000000..28271e87df --- /dev/null +++ b/components/widgets/list.cpp @@ -0,0 +1,160 @@ +#include "list.hpp" + +#include +#include +#include +#include + +namespace Gui +{ + + MWList::MWList() : + mClient(0) + , mScrollView(0) + , mItemHeight(0) + { + } + + void MWList::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mClient, "Client"); + if (mClient == 0) + mClient = this; + + mScrollView = mClient->createWidgetReal( + "MW_ScrollView", MyGUI::FloatCoord(0.0, 0.0, 1.0, 1.0), + MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView"); + } + + void MWList::addItem(const std::string& name) + { + mItems.push_back(name); + } + + void MWList::addSeparator() + { + mItems.push_back(""); + } + + void MWList::adjustSize() + { + redraw(); + } + + void MWList::redraw(bool scrollbarShown) + { + const int _scrollBarWidth = 20; // fetch this from skin? + const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; + const int spacing = 3; + size_t viewPosition = -mScrollView->getViewOffset().top; + + while (mScrollView->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); + } + + mItemHeight = 0; + int i=0; + for (std::vector::const_iterator it=mItems.begin(); + it!=mItems.end(); ++it) + { + if (*it != "") + { + if (mListItemSkin.empty()) + return; + MyGUI::Button* button = mScrollView->createWidget( + mListItemSkin, MyGUI::IntCoord(0, mItemHeight, mScrollView->getSize().width - scrollBarWidth - 2, 24), + MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + (*it)); + button->setCaption((*it)); + button->getSubWidgetText()->setWordWrap(true); + button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); + button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheel); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); + + int height = button->getTextSize().height; + button->setSize(MyGUI::IntSize(button->getSize().width, height)); + button->setUserData(i); + + mItemHeight += height + spacing; + } + else + { + MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", + MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth() - scrollBarWidth - 4, 18), + MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); + separator->setNeedMouseFocus(false); + + mItemHeight += 18 + spacing; + } + ++i; + } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height)); + mScrollView->setVisibleVScroll(true); + + if (!scrollbarShown && mItemHeight > mClient->getSize().height) + redraw(true); + + size_t viewRange = mScrollView->getCanvasSize().height; + if(viewPosition > viewRange) + viewPosition = viewRange; + mScrollView->setViewOffset(MyGUI::IntPoint(0, viewPosition * -1)); + } + + void MWList::setPropertyOverride(const std::string &_key, const std::string &_value) + { + if (_key == "ListItemSkin") + mListItemSkin = _value; + else + Base::setPropertyOverride(_key, _value); + } + + unsigned int MWList::getItemCount() + { + return mItems.size(); + } + + std::string MWList::getItemNameAt(unsigned int at) + { + assert(at < mItems.size() && "List item out of bounds"); + return mItems[at]; + } + + void MWList::removeItem(const std::string& name) + { + assert( std::find(mItems.begin(), mItems.end(), name) != mItems.end() ); + mItems.erase( std::find(mItems.begin(), mItems.end(), name) ); + } + + void MWList::clear() + { + mItems.clear(); + } + + void MWList::onMouseWheel(MyGUI::Widget* _sender, int _rel) + { + //NB view offset is negative + if (mScrollView->getViewOffset().top + _rel*0.3 > 0) + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3)); + } + + void MWList::onItemSelected(MyGUI::Widget* _sender) + { + std::string name = _sender->castType()->getCaption(); + int id = *_sender->getUserData(); + eventItemSelected(name, id); + eventWidgetSelected(_sender); + } + + MyGUI::Widget* MWList::getItemWidget(const std::string& name) + { + return mScrollView->findWidget (getName() + "_item_" + name); + } + +} diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp new file mode 100644 index 0000000000..093cd8c186 --- /dev/null +++ b/components/widgets/list.hpp @@ -0,0 +1,70 @@ +#ifndef MWGUI_LIST_HPP +#define MWGUI_LIST_HPP + +#include + +namespace Gui +{ + /** + * \brief a very simple list widget that supports word-wrapping entries + * \note if the width or height of the list changes, you must call adjustSize() method + */ + class MWList : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(MWList) + public: + MWList(); + + typedef MyGUI::delegates::CMultiDelegate2 EventHandle_StringInt; + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Widget; + + /** + * Event: Item selected with the mouse. + * signature: void method(std::string itemName, int index) + */ + EventHandle_StringInt eventItemSelected; + + /** + * Event: Item selected with the mouse. + * signature: void method(MyGUI::Widget* sender) + */ + EventHandle_Widget eventWidgetSelected; + + + /** + * Call after the size of the list changed, or items were inserted/removed + */ + void adjustSize(); + + void addItem(const std::string& name); + void addSeparator(); ///< add a seperator between the current and the next item. + void removeItem(const std::string& name); + unsigned int getItemCount(); + std::string getItemNameAt(unsigned int at); ///< \attention if there are separators, this method will return "" at the place where the separator is + void clear(); + + MyGUI::Widget* getItemWidget(const std::string& name); + ///< get widget for an item name, useful to set up tooltip + + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + + protected: + void initialiseOverride(); + + void redraw(bool scrollbarShown = false); + + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onItemSelected(MyGUI::Widget* _sender); + + private: + MyGUI::ScrollView* mScrollView; + MyGUI::Widget* mClient; + std::string mListItemSkin; + + std::vector mItems; + + int mItemHeight; // height of all items + }; +} + +#endif diff --git a/components/widgets/numericeditbox.cpp b/components/widgets/numericeditbox.cpp new file mode 100644 index 0000000000..5361b31271 --- /dev/null +++ b/components/widgets/numericeditbox.cpp @@ -0,0 +1,74 @@ +#include "numericeditbox.hpp" + +#include + +namespace Gui +{ + + void NumericEditBox::initialiseOverride() + { + Base::initialiseOverride(); + eventEditTextChange += MyGUI::newDelegate(this, &NumericEditBox::onEditTextChange); + + mValue = 0; + setCaption("0"); + } + + void NumericEditBox::shutdownOverride() + { + Base::shutdownOverride(); + eventEditTextChange -= MyGUI::newDelegate(this, &NumericEditBox::onEditTextChange); + } + + void NumericEditBox::onEditTextChange(MyGUI::EditBox *sender) + { + std::string newCaption = sender->getCaption(); + if (newCaption.empty()) + { + return; + } + + try + { + mValue = boost::lexical_cast(newCaption); + int capped = std::min(mMaxValue, std::max(mValue, mMinValue)); + if (capped != mValue) + { + mValue = capped; + setCaption(MyGUI::utility::toString(mValue)); + } + } + catch (boost::bad_lexical_cast&) + { + setCaption(MyGUI::utility::toString(mValue)); + } + + eventValueChanged(mValue); + } + + void NumericEditBox::setValue(int value) + { + if (value != mValue) + { + setCaption(MyGUI::utility::toString(value)); + mValue = value; + } + } + + void NumericEditBox::setMinValue(int minValue) + { + mMinValue = minValue; + } + + void NumericEditBox::setMaxValue(int maxValue) + { + mMaxValue = maxValue; + } + + void NumericEditBox::onKeyLostFocus(MyGUI::Widget* _new) + { + Base::onKeyLostFocus(_new); + setCaption(MyGUI::utility::toString(mValue)); + } + +} diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp new file mode 100644 index 0000000000..bbc0e48f47 --- /dev/null +++ b/components/widgets/numericeditbox.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_NUMERIC_EDIT_BOX_H +#define OPENMW_NUMERIC_EDIT_BOX_H + +#include + +namespace Gui +{ + + /** + * @brief A variant of the EditBox that only allows integer inputs + */ + class NumericEditBox : public MyGUI::EditBox + { + MYGUI_RTTI_DERIVED(NumericEditBox) + + public: + NumericEditBox() + : mValue(0), mMinValue(std::numeric_limits().min()), + mMaxValue(std::numeric_limits().max()) + {} + + void initialiseOverride(); + void shutdownOverride(); + + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ValueChanged; + EventHandle_ValueChanged eventValueChanged; + + /// @note Does not trigger eventValueChanged + void setValue (int value); + + void setMinValue(int minValue); + void setMaxValue(int maxValue); + private: + void onEditTextChange(MyGUI::EditBox* sender); + void onKeyLostFocus(MyGUI::Widget* _new); + + int mValue; + + int mMinValue; + int mMaxValue; + }; + +} + +#endif diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp new file mode 100644 index 0000000000..160698c8ec --- /dev/null +++ b/components/widgets/tags.cpp @@ -0,0 +1,57 @@ +#include "tags.hpp" + +#include + +namespace Gui +{ + +bool replaceTag(const MyGUI::UString& tag, MyGUI::UString& out, const std::map& fallbackSettings) +{ + std::string fontcolour = "fontcolour="; + size_t fontcolourLength = fontcolour.length(); + + std::string fontcolourhtml = "fontcolourhtml="; + size_t fontcolourhtmlLength = fontcolourhtml.length(); + + if (tag.compare(0, fontcolourLength, fontcolour) == 0) + { + std::string fallbackName = "FontColor_color_" + tag.substr(fontcolourLength); + std::map::const_iterator it = fallbackSettings.find(fallbackName); + if (it == fallbackSettings.end()) + throw std::runtime_error("Unknown fallback name: " + fallbackName); + std::string str = it->second; + + std::string ret[3]; + unsigned int j=0; + for(unsigned int i=0;i::const_iterator it = fallbackSettings.find(fallbackName); + if (it == fallbackSettings.end()) + throw std::runtime_error("Unknown fallback name: " + fallbackName); + std::string str = it->second; + + std::string ret[3]; + unsigned int j=0; + for(unsigned int i=0;i +#include +#include + +namespace Gui +{ + +/// Try to replace a tag. Returns true on success and writes the result to \a out. +bool replaceTag (const MyGUI::UString& tag, MyGUI::UString& out, const std::map& fallbackSettings); + +} + +#endif diff --git a/components/widgets/widgets.cpp b/components/widgets/widgets.cpp new file mode 100644 index 0000000000..b35dc88a4d --- /dev/null +++ b/components/widgets/widgets.cpp @@ -0,0 +1,25 @@ +#include "widgets.hpp" + +#include + +#include "list.hpp" +#include "numericeditbox.hpp" +#include "box.hpp" +#include "imagebutton.hpp" + +namespace Gui +{ + + void registerAllWidgets() + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + } + +} diff --git a/components/widgets/widgets.hpp b/components/widgets/widgets.hpp new file mode 100644 index 0000000000..d171321357 --- /dev/null +++ b/components/widgets/widgets.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_COMPONENTS_WIDGETS_H +#define OPENMW_COMPONENTS_WIDGETS_H + +namespace Gui +{ + + /// Register all widgets from this component with MyGUI's factory manager. + void registerAllWidgets(); + +} + +#endif diff --git a/credits.txt b/credits.txt index 5c757f957a..ab83b93beb 100644 --- a/credits.txt +++ b/credits.txt @@ -30,22 +30,28 @@ darkf Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) Douglas Mencken (dougmencken) +dreamer-dead Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) +eroen Fil Krynicki (filkry) +Gašper Sedej gugus/gus Hallfaer Tuilinn Jacob Essex (Yacoby) Jannik Heller (scrawl) Jason Hooks (jhooks) +jeaye Jeffrey Haines (Jyby) Joel Graff (graffy) John Blomberg (fstp) +Jordan Ayers Jordan Milne Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) +Kevin Poitra (PuppyKevin) Lars Söderberg (Lazaroth) lazydev Leon Saunders (emoose) @@ -54,28 +60,40 @@ Manuel Edelmann (vorenon) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) +Marco Melletti (mellotanica) Mateusz Kołaczek (PL_kolek) megaton Michael Hogan (Xethik) Michael Mc Donnell Michael Papageorgiou (werdanith) Michał Bień (Glorf) +Miroslav Puda (pakanek) +MiroslavR Nathan Jeffords (blunted2night) Nikolay Kasyanov (corristo) +nobrakal Nolan Poe (nopoe) Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) Radu-Marius Popovici (rpopovici) +riothamus +Robert MacGregor (Ragora) +Rohit Nirmal Roman Melnik (Kromgart) Roman Proskuryakov (humbug) +sandstranger Sandy Carter (bwrsandman) +Scott Howard Sebastian Wick (swick) Sergey Shambir sir_herrbatka +Stefan Galowicz (bogglez) +Stanislav Bobrov (Jiub) Sylvain Thesnieres (Garvek) Thomas Luppi (Digmaster) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) +Vincent Heuken Packagers: Alexander Olofsson (Ace) - Windows @@ -87,24 +105,23 @@ Kenny Armstrong (artorius) - Fedora Linux Nikolay Kasyanov (corristo) - Mac OS X Sandy Carter (bwrsandman) - Arch Linux - Public Relations and Translations: Alex McKibben (WeirdSexy) - Podcaster Artem Kotsynyak (greye) - Russian News Writer Jim Clauwaert (Zedd) - Public Outreach Julien Voisin (jvoisin/ap0) - French News Writer +Tom Koenderink (Okulo) - English News Writer Lukasz Gromanowski (lgro) - English News Writer Mickey Lyle (raevol) - Release Manager Pithorn - Chinese News Writer sir_herrbatka - Polish News Writer - +Dawid Lakomy (Vedyimyn) - Polish News Writer Website: -Lukasz Gromanowski (lgro) - Website Administrator +Lukasz Gromanowski (Lgro) - Website Administrator Ryan Sardonic (Wry) - Wiki Editor sir_herrbatka - Forum Administrator - Formula Research: Hrnchamd Epsilon @@ -116,15 +133,15 @@ Myckel natirips Sadler - Artwork: Necrod - OpenMW Logo Mickey Lyle (raevol) - Wordpress Theme -Okulo - OpenMW Editor Icons +Tom Koenderink (Okulo), SirHerrbatka, crysthala - OpenMW Editor Icons Inactive Contributors: Ardekantur Armin Preiml +Berulacks Carl Maxwell Diggory Hardy Dmitry Marakasov (AMDmi3) @@ -138,6 +155,7 @@ Kingpix Lordrea Michal Sciubidlo Nicolay Korslund +Nekochan pchan3 penguinroad psi29a diff --git a/docs/Doxyfile b/docs/Doxyfile index 156e23abd2..a74fc7b0aa 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,90 +1,122 @@ -# Doxyfile 1.5.8 +# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project +# doxygen (www.doxygen.org) for a project. # -# All text after a hash (#) is considered a comment and will be ignored +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. # The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. PROJECT_NAME = OpenMW -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. PROJECT_NUMBER = -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. OUTPUT_DIRECTORY = Doxygen -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. CREATE_SUBDIRS = NO +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, -# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, -# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, -# Spanish, Swedish, and Ukrainian. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. +# The default value is: YES. REPEAT_BRIEF = YES -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ @@ -99,8 +131,9 @@ ABBREVIATE_BRIEF = "The $name class" \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# doxygen will generate a detailed section even if there is only a brief # description. +# The default value is: NO. ALWAYS_DETAILED_SEC = NO @@ -108,152 +141,207 @@ ALWAYS_DETAILED_SEC = NO # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. +# The default value is: NO. INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. FULL_PATH_NAMES = YES -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. SEPARATE_MEMBER_PAGES = NO -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. -TAB_SIZE = 8 +TAB_SIZE = 4 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. ALIASES = -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO -# Doxygen selects the parser to use depending on the extension of the files it parses. -# With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this tag. -# The format is ext=language, where ext is a file extension, and language is one of -# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, -# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. EXTENSION_MAPPING = -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. -BUILTIN_STL_SUPPORT = NO +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. +# The default value is: NO. CPP_CLI_SUPPORT = NO -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. SIP_SUPPORT = NO -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. IDL_PROPERTY_SUPPORT = YES @@ -261,317 +349,420 @@ IDL_PROPERTY_SUPPORT = YES # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. +# The default value is: NO. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. SUBGROUPING = YES -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. TYPEDEF_HIDES_STRUCT = NO -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penality. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. -SYMBOL_CACHE_SIZE = 0 +LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. EXTRACT_PRIVATE = YES -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. EXTRACT_STATIC = YES -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. EXTRACT_LOCAL_METHODS = YES # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. EXTRACT_ANON_NSPACES = YES -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. +# The default value is: system dependent. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. SHOW_INCLUDE_FILES = YES -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. SORT_BRIEF_DOCS = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. SORT_GROUP_NAMES = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. SORT_BY_SCOPE_NAME = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. SHOW_USED_FILES = YES -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. SHOW_FILES = YES -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. FILE_VERSION_FILTER = -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by -# doxygen. The layout file controls the global structure of the generated output files -# in an output format independent way. The create the layout file that represents -# doxygen's defaults, run doxygen with the -l option. You can optionally specify a -# file name after the option, if omitted DoxygenLayout.xml will be used as the name -# of the layout file. +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. LAYOUT_FILE = +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. WARN_IF_UNDOCUMENTED = NO -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. WARN_IF_DOC_ERROR = YES -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. WARN_NO_PARAMDOC = NO -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. INPUT = apps \ components \ @@ -579,19 +770,22 @@ INPUT = apps \ docs # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. FILE_PATTERNS = *.c \ *.cc \ @@ -624,29 +818,34 @@ FILE_PATTERNS = *.c \ *.vhd \ *.vhdl -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. RECURSIVE = YES -# The EXCLUDE tag can be used to specify files and/or directories that should +# The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. EXCLUDE = -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded # from the input. +# The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = @@ -655,578 +854,1076 @@ EXCLUDE_PATTERNS = # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be -# ignored. +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. FILTER_SOURCE_FILES = NO +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. SOURCE_BROWSER = NO -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. REFERENCED_BY_RELATION = NO -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. REFERENCES_RELATION = NO -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentation. +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. ALPHABETICAL_INDEX = NO -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a # standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. -HTML_ALIGN_MEMBERS = YES +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. -DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_FEEDNAME = "OpenMW Documentation" -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. -DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_BUNDLE_ID = org.openmw -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.openmw + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = OpenMW + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be # written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER -# are set, an additional index file will be generated that can be used as input for -# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated -# HTML documentation. +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. -QHP_NAMESPACE = +QHP_NAMESPACE = org.openmw -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. -# For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see -# Qt Help Project / Custom Filters. +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's -# filter section matches. -# Qt Help Project / Filter Attributes. +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.openmw + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to FRAME, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. Other possible values -# for this tag are: HIERARCHIES, which will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list; -# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which -# disables this behavior completely. For backwards compatibility with previous -# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE -# respectively. - -GENERATE_TREEVIEW = NONE - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = YES + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /