diff --git a/.gitignore b/.gitignore index 88f591aae9..e1abcaa639 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ CMakeCache.txt cmake_install.cmake Makefile makefile -build +build* prebuilt ## doxygen diff --git a/.travis.yml b/.travis.yml index 69879bd781..0910ac5462 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,8 +40,8 @@ script: - cd ./build - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi notifications: recipients: - corrmage+travis-ci@gmail.com diff --git a/AUTHORS.md b/AUTHORS.md index e968d1d01a..292c1e9e3d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -13,6 +13,7 @@ Programmers Marc Zinnschlag (Zini) - Lead Programmer/Project Manager Adam Hogan (aurix) + Aesylwinn Aleksandar Jovanov Alex Haddad (rainChu) Alex McKibben (WeirdSexy) @@ -74,6 +75,7 @@ Programmers Marco Melletti (mellotanica) Marco Schulze Mateusz Kołaczek (PL_kolek) + Mateusz Malisz (malice) megaton Michael Hogan (Xethik) Michael Mc Donnell @@ -88,6 +90,7 @@ Programmers Nikolay Kasyanov (corristo) nobrakal Nolan Poe (nopoe) + Paul Cercueil (pcercuei) Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) pkubik diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e7dc25c2..43e598566c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,232 @@ +0.37.0 +------ + + Bug #385: Light emitting objects have a too short distance of activation + Bug #455: Animation doesn't resize creature's bounding box + Bug #602: Only collision model is updated when modifying objects trough console + Bug #639: Sky horizon at nighttime + Bug #672: incorrect trajectory of the moons + Bug #814: incorrect NPC width + Bug #827: Inaccurate raycasting for dead actors + Bug #996: Can see underwater clearly when at right height/angle + Bug #1317: Erene Llenim in Seyda Neen does not walk around + Bug #1330: Cliff racers fail to hit the player + Bug #1366: Combat AI can't aim down (in order to hit small creatures) + Bug #1511: View distance while under water is much too short + Bug #1563: Terrain positioned incorrectly and appears to vibrate in far-out cells + Bug #1612: First person models clip through walls + Bug #1647: Crash switching from full screen to windows mode - D3D9 + Bug #1650: No textures with directx on windows + Bug #1730: Scripts names starting with digit(s) fail to compile + Bug #1738: Socucius Ergalla's greetings are doubled during the tutorial + Bug #1784: First person weapons always in the same position + Bug #1813: Underwater flora lighting up entire area. + Bug #1871: Handle controller extrapolation flags + Bug #1921: Footstep frequency and velocity do not immediately update when speed attribute changes + Bug #2001: OpenMW crashes on start with OpenGL 1.4 drivers + Bug #2014: Antialiasing setting does nothing on Linux + Bug #2037: Some enemies attack the air when spotting the player + Bug #2052: NIF rotation matrices including scales are not supported + Bug #2062: Crank in Old Mournhold: Forgotten Sewer turns about the wrong axis + Bug #2111: Raindrops in front of fire look wrong + Bug #2140: [OpenGL] Water effects, flames and parts of creatures solid black when observed through brazier flame + Bug #2147: Trueflame and Hopesfire flame effects not properly aligned with blade + Bug #2148: Verminous fabricants have little coloured box beneath their feet + Bug #2149: Sparks in Clockwork City should bounce off the floor + Bug #2151: Clockwork City dicer trap doesn't activate when you're too close + Bug #2186: Mini map contains scrambled pixels that cause the mini map to flicker + Bug #2187: NIF file with more than 255 NiBillboardNodes does not load + Bug #2191: Editor: Crash when trying to view cell in render view in OpenCS + Bug #2270: Objects flicker transparently + Bug #2280: Latest 32bit windows build of openmw runns out of vram + Bug #2281: NPCs don't scream when they die + Bug #2286: Jumping animation restarts when equipping mid-air + Bug #2287: Weapon idle animation stops when turning + Bug #2355: Light spell doesn't work in 1st person view + Bug #2362: Lantern glas opaque to flame effect from certain viewing angles + Bug #2364: Light spells are not as bright as in Morrowind + Bug #2383: Remove the alpha testing override list + Bug #2436: Crash on entering cell "Tower of Tel Fyr, Hall of Fyr" + Bug #2457: Player followers should not report crimes + Bug #2458: crash in some fighting situations + Bug #2464: Hiding an emitter node should make that emitter stop firing particles + Bug #2466: Can't load a save created with OpenMW-0.35.0-win64 + Bug #2468: music from title screen continues after loading savegame + Bug #2494: Map not consistent between saves + Bug #2504: Dialog scroll should always start at the top + Bug #2506: Editor: Undo/Redo shortcuts do not work in script editor + Bug #2513: Mannequins in mods appear as dead bodies + Bug #2524: Editor: TopicInfo "custom" condition section is missing + Bug #2540: Editor: search and verification result table can not be sorted by clicking on the column names + Bug #2543: Editor: there is a problem with spell effects + Bug #2544: Editor fails to save NPC information correctly. + Bug #2545: Editor: delete record in Objects (referenceables) table messes up data + Bug #2546: Editor: race base attributes and skill boni are not displayed, thus not editable + Bug #2547: Editor: some NPC data is not displayed, thus not editable + Bug #2551: Editor: missing data in cell definition + Bug #2553: Editor: value filter does not work for float values + Bug #2555: Editor: undo leaves the record status as Modified + Bug #2559: Make Detect Enchantment marks appear on top of the player arrow + Bug #2563: position consoling npc doesn't work without cell reload + Bug #2564: Editor: Closing a subview from code does not clean up properly and will lead to crash on opening the next subview + Bug #2568: Editor: Setting default window size is ignored + Bug #2569: Editor: saving from an esp to omwaddon file results in data loss for TopicInfo + Bug #2575: Editor: Deleted record (with Added (ModifiedOnly) status) remains in the Dialog SubView + Bug #2576: Editor: Editor doesn't scroll to a newly opened subview, when ScrollBar Only mode is active + Bug #2578: Editor: changing Level or Reputation of an NPC crashes the editor + Bug #2579: Editor: filters not updated when adding or cloning records + Bug #2580: Editor: omwaddon makes OpenMW crash + Bug #2581: Editor: focus problems in edit subviews single- and multiline input fields + Bug #2582: Editor: object verifier should check for non-existing scripts being referenced + Bug #2583: Editor: applying filter to TopicInfo on mods that have added dialouge makes the Editor crash + Bug #2586: Editor: some dialogue only editable items do not refresh after undo + Bug #2588: Editor: Cancel button exits program + Bug #2589: Editor: Regions table - mapcolor does not change correctly + Bug #2591: Placeatme - spurious 5th parameter raises error + Bug #2593: COC command prints multiple times when GUI is hidden + Bug #2598: Editor: scene view of instances has to be zoomed out to displaying something - center camera instance please + Bug #2607: water behind an invisible NPC becomes invisible as well + Bug #2611: Editor: Sort problem in Objects table when few nested rows are added + Bug #2621: crash when a creature has no model + Bug #2624: Editor: missing columns in tables + Bug #2627: Character sheet doesn't properly update when backing out of CharGen + Bug #2642: Editor: endif without if - is not reported as error when "verify" was executed + Bug #2644: Editor: rebuild the list of available content files when opening the open/new dialogues + Bug #2656: OpenMW & OpenMW-CS: setting "Flies" flag for ghosts has no effect + Bug #2659: OpenMW & OpenMW-CS: savegame load fail due to script attached to NPCs + Bug #2668: Editor: reputation value in the input field is not stored + Bug #2696: Horkers use land idle animations under water + Bug #2705: Editor: Sort by Record Type (Objects table) is incorrect + Bug #2711: Map notes on an exterior cell that shows up with a map marker on the world map do not show up in the tooltip for that cell's marker on the world map + Bug #2714: Editor: Can't reorder rows with the same topic in different letter case + Bug #2720: Head tracking for creatures not implemented + Bug #2722: Alchemy should only include effects shared by at least 2 ingredients + Bug #2723: "ori" console command is not working + Bug #2726: Ashlanders in front of Ghostgate start wandering around + Bug #2727: ESM writer does not handle encoding when saving the TES3 header + Bug #2728: Editor: Incorrect position of an added row in Info tables + Bug #2731: Editor: Deleting a record triggers a Qt warning + Bug #2733: Editor: Undo doesn't restore the Modified status of a record when a nested data is changed + Bug #2734: Editor: The Search doesn't work + Bug #2738: Additive moon blending + Bug #2746: NIF node names should be case insensitive + Bug #2752: Fog depth/density not handled correctly + Bug #2753: Editor: line edit in dialogue subview tables shows after a single click + Bug #2755: Combat AI changes target too frequently + Bug #2761: Can't attack during block animations + Bug #2764: Player doesn't raise arm in 3rd person for weathertype 9 + Bug #2768: Current screen resolution not selected in options when starting OpenMW + Bug #2773: Editor: Deleted scripts are editable + Bug #2776: ordinators still think I'm wearing their helm even though Khajiit and argonians can't + Bug #2779: Slider bars continue to move if you don't release mouse button + Bug #2781: sleep interruption is a little off (is this an added feature?) + Bug #2782: erroneously able to ready weapon/magic (+sheathe weapon/magic) while paralyzed + Bug #2785: Editor: Incorrect GMSTs for newly created omwgame files + Bug #2786: Kwama Queen head is inverted under OpenMW + Bug #2788: additem and removeitem incorrect gold behavior + Bug #2790: --start doesn't trace down + Bug #2791: Editor: Listed attributes and skill should not be based on number of NPC objects. + Bug #2792: glitched merchantile/infinite free items + Bug #2794: Need to ignore quotes in names of script function + Bug #2797: Editor: Crash when removing the first row in a nested table + Bug #2800: Show an error message when S3TC support is missing + Bug #2811: Targetted Open spell effect persists. + Bug #2819: Editor: bodypart's race filter not displayed correctly + Bug #2820: Editor: table sorting is inverted + Bug #2821: Editor: undo/redo command labels are incorrect + Bug #2826: locking beds that have been locked via magic psuedo-freezes the game + Bug #2830: Script compiler does not accept IDs as instruction/functions arguments if the ID is also a keyword + Bug #2832: Cell names are not localized on the world map + Bug #2833: [cosmetic] Players swimming at water's surface are slightly too low. + Bug #2840: Save/load menu is not entirely localized + Bug #2853: [exploit/bug] disintegrate weapon incorrectly applying to lockpicks, probes. creates unbreakable lockpicks + Bug #2855: Mouse wheel in journal is not disabled by "Options" panel. + Bug #2856: Heart of Lorkhan doesn't visually respond to attacks + Bug #2863: Inventory highlights wrong category after load + Bug #2864: Illuminated Order 1.0c Bug – The teleport amulet is not placed in the PC inventory. + Bug #2866: Editor: use checkbox instead of combobox for boolean values + Bug #2875: special cases of fSleepRandMod not behaving properly. + Bug #2878: Editor: Verify reports "creature has non-positive level" but there is no level setting + Bug #2879: Editor: entered value of field "Buys *" is not saved for a creature + Bug #2880: OpenMW & OpenMW-CS: having a scale value of 0.000 makes the game laggy + Bug #2882: Freeze when entering cell "Guild of Fighters (Ald'ruhn)" after dropping some items inside + Bug #2883: game not playable if mod providing a spell is removed but the list of known spells still contains it + Bug #2884: NPC chats about wrong player race + Bug #2886: Adding custom races breaks existing numbering of PcRace + Bug #2888: Editor: value entered in "AI Wander Idle" is not kept + Bug #2889: Editor: creatures made with the CS (not cloned) are always dead + Bug #2890: Editor: can't make NPC say a specific "Hello" voice-dialouge + Bug #2893: Editor: making a creature use textual dialogue doesn't work. + Bug #2901: Editor: gold for trading can not be set for creatures + Bug #2907: looking from uderwater part of the PC that is below the surface looks like it would be above the water + Bug #2914: Magicka not recalculated on character generation + Bug #2915: When paralyzed, you can still enter and exit sneak + Bug #2917: chameleon does not work for creatures + Bug #2927: Editor: in the automatic script checker local variable caches are not invalidated/updated on modifications of other scripts + Bug #2930: Editor: AIWander Idle can not be set for a creature + Bug #2932: Editor: you can add rows to "Creature Attack" but you can not enter values + Bug #2938: Editor: Can't add a start script. + Bug #2944: Spell chance for power to show as 0 on hud when used + Bug #2953: Editor: rightclick in an empty place in the menu bar shows an unnamed checkbox + Bug #2956: Editor: freezes while editing Filter + Bug #2959: space character in field enchantment (of an amulet) prevents rendering of surroundings + Bug #2962: OpenMW: Assertion `it != invStore.end()' failed + Bug #2964: Recursive script execution can corrupt script runtime data + Bug #2973: Editor: placing a chest in the game world and activating it heavily blurrs the character portrait + Bug #2978: Editor: Cannot edit alchemy ingredient properties + Bug #2980: Editor: Attribute and Skill can be selected for spells that do not require these parameters, leading to non-functional spells + Bug #2990: Compiling a script with warning mode 2 and enabled error downgrading leads to infinite recursion + Bug #2992: [Mod: Great House Dagoth] Killing Dagoth Gares freezes the game + Bug #3007: PlaceItem takes radians instead of degrees + angle reliability + Feature #706: Editor: Script Editor enhancements + Feature #872: Editor: Colour values in tables + Feature #880: Editor: ID auto-complete + Feature #928: Editor: Partial sorting in info tables + Feature #942: Editor: Dialogue for editing/viewing content file meta information + Feature #1057: NiStencilProperty + Feature #1278: Editor: Mouse picking in worldspace widget + Feature #1280: Editor: Cell border arrows + Feature #1401: Editor: Cloning enhancements + Feature #1463: Editor: Fine grained configuration of extended revert/delete commands + Feature #1591: Editor: Make fields in creation bar drop targets where applicable + Feature #1998: Editor: Magic effect record verifier + Feature #1999: Editor Sound Gen record verifier + Feature #2000: Editor: Pathgrid record verifier + Feature #2528: Game Time Tracker + Feature #2534: Editor: global search does not auomatically focus the search input field + Feature #2535: OpenMW: allow comments in openmw.cfg + Feature #2541: Editor: provide a go to the very bottom button for TopicInfo and JournalInfo + Feature #2549: Editor: add a horizontal slider to scroll between opened tables + Feature #2558: Editor: provide a shortcut for closing the subview that has the focus + Feature #2565: Editor: add context menu for dialogue sub view fields with an item matching "Edit 'x'" from the table subview context menu + Feature #2585: Editor: Ignore mouse wheel input for numeric values unless the respective widget has the focus + Feature #2620: Editor: make the verify-view refreshable + Feature #2622: Editor: Make double click behaviour in result tables configurable (see ID tables) + Feature #2717: Editor: Add severity column to report tables + Feature #2729: Editor: Various dialogue button bar improvements + Feature #2739: Profiling overlay + Feature #2740: Resource manager optimizations + Feature #2741: Make NIF files into proper resources + Feature #2742: Use the skinning data in NIF files as-is + Feature #2743: Small feature culling + Feature #2744: Configurable near clip distance + Feature #2745: GUI scaling option + Feature #2747: Support anonymous textures + Feature #2749: Loading screen optimizations + Feature #2751: Character preview optimization + Feature #2804: Editor: Merge Tool + Feature #2818: Editor: allow copying a record ID to the clipboard + Feature #2946: Editor: add script line number in results of search + Feature #2963: Editor: Mouse button bindings in 3D scene + Feature #2983: Sun Glare fader + Feature #2999: Scaling of journal and books + Task #2665: Support building with Qt5 + Task #2725: Editor: Remove Display_YesNo + Task #2730: Replace hardcoded column numbers in SimpleDialogueSubView/DialogueSubView + Task #2750: Bullet shape instancing optimization + Task #2793: Replace grid size setting with half grid size setting + Task #3003: Support FFMPEG 2.9 (Debian request) + 0.36.1 ------ diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 2efb6e2bbf..6e288aa149 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -14,7 +14,7 @@ echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev -sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev +sudo apt-get install -qq ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi diff --git a/CI/check_tabs.sh b/CI/check_tabs.sh index 91f81e2a1e..1e88b57fd4 100755 --- a/CI/check_tabs.sh +++ b/CI/check_tabs.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} apps components) +OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} --exclude=ui_\* apps components) if [[ $OUTPUT ]] ; then echo "Error: Tab characters found!" diff --git a/CMakeLists.txt b/CMakeLists.txt index d75d044b12..906c87db20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,9 @@ project(OpenMW) # If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING - "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS None Debug Release RelWithDebInfo MinSizeRel) ENDIF() if (APPLE) @@ -19,8 +20,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 36) -set(OPENMW_VERSION_RELEASE 1) +set(OPENMW_VERSION_MINOR 37) +set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") @@ -45,10 +46,6 @@ 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_BINARY_DIR}/docs/mainpage.hpp") @@ -83,13 +80,28 @@ if (MSVC) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) endif() -# Location of morrowind data files +# Set up common paths if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "./resources" CACHE PATH "location of OpenMW resources files") elseif(UNIX) - set(MORROWIND_DATA_FILES "${CMAKE_INSTALL_PREFIX}/share/games/openmw/data/" CACHE PATH "location of Morrowind data files") - set(OPENMW_RESOURCE_FILES "${CMAKE_INSTALL_PREFIX}/share/games/openmw/resources/" CACHE PATH "location of OpenMW resources files") + # Paths + SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") + SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" 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(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.") + 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") + + set(MORROWIND_DATA_FILES "${DATADIR}/data" CACHE PATH "location of Morrowind data files") + set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files") else() set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files") @@ -107,27 +119,14 @@ unset(FFMPEG_LIBRARIES CACHE) find_package(FFmpeg REQUIRED) -set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY}) +set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY} ${SWRESAMPLE_LIBRARIES}) -if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) +if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND OR NOT SWRESAMPLE_FOUND) message(FATAL_ERROR "FFmpeg component required, but not found!") endif() -set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIRS}) -if( SWRESAMPLE_FOUND ) - add_definitions(-DHAVE_LIBSWRESAMPLE) - set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) -else() - if( AVRESAMPLE_FOUND ) - set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) - else() - message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") - endif() -endif() # Required for building the FFmpeg headers add_definitions(-D__STDC_CONSTANT_MACROS) -set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) - # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) if(USE_SYSTEM_TINYXML) @@ -157,20 +156,39 @@ if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() -# Dependencies +if (ANDROID) + set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") + set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) +endif (ANDROID) + +option(OPENGL_ES "enable opengl es support" FALSE ) -set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") -message(STATUS "Using Qt${DESIRED_QT_VERSION}") +if (OPENGL_ES) + add_definitions(-DOPENGL_ES) +endif(OPENGL_ES) -if (DESIRED_QT_VERSION MATCHES 4) - find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork QtOpenGL) +if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) + set(USE_QT FALSE) else() - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Core REQUIRED) - find_package(Qt5Network REQUIRED) - find_package(Qt5OpenGL REQUIRED) + set(USE_QT TRUE) +endif() + +# Dependencies +if (USE_QT) + set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") + set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) + message(STATUS "Using Qt${DESIRED_QT_VERSION}") + + if (DESIRED_QT_VERSION MATCHES 4) + find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork QtOpenGL) + else() + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Core REQUIRED) + find_package(Qt5Network REQUIRED) + find_package(Qt5OpenGL REQUIRED) # Instruct CMake to run moc automatically when needed. #set(CMAKE_AUTOMOC ON) + endif() endif() # Fix for not visible pthreads functions for linker with glibc 2.15 @@ -202,7 +220,12 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgQt osgUtil osgFX) +if (USE_QT) + set (OSG_QT osgQt) +endif() + +find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) + include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) if(OSG_STATIC) @@ -308,8 +331,7 @@ if (APPLE) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") if (OPENMW_OSX_DEPLOYMENT) - SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) + SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) endif() else (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") @@ -327,8 +349,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg.install") -configure_file(${OpenMW_SOURCE_DIR}/files/opencs.ini - "${OpenMW_BINARY_DIR}/opencs.ini") +configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg + "${OpenMW_BINARY_DIR}/openmw-cs.cfg") configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) @@ -375,21 +397,7 @@ elseif (MSVC) endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) 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${LIB_SUFFIX}" 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(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.") - 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") + # Linux installation # Install binaries IF(BUILD_OPENMW) @@ -442,7 +450,7 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources @@ -475,7 +483,7 @@ if(WIN32) ENDIF(BUILD_ESSIMPORTER) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-cs.exe" DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") @@ -721,6 +729,18 @@ endif() # Apple bundling if (APPLE) + get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME) + get_filename_component(QT_COCOA_PLUGIN_NAME "${QT_COCOA_PLUGIN_PATH}" NAME) + configure_file("${QT_COCOA_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) + + if (BUILD_OPENCS) + get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME) + set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") + configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) + endif () + set(INSTALL_SUBDIR OpenMW) install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) @@ -728,7 +748,7 @@ if (APPLE) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + install(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) @@ -736,22 +756,22 @@ if (APPLE) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") - - set(OPENCS_BUNDLE_NAME "OpenMW-CS.app") - set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}") + set(INSTALLED_OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + set(INSTALLED_OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}") install(CODE " set(BU_CHMOD_BUNDLE_ITEMS ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) - include(BundleUtilitiesWithRPath) + include(BundleUtilities) + cmake_minimum_required(VERSION 3.1) " COMPONENT Runtime) set(ABSOLUTE_PLUGINS "") set(USED_OSG_PLUGINS - osgdb_tga osgdb_dds - osgdb_imageio + osgdb_jpeg + osgdb_png + osgdb_tga ) foreach (PLUGIN_NAME ${USED_OSG_PLUGINS}) @@ -759,12 +779,12 @@ if (APPLE) set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS}) endforeach () - get_filename_component(PLUGIN_PREFIX_DIR "${OSG_PLUGIN_LIB_SEARCH_PATH}" NAME) + get_filename_component(OSG_PLUGIN_PREFIX_DIR "${OSG_PLUGIN_LIB_SEARCH_PATH}" NAME) # installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX}) # and returns list of install paths for all installed plugins function (install_plugins_for_bundle bundle_path plugins_var) - set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/PlugIns/${PLUGIN_PREFIX_DIR}") + set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/PlugIns/${OSG_PLUGIN_PREFIX_DIR}") set(PLUGINS "") set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}") @@ -787,19 +807,20 @@ if (APPLE) install_plugins_for_bundle("${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}" PLUGINS) install_plugins_for_bundle("${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) - set(DIRS "${CMAKE_PREFIX_PATH}/lib") + set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") + set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") install(CODE " function(gp_item_default_embedded_path_override item default_embedded_path_var) - if (\${item} MATCHES ${PLUGIN_PREFIX_DIR}) - set(path \"@executable_path/../PlugIns/${PLUGIN_PREFIX_DIR}\") + if (\${item} MATCHES ${OSG_PLUGIN_PREFIX_DIR}) + set(path \"@executable_path/../PlugIns/${OSG_PLUGIN_PREFIX_DIR}\") set(\${default_embedded_path_var} \"\${path}\" PARENT_SCOPE) endif() endfunction() cmake_policy(SET CMP0009 OLD) - fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\") - fixup_bundle(\"${OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${INSTALLED_OPENMW_APP}\" \"${PLUGINS}\" \"\") + fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\") " COMPONENT Runtime) include(CPack) endif (APPLE) diff --git a/README.md b/README.md index 362d2eef45..f353cd76ef 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. -* Version: 0.36.1 +* Version: 0.37.0 * License: GPL (see docs/license/GPL3.txt for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 5b6e50b965..be90afec38 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -27,7 +27,8 @@ struct ESMData std::vector masters; std::deque mRecords; - std::map > mCellRefs; + // Value: (Reference, Deleted flag) + std::map > > mCellRefs; std::map mRecordStats; static const std::set sLabeledRec; @@ -255,7 +256,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) while(cell.getNextRef(esm, ref, deleted)) { if (save) { - info.data.mCellRefs[&cell].push_back(ref); + info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); } if(quiet) continue; @@ -352,61 +353,58 @@ int load(Arguments& info) uint32_t flags; esm.getRecHeader(flags); + EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); + if (record == 0) + { + if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) + { + std::cout << "Skipping " << n.toString() << " records." << std::endl; + skipped.push_back(n.val); + } + + esm.skipRecord(); + if (quiet) break; + std::cout << " Skipping\n"; + + continue; + } + + record->setFlags(static_cast(flags)); + record->setPrintPlain(info.plain_given); + record->load(esm); + // Is the user interested in this record type? bool interested = true; if (!info.types.empty()) { std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), - n.toString()); + match = std::find(info.types.begin(), info.types.end(), n.toString()); if (match == info.types.end()) interested = false; } - std::string id = esm.getHNOString("NAME"); - if (id.empty()) - id = esm.getHNOString("INAM"); - - if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id)) + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) interested = false; if(!quiet && interested) - std::cout << "\nRecord: " << n.toString() - << " '" << id << "'\n"; - - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - - if (record == 0) { - if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) - { - std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.push_back(n.val); - } + { + std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; + record->print(); + } - esm.skipRecord(); - if (quiet) break; - std::cout << " Skipping\n"; - } else { - if (record->getType().val == ESM::REC_GMST) { - // preset id for GameSetting record - record->cast()->get().mId = id; - } - record->setId(id); - record->setFlags((int) flags); - record->setPrintPlain(info.plain_given); - record->load(esm); - if (!quiet && interested) record->print(); - - if (record->getType().val == ESM::REC_CELL && loadCells && interested) { - loadCell(record->cast()->get(), esm, info); - } + if (record->getType().val == ESM::REC_CELL && loadCells && interested) + { + loadCell(record->cast()->get(), esm, info); + } - if (save) { - info.data.mRecords.push_back(record); - } else { - delete record; - } - ++info.data.mRecordStats[n.val]; + if (save) + { + info.data.mRecords.push_back(record); + } + else + { + delete record; } + ++info.data.mRecordStats[n.val]; } } catch(std::exception &e) { @@ -493,28 +491,19 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - name.val = record->getType().val; esm.startRecord(name.toString(), record->getFlags()); - // TODO wrap this with std::set - if (ESMData::sLabeledRec.count(name.val) > 0) { - esm.writeHNCString("NAME", record->getId()); - } else { - esm.writeHNOString("NAME", record->getId()); - } - record->save(esm); - if (name.val == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { - typedef std::deque RefList; + typedef std::deque > RefList; RefList &refs = info.data.mCellRefs[ptr]; for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt) { - refIt->save(esm); + refIt->first.save(esm, refIt->second); } } } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 5fe6471b0f..d7cbea73b0 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -405,6 +405,7 @@ void Record::print() std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -419,6 +420,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -447,6 +449,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -461,6 +464,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -474,6 +478,7 @@ void Record::print() std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -502,6 +507,7 @@ void Record::print() { std::cout << " Text: [skipped]" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -513,6 +519,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) std::cout << " Power: " << *pit << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -541,6 +548,7 @@ void Record::print() std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -563,6 +571,7 @@ void Record::print() for (int i = 0; i != 5; i++) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -589,6 +598,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -604,6 +614,7 @@ void Record::print() 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::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -670,6 +681,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -677,6 +689,7 @@ void Record::print() { std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? @@ -693,6 +706,7 @@ void Record::print() std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -704,6 +718,7 @@ void Record::print() std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -737,12 +752,14 @@ void Record::print() std::map::iterator rit; for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -809,6 +826,7 @@ void Record::print() std::cout << " Result Script: [skipped]" << std::endl; } } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -832,6 +850,7 @@ void Record::print() std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" << mData.mData.mAttributes[i] << ")" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -848,6 +867,8 @@ void Record::print() std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl; } + mData.unloadData(); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -860,6 +881,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -872,6 +894,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -892,6 +915,7 @@ void Record::print() std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -906,6 +930,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -920,6 +945,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -934,6 +960,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -942,6 +969,7 @@ void Record::print() std::cout << " Id: " << mData.mId << std::endl; std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -992,6 +1020,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1077,6 +1106,8 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1111,6 +1142,8 @@ void Record::print() std::cout << " BAD POINT IN EDGE!" << std::endl; i++; } + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1151,6 +1184,8 @@ void Record::print() std::vector::iterator sit; for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) std::cout << " Power: " << *sit << std::endl; + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1210,6 +1245,8 @@ void Record::print() { std::cout << " Script: [skipped]" << std::endl; } + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1233,6 +1270,7 @@ void Record::print() std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1243,6 +1281,7 @@ void Record::print() if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1254,13 +1293,15 @@ void Record::print() std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { - std::cout << "Start Script: " << mData.mId << std::endl; - std::cout << "Start Data: " << mData.mData << std::endl; + std::cout << " Start Script: " << mData.mId << std::endl; + std::cout << " Start Data: " << mData.mData << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1301,6 +1342,37 @@ void Record::print() if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; +} + +template<> +std::string Record::getId() const +{ + return mData.mName; +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Land record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for MagicEffect record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Pathgrid record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Skill record } } // end namespace diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 5e03c64dba..dca38409fe 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -32,13 +32,7 @@ namespace EsmTool virtual ~RecordBase() {} - const std::string &getId() const { - return mId; - } - - void setId(const std::string &id) { - mId = id; - } + virtual std::string getId() const = 0; uint32_t getFlags() const { return mFlags; @@ -73,22 +67,37 @@ namespace EsmTool class Record : public RecordBase { T mData; + bool mIsDeleted; public: + Record() + : mIsDeleted(false) + {} + + std::string getId() const { + return mData.mId; + } + T &get() { return mData; } void save(ESM::ESMWriter &esm) { - mData.save(esm); + mData.save(esm, mIsDeleted); } void load(ESM::ESMReader &esm) { - mData.load(esm); + mData.load(esm, mIsDeleted); } void print(); }; + + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; template<> void Record::print(); template<> void Record::print(); diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 6267ef91ee..afd0ef131d 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -158,9 +158,9 @@ namespace ESSImport void ConvertCell::read(ESM::ESMReader &esm) { ESM::Cell cell; - std::string id = esm.getHNString("NAME"); - cell.mName = id; - cell.load(esm, false); + bool isDeleted = false; + + cell.load(esm, isDeleted, false); // I wonder what 0x40 does? if (cell.isExterior() && cell.mData.mFlags & 0x20) @@ -169,7 +169,7 @@ namespace ESSImport } // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position - if (id == mContext->mPlayerCellName) + if (cell.mName == mContext->mPlayerCellName) { mContext->mPlayer.mCellId = cell.getCellId(); } @@ -277,7 +277,7 @@ namespace ESSImport if (cell.isExterior()) mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; else - mIntCells[id] = newcell; + mIntCells[cell.mName] = newcell; } void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 4bad5e23b6..f364e166c8 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -54,6 +54,8 @@ public: void setContext(Context& context) { mContext = &context; } + /// @note The load method of ESM records accept the deleted flag as a parameter. + /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. virtual void read(ESM::ESMReader& esm) { } @@ -78,10 +80,11 @@ public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); T record; - record.load(esm); - mRecords[id] = record; + bool isDeleted = false; + + record.load(esm, isDeleted); + mRecords[record.mId] = record; } virtual void write(ESM::ESMWriter& esm) @@ -89,7 +92,6 @@ public: for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { esm.startRecord(T::sRecordId); - esm.writeHNString("NAME", it->first); it->second.save(esm); esm.endRecord(T::sRecordId); } @@ -105,14 +107,15 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::NPC npc; - std::string id = esm.getHNString("NAME"); - npc.load(esm); - if (id != "player") + bool isDeleted = false; + + npc.load(esm, isDeleted); + if (npc.mId != "player") { // Handles changes to the NPC struct, but since there is no index here // it will apply to ALL instances of the class. seems to be the reason for the // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. - mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc; + mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc; } else { @@ -142,9 +145,10 @@ public: { // See comment in ConvertNPC ESM::Creature creature; - std::string id = esm.getHNString("NAME"); - creature.load(esm); - mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature; + bool isDeleted = false; + + creature.load(esm, isDeleted); + mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; @@ -157,18 +161,19 @@ class ConvertGlobal : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Global global; - global.load(esm); - if (Misc::StringUtils::ciEqual(id, "gamehour")) + bool isDeleted = false; + + global.load(esm, isDeleted); + if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); - if (Misc::StringUtils::ciEqual(id, "day")) + if (Misc::StringUtils::ciEqual(global.mId, "day")) mContext->mDay = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "month")) + if (Misc::StringUtils::ciEqual(global.mId, "month")) mContext->mMonth = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "year")) + if (Misc::StringUtils::ciEqual(global.mId, "year")) mContext->mYear = global.mValue.getInteger(); - mRecords[id] = global; + mRecords[global.mId] = global; } }; @@ -177,14 +182,14 @@ class ConvertClass : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Class class_; - class_.load(esm); + bool isDeleted = false; - if (id == "NEWCLASSID_CHARGEN") + class_.load(esm, isDeleted); + if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; - mRecords[id] = class_; + mRecords[class_.mId] = class_; } }; @@ -193,13 +198,14 @@ class ConvertBook : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Book book; - book.load(esm); + bool isDeleted = false; + + book.load(esm, isDeleted); if (book.mData.mSkillID == -1) - mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id)); + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); - mRecords[id] = book; + mRecords[book.mId] = book; } }; @@ -371,11 +377,12 @@ class ConvertFACT : public Converter public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); ESM::Faction faction; - faction.load(esm); + bool isDeleted = false; + + faction.load(esm, isDeleted); + std::string id = Misc::StringUtils::lowerCase(faction.mId); - Misc::StringUtils::toLower(id); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { std::string faction2 = Misc::StringUtils::lowerCase(it->first); @@ -391,7 +398,7 @@ public: virtual void read(ESM::ESMReader &esm) { std::string itemid = esm.getHNString("NAME"); - Misc::StringUtils::toLower(itemid); + Misc::StringUtils::lowerCaseInPlace(itemid); while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index a5f9d6eb3b..239c46698d 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -32,7 +32,8 @@ namespace ESSImport if (esm.isNextSub("MNAM")) esm.skipHSub(); - ESM::CellRef::loadData(esm); + bool isDeleted = false; + ESM::CellRef::loadData(esm, isDeleted); mHasACDT = false; if (esm.isNextSub("ACDT")) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 624241039d..4fbf062170 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -394,7 +394,7 @@ namespace ESSImport } writer.startRecord(ESM::REC_NPC_); - writer.writeHNString("NAME", "player"); + context.mPlayerBase.mId = "player"; context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index d27cd5c8ca..3ec640d3d5 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -32,7 +32,8 @@ namespace ESSImport item.mSCRI.load(esm); // for XSOL and XCHG seen so far, but probably others too - item.ESM::CellRef::loadData(esm); + bool isDeleted = false; + item.ESM::CellRef::loadData(esm, isDeleted); int charge=-1; esm.getHNOT(charge, "XHLT"); diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 4d2dc6ac4b..207f6a84b8 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -7,8 +7,6 @@ set(LAUNCHER textslotmsgbox.cpp settingspage.cpp - settings/graphicssettings.cpp - utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp @@ -24,8 +22,6 @@ set(LAUNCHER_HEADER textslotmsgbox.hpp settingspage.hpp - settings/graphicssettings.hpp - utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index ec2e1246ef..0b0f8c75e2 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -75,7 +75,7 @@ bool Launcher::DataFilesPage::loadSettings() QStringList profiles = mLauncherSettings.getContentLists(); QString currentProfile = mLauncherSettings.getCurrentContentListName(); - qDebug() << "current profile is: " << currentProfile; + qDebug() << "The current profile is: " << currentProfile; foreach (const QString &item, profiles) addProfile (item, false); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 2128c08f74..622db4da46 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -18,7 +18,7 @@ #include -#include "settings/graphicssettings.hpp" +#include QString getAspect(int x, int y) { @@ -32,10 +32,10 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) - , mGraphicsSettings(graphicsSetting) + , mEngineSettings(engineSettings) { setObjectName ("GraphicsPage"); setupUi(this); @@ -80,25 +80,26 @@ bool Launcher::GraphicsPage::loadSettings() if (!setupSDL()) return false; - if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true")) + if (mEngineSettings.getBool("vsync", "Video")) vSyncCheckBox->setCheckState(Qt::Checked); - if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true")) + if (mEngineSettings.getBool("fullscreen", "Video")) fullScreenCheckBox->setCheckState(Qt::Checked); - if (mGraphicsSettings.value(QString("Video/window border")) == QLatin1String("true")) + if (mEngineSettings.getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); - int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing"))); + // aaValue is the actual value (0, 1, 2, 4, 8, 16) + int aaValue = mEngineSettings.getInt("antialiasing", "Video"); + // aaIndex is the index into the allowed values in the pull down. + int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - QString width = mGraphicsSettings.value(QString("Video/resolution x")); - QString height = mGraphicsSettings.value(QString("Video/resolution y")); - QString resolution = width + QString(" x ") + height; - QString screen = mGraphicsSettings.value(QString("Video/screen")); - - screenComboBox->setCurrentIndex(screen.toInt()); + int width = mEngineSettings.getInt("resolution x", "Video"); + int height = mEngineSettings.getInt("resolution y", "Video"); + QString resolution = QString::number(width) + QString(" x ") + QString::number(height); + screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video")); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -107,9 +108,8 @@ bool Launcher::GraphicsPage::loadSettings() resolutionComboBox->setCurrentIndex(resIndex); } else { customRadioButton->toggle(); - customWidthSpinBox->setValue(width.toInt()); - customHeightSpinBox->setValue(height.toInt()); - + customWidthSpinBox->setValue(width); + customHeightSpinBox->setValue(height); } return true; @@ -117,31 +117,46 @@ bool Launcher::GraphicsPage::loadSettings() void Launcher::GraphicsPage::saveSettings() { - vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); - - fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false")); - - windowBorderCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/window border"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/window border"), QString("false")); - - mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText()); - - + // Ensure we only set the new settings if they changed. This is to avoid cluttering the + // user settings file (which by definition should only contain settings the user has touched) + bool cVSync = vSyncCheckBox->checkState(); + if (cVSync != mEngineSettings.getBool("vsync", "Video")) + mEngineSettings.setBool("vsync", "Video", cVSync); + + bool cFullScreen = fullScreenCheckBox->checkState(); + if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video")) + mEngineSettings.setBool("fullscreen", "Video", cFullScreen); + + bool cWindowBorder = windowBorderCheckBox->checkState(); + if (cWindowBorder != mEngineSettings.getBool("window border", "Video")) + mEngineSettings.setBool("window border", "Video", cWindowBorder); + + int cAAValue = antiAliasingComboBox->currentText().toInt(); + if (cAAValue != mEngineSettings.getInt("antialiasing", "Video")) + mEngineSettings.setInt("antialiasing", "Video", cAAValue); + + int cWidth = 0; + int cHeight = 0; if (standardRadioButton->isChecked()) { QRegExp resolutionRe(QString("(\\d+) x (\\d+).*")); - if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) { - mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1)); - mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2)); + cWidth = resolutionRe.cap(1).toInt(); + cHeight = resolutionRe.cap(2).toInt(); } } else { - mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value())); - mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value())); + cWidth = customWidthSpinBox->value(); + cHeight = customHeightSpinBox->value(); } - mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex())); + if (cWidth != mEngineSettings.getInt("resolution x", "Video")) + mEngineSettings.setInt("resolution x", "Video", cWidth); + + if (cHeight != mEngineSettings.getInt("resolution y", "Video")) + mEngineSettings.setInt("resolution y", "Video", cHeight); + + int cScreen = screenComboBox->currentIndex(); + if (cScreen != mEngineSettings.getInt("screen", "Video")) + mEngineSettings.setInt("screen", "Video", cScreen); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index fb96c39d78..e6eb53a3b7 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -5,6 +5,8 @@ #include "ui_graphicspage.h" +#include + namespace Files { struct ConfigurationManager; } namespace Launcher @@ -16,7 +18,7 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); + GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); void saveSettings(); bool loadSettings(); @@ -30,7 +32,7 @@ namespace Launcher private: Files::ConfigurationManager &mCfgMgr; - GraphicsSettings &mGraphicsSettings; + Settings::Manager &mEngineSettings; QStringList getAvailableResolutions(int screen); QRect getMaximumResolution(); diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 32fe8c93a7..282b3fb89f 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -41,15 +41,6 @@ int main(int argc, char *argv[]) dir.cdUp(); dir.cdUp(); } - - // force Qt to load only LOCAL plugins, don't touch system Qt installation - QDir pluginsPath(QCoreApplication::applicationDirPath()); - pluginsPath.cdUp(); - pluginsPath.cd("Plugins"); - - QStringList libraryPaths; - libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - app.setLibraryPaths(libraryPaths); #endif QDir::setCurrent(dir.absolutePath()); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 9a6f91a0f5..60ae5b3a07 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -24,6 +24,15 @@ using namespace Process; +void cfgError(const QString& title, const QString& msg) { + QMessageBox msgBox; + msgBox.setWindowTitle(title); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(msg); + msgBox.exec(); +} + Launcher::MainDialog::MainDialog(QWidget *parent) : QMainWindow(parent), mGameSettings (mCfgMgr) { @@ -105,7 +114,7 @@ void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); + mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage @@ -248,6 +257,8 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem bool Launcher::MainDialog::setupLauncherSettings() { + mLauncherSettings.clear(); + mLauncherSettings.setMultiValueEnabled(true); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); @@ -257,18 +268,14 @@ bool Launcher::MainDialog::setupLauncherSettings() paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -284,6 +291,8 @@ bool Launcher::MainDialog::setupLauncherSettings() bool Launcher::MainDialog::setupGameSettings() { + mGameSettings.clear(); + QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); @@ -292,18 +301,14 @@ bool Launcher::MainDialog::setupGameSettings() QString path = userPath + QLatin1String("openmw.cfg"); QFile file(path); - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -319,19 +324,15 @@ bool Launcher::MainDialog::setupGameSettings() paths.append(userPath + QString("openmw.cfg")); foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -383,53 +384,54 @@ bool Launcher::MainDialog::setupGameSettings() bool Launcher::MainDialog::setupGraphicsSettings() { - mGraphicsSettings.setMultiValueEnabled(false); - - QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); - QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); - - QFile localDefault(QString("settings-default.cfg")); - QFile globalDefault(globalPath + QString("settings-default.cfg")); - - if (!localDefault.exists() && !globalDefault.exists()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error reading OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find settings-default.cfg

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.")); - msgBox.exec(); + // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely + // remain consistent, and possibly be merged into a shared component. At the very least + // the filenames should be in the CfgMgr component. + + // Ensure to clear previous settings in case we had already loaded settings. + mEngineSettings.clear(); + + // Create the settings manager and load default settings file + const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); + const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); + std::string defaultPath; + + // Prefer the settings-default.cfg in the current directory. + if (boost::filesystem::exists(localDefault)) + defaultPath = localDefault; + else if (boost::filesystem::exists(globalDefault)) + defaultPath = globalDefault; + // Something's very wrong if we can't find the file at all. + else { + cfgError(tr("Error reading OpenMW configuration file"), + tr("
Could not find settings-default.cfg

\ + The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.")); return false; } + // Load the default settings, report any parsing errors. + try { + mEngineSettings.loadDefault(defaultPath); + } + catch (std::exception& e) { + std::string msg = std::string("
Error reading settings-default.cfg

") + e.what(); + cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + return false; + } - QStringList paths; - paths.append(globalPath + QString("settings-default.cfg")); - paths.append(QString("settings-default.cfg")); - paths.append(userPath + QString("settings.cfg")); - - foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); - QFile file(path); - if (file.exists()) { - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; - } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); + // Load user settings if they exist + const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + // User settings are not required to exist, so if they don't we're done. + if (!boost::filesystem::exists(userPath)) return true; - mGraphicsSettings.readFile(stream); - } - file.close(); + try { + mEngineSettings.loadUser(userPath); + } + catch (std::exception& e) { + std::string msg = std::string("
Error reading settings.cfg

") + e.what(); + cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + return false; } return true; @@ -478,15 +480,11 @@ bool Launcher::MainDialog::writeSettings() if (!dir.exists()) { if (!dir.mkpath(userPath)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not create %0

\ - Please make sure you have the right permissions \ - and try again.
").arg(userPath)); - msgBox.exec(); - return false; + cfgError(tr("Error creating OpenMW configuration directory"), + tr("
Could not create %0

\ + Please make sure you have the right permissions \ + and try again.
").arg(userPath)); + return false; } } @@ -495,15 +493,11 @@ bool Launcher::MainDialog::writeSettings() if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + cfgError(tr("Error writing OpenMW configuration file"), + tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + return false; } @@ -511,44 +505,30 @@ bool Launcher::MainDialog::writeSettings() file.close(); // Graphics settings - file.setFileName(userPath + QString("settings.cfg")); - - if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + try { + mEngineSettings.saveUser(settingsPath); + } + catch (std::exception& e) { + std::string msg = "
Error writing settings.cfg

" + + settingsPath + "

" + e.what(); + cfgError(tr("Error writing user settings file"), tr(msg.c_str())); + return false; } - - QTextStream stream(&file); - stream.setDevice(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGraphicsSettings.writeFile(stream); - file.close(); // Launcher settings file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing Launcher configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + cfgError(tr("Error writing Launcher configuration file"), + tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + return false; } + QTextStream stream(&file); stream.setDevice(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 298682d207..0dfea48659 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -13,7 +13,7 @@ #include #include -#include "settings/graphicssettings.hpp" +#include #include "ui_mainwindow.h" @@ -93,7 +93,7 @@ namespace Launcher Files::ConfigurationManager mCfgMgr; Config::GameSettings mGameSettings; - GraphicsSettings mGraphicsSettings; + Settings::Manager mEngineSettings; Config::LauncherSettings mLauncherSettings; }; diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp index 6cfb9686fa..99b74fdd39 100644 --- a/apps/launcher/playpage.cpp +++ b/apps/launcher/playpage.cpp @@ -2,20 +2,11 @@ #include -#ifdef Q_OS_MAC -#include -#endif - Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { setObjectName ("PlayPage"); setupUi(this); - // Hacks to get the stylesheet look properly -#ifdef Q_OS_MAC - QPlastiqueStyle *style = new QPlastiqueStyle; - profilesComboBox->setStyle(style); -#endif profilesComboBox->setView(new QListView()); connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int))); diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp deleted file mode 100644 index 9dad3dee6b..0000000000 --- a/apps/launcher/settings/graphicssettings.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "graphicssettings.hpp" - -#include -#include -#include -#include - -Launcher::GraphicsSettings::GraphicsSettings() -{ -} - -Launcher::GraphicsSettings::~GraphicsSettings() -{ -} - -bool Launcher::GraphicsSettings::writeFile(QTextStream &stream) -{ - QString sectionPrefix; - QRegExp sectionRe("([^/]+)/(.+)$"); - QMap settings = SettingsBase::getSettings(); - - QMapIterator i(settings); - while (i.hasNext()) { - i.next(); - - QString prefix; - QString key; - - if (sectionRe.exactMatch(i.key())) { - prefix = sectionRe.cap(1); - key = sectionRe.cap(2); - } - - if (sectionPrefix != prefix) { - sectionPrefix = prefix; - stream << "\n[" << prefix << "]\n"; - } - - stream << key << " = " << i.value() << "\n"; - } - - return true; - -} diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp deleted file mode 100644 index a52e0aa84c..0000000000 --- a/apps/launcher/settings/graphicssettings.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GRAPHICSSETTINGS_HPP -#define GRAPHICSSETTINGS_HPP - -#include - -namespace Launcher -{ - class GraphicsSettings : public Config::SettingsBase > - { - public: - GraphicsSettings(); - ~GraphicsSettings(); - - bool writeFile(QTextStream &stream); - - }; -} -#endif // GRAPHICSSETTINGS_HPP diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 479f8cba28..f90ba4184f 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -20,7 +20,6 @@ MwIniImporter::MwIniImporter() { const char *map[][2] = { - { "fps", "General:Show FPS" }, { "no-sound", "General:Disable Audio" }, { 0, 0 } }; @@ -639,6 +638,9 @@ MwIniImporter::MwIniImporter() "Blood:Texture Name 1", "Blood:Texture Name 2", + // werewolf (Bloodmoon) + "General:Werewolf FOV", + 0 }; @@ -847,7 +849,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { std::string filetype(entry->substr(entry->length()-3)); - Misc::StringUtils::toLower(filetype); + Misc::StringUtils::lowerCaseInPlace(filetype); if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { boost::filesystem::path filepath(gameFilesDir); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a8ed683282..f1b4f3ab68 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -106,31 +106,16 @@ opencs_units_noqt (view/tools subviews ) -opencs_units (view/settings - settingwindow - dialog - page - view - booleanview - textview - listview - rangeview - resizeablestackedwidget - spinbox +opencs_units (view/prefs + dialogue pagebase page ) -opencs_units_noqt (view/settings - frame +opencs_units (model/prefs + state setting intsetting doublesetting boolsetting enumsetting coloursetting ) -opencs_units (model/settings - usersettings - setting - connector - ) - -opencs_hdrs_noqt (model/settings - support +opencs_units_noqt (model/prefs + category ) opencs_units_noqt (model/filter @@ -205,7 +190,13 @@ if(APPLE) endif(APPLE) target_link_libraries(openmw-cs - ${OPENSCENEGRAPH_LIBRARIES} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGVIEWER_LIBRARIES} + ${OSGGA_LIBRARIES} + ${OSGFX_LIBRARIES} + ${OSGQT_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index e3ecdce05b..92f1082b75 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -18,7 +18,7 @@ #endif CS::Editor::Editor () -: mUserSettings (mCfgMgr), mDocumentManager (mCfgMgr), +: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), mLock(), mMerge (mDocumentManager), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) @@ -27,9 +27,6 @@ CS::Editor::Editor () setupDataFiles (config.first); - CSMSettings::UserSettings::instance().loadSettings ("opencs.ini"); - mSettings.setModel (CSMSettings::UserSettings::instance()); - NifOsg::Loader::setShowMarkers(true); mVFS.reset(new VFS::Manager(mFsStrict)); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index ac403b1eee..b088e9a442 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -17,15 +17,16 @@ #include -#include "model/settings/usersettings.hpp" #include "model/doc/documentmanager.hpp" +#include "model/prefs/state.hpp" + #include "view/doc/viewmanager.hpp" #include "view/doc/startup.hpp" #include "view/doc/filedialog.hpp" #include "view/doc/newgame.hpp" -#include "view/settings/dialog.hpp" +#include "view/prefs/dialogue.hpp" #include "view/tools/merge.hpp" @@ -49,12 +50,12 @@ namespace CS std::auto_ptr mVFS; Files::ConfigurationManager mCfgMgr; - CSMSettings::UserSettings mUserSettings; + CSMPrefs::State mSettingsState; CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; CSVDoc::NewGameDialogue mNewGame; - CSVSettings::Dialog mSettings; + CSVPrefs::Dialogue mSettings; CSVDoc::FileDialog mFileDialog; boost::filesystem::path mLocal; boost::filesystem::path mResources; diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index de2e6e83e1..c6fe348356 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -43,6 +43,10 @@ class Application : public QApplication int main(int argc, char *argv[]) { + #ifdef Q_OS_MAC + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); + #endif + try { // To allow background thread drawing in OSG @@ -64,15 +68,6 @@ int main(int argc, char *argv[]) dir.cdUp(); } QDir::setCurrent(dir.absolutePath()); - - // force Qt to load only LOCAL plugins, don't touch system Qt installation - QDir pluginsPath(QCoreApplication::applicationDirPath()); - pluginsPath.cdUp(); - pluginsPath.cd("Plugins"); - - QStringList libraryPaths; - libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - application.setLibraryPaths(libraryPaths); #endif application.setWindowIcon (QIcon (":./openmw-cs.png")); diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index cb9b7ec295..a27ee9f514 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -6,7 +6,6 @@ #include #include "../world/universalid.hpp" -#include "../settings/usersettings.hpp" #include "state.hpp" #include "stage.hpp" @@ -23,9 +22,6 @@ void CSMDoc::Operation::prepareStages() { iter->second = iter->first->setup(); mTotalSteps += iter->second; - - for (std::map::const_iterator iter2 (mSettings.begin()); iter2!=mSettings.end(); ++iter2) - iter->first->updateUserSetting (iter2->first, iter2->second); } } @@ -47,7 +43,7 @@ CSMDoc::Operation::~Operation() void CSMDoc::Operation::run() { mTimer->stop(); - + if (!mConnected) { connect (mTimer, SIGNAL (timeout()), this, SLOT (executeStage())); @@ -64,14 +60,6 @@ void CSMDoc::Operation::appendStage (Stage *stage) mStages.push_back (std::make_pair (stage, 0)); } -void CSMDoc::Operation::configureSettings (const std::vector& settings) -{ - for (std::vector::const_iterator iter (settings.begin()); iter!=settings.end(); ++iter) - { - mSettings.insert (std::make_pair (*iter, CSMSettings::UserSettings::instance().definitions (*iter))); - } -} - void CSMDoc::Operation::setDefaultSeverity (Message::Severity severity) { mDefaultSeverity = severity; @@ -101,14 +89,6 @@ void CSMDoc::Operation::abort() mCurrentStage = mStages.end(); } -void CSMDoc::Operation::updateUserSetting (const QString& name, const QStringList& value) -{ - std::map::iterator iter = mSettings.find (name); - - if (iter!=mSettings.end()) - iter->second = value; -} - void CSMDoc::Operation::executeStage() { if (!mPrepared) @@ -116,7 +96,7 @@ void CSMDoc::Operation::executeStage() prepareStages(); mPrepared = true; } - + Messages messages (mDefaultSeverity); while (mCurrentStage!=mStages.end()) diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 3c86a6e235..ff396fa3ce 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -34,7 +34,6 @@ namespace CSMDoc bool mError; bool mConnected; QTimer *mTimer; - std::map mSettings; bool mPrepared; Message::Severity mDefaultSeverity; @@ -53,11 +52,6 @@ namespace CSMDoc /// /// \attention Do no call this function while this Operation is running. - /// Specify settings to be passed on to stages. - /// - /// \attention Do no call this function while this Operation is running. - void configureSettings (const std::vector& settings); - /// \attention Do no call this function while this Operation is running. void setDefaultSeverity (Message::Severity severity); @@ -77,8 +71,6 @@ namespace CSMDoc void run(); - void updateUserSetting (const QString& name, const QStringList& value); - private slots: void executeStage(); diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index db0d1a9a4b..5fcf24fe4f 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -1,7 +1,5 @@ #include "operationholder.hpp" -#include "../settings/usersettings.hpp" - #include "operation.hpp" CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mRunning (false) @@ -30,9 +28,6 @@ void CSMDoc::OperationHolder::setOperation (Operation *operation) connect (this, SIGNAL (abortSignal()), mOperation, SLOT (abort())); connect (&mThread, SIGNAL (started()), mOperation, SLOT (run())); - - connect (&CSMSettings::UserSettings::instance(), SIGNAL (userSettingUpdated (const QString&, const QStringList&)), - mOperation, SLOT (updateUserSetting (const QString&, const QStringList&))); } bool CSMDoc::OperationHolder::isRunning() const diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index c6d8a8cb34..db38c4779b 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -99,84 +99,77 @@ int CSMDoc::WriteDialogueCollectionStage::setup() void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) { + ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& topic = mTopics.getRecord (stage); - CSMWorld::RecordBase::State state = topic.mState; - - if (state==CSMWorld::RecordBase::State_Deleted) + if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. - - /// \todo wrote record with delete flag - + ESM::Dialogue dialogue = topic.get(); + writer.startRecord(dialogue.sRecordId); + dialogue.save(writer, true); + writer.endRecord(dialogue.sRecordId); return; } // Test, if we need to save anything associated info records. bool infoModified = false; - CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; - - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - state==CSMWorld::RecordBase::State_Deleted) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; } } - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - infoModified) + if (topic.isModified() || infoModified) { - mState.getWriter().startRecord (topic.mModified.sRecordId); - mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); - topic.mModified.save (mState.getWriter()); - mState.getWriter().endRecord (topic.mModified.sRecordId); + if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified + && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) + { + mState.getWriter().startRecord (topic.mBase.sRecordId); + topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); + mState.getWriter().endRecord (topic.mBase.sRecordId); + } + else + { + mState.getWriter().startRecord (topic.mModified.sRecordId); + topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); + mState.getWriter().endRecord (topic.mModified.sRecordId); + } // write modified selected info records - for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; - ++iter) + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; - - if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo wrote record with delete flag - } - else if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + info.mPrev = ""; if (iter!=range.first) { CSMWorld::InfoCollection::RecordConstIterator prev = iter; --prev; - info.mPrev = - prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1); + info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); } CSMWorld::InfoCollection::RecordConstIterator next = iter; ++next; + info.mNext = ""; if (next!=range.second) { - info.mNext = - next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); + info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); } - mState.getWriter().startRecord (info.sRecordId); - mState.getWriter().writeHNCString ("INAM", info.mId); - info.save (mState.getWriter()); - mState.getWriter().endRecord (info.sRecordId); + writer.startRecord (info.sRecordId); + info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (info.sRecordId); } } } @@ -224,9 +217,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) const CSMWorld::Record& record = mDocument.getData().getReferences().getRecord (i); - if (record.mState==CSMWorld::RecordBase::State_Deleted || - record.mState==CSMWorld::RecordBase::State_Modified || - record.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { std::string cellId = record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell; @@ -268,36 +259,34 @@ int CSMDoc::WriteCellCollectionStage::setup() void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& cell = - mDocument.getData().getCells().getRecord (stage); + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); - if (cell.mState==CSMWorld::RecordBase::State_Modified || - cell.mState==CSMWorld::RecordBase::State_ModifiedOnly || + if (cell.isModified() || + cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { - bool interior = cell.get().mId.substr (0, 1)!="#"; + CSMWorld::Cell cellRecord = cell.get(); + bool interior = cellRecord.mId.substr (0, 1)!="#"; // write cell data - mState.getWriter().startRecord (cell.mModified.sRecordId); - - mState.getWriter().writeHNOCString ("NAME", cell.get().mName); - - ESM::Cell cell2 = cell.get(); + writer.startRecord (cellRecord.sRecordId); if (interior) - cell2.mData.mFlags |= ESM::Cell::Interior; + cellRecord.mData.mFlags |= ESM::Cell::Interior; else { - cell2.mData.mFlags &= ~ESM::Cell::Interior; + cellRecord.mData.mFlags &= ~ESM::Cell::Interior; - std::istringstream stream (cell.get().mId.c_str()); + std::istringstream stream (cellRecord.mId.c_str()); char ignore; - stream >> ignore >> cell2.mData.mX >> cell2.mData.mY; + stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } - cell2.save (mState.getWriter()); + + cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted); // write references if (references!=mState.getSubRecords().end()) @@ -308,24 +297,25 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); - if (ref.mState==CSMWorld::RecordBase::State_Modified || - ref.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) { + CSMWorld::CellRef refRecord = ref.get(); + // recalculate the ref's cell location std::ostringstream stream; if (!interior) { - std::pair index = ref.get().getCellIndex(); + std::pair index = refRecord.getCellIndex(); stream << "#" << index.first << " " << index.second; } // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. - if ((ref.get().mOriginalCell.empty() ? ref.get().mCell : ref.get().mOriginalCell) + if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { ESM::MovedCellRef moved; - moved.mRefNum = ref.get().mRefNum; + moved.mRefNum = refRecord.mRefNum; // Need to fill mTarget with the ref's new position. std::istringstream istream (stream.str().c_str()); @@ -333,24 +323,16 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) char ignore; istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; - ref.get().mRefNum.save (mState.getWriter(), false, "MVRF"); - mState.getWriter().writeHNT ("CNDT", moved.mTarget, 8); + refRecord.mRefNum.save (writer, false, "MVRF"); + writer.writeHNT ("CNDT", moved.mTarget, 8); } - ref.get().save (mState.getWriter()); - } - else if (ref.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); } } } - mState.getWriter().endRecord (cell.mModified.sRecordId); - } - else if (cell.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (cellRecord.sRecordId); } } @@ -367,11 +349,11 @@ int CSMDoc::WritePathgridCollectionStage::setup() void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& pathgrid = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); - if (pathgrid.mState==CSMWorld::RecordBase::State_Modified || - pathgrid.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); @@ -384,15 +366,9 @@ void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& message else record.mCell = record.mId; - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (pathgrid.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } @@ -409,26 +385,20 @@ int CSMDoc::WriteLandCollectionStage::setup() void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& land = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); - if (land.mState==CSMWorld::RecordBase::State_Modified || - land.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { - const CSMWorld::Land& record = land.get(); - - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); + CSMWorld::Land record = land.get(); + writer.startRecord (record.sRecordId); + record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) data->save (mState.getWriter()); - mState.getWriter().endRecord (record.sRecordId); - } - else if (land.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (record.sRecordId); } } @@ -445,25 +415,16 @@ int CSMDoc::WriteLandTextureCollectionStage::setup() void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& landTexture = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); - if (landTexture.mState==CSMWorld::RecordBase::State_Modified || - landTexture.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); - - mState.getWriter().startRecord (record.sRecordId); - - mState.getWriter().writeHNString("NAME", record.mId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (landTexture.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 188f22f961..64afd0dd8e 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -100,26 +100,17 @@ namespace CSMDoc if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) return; + ESM::ESMWriter& writer = mState.getWriter(); CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; + typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (state == CSMWorld::RecordBase::State_Modified || + state == CSMWorld::RecordBase::State_ModifiedOnly || + state == CSMWorld::RecordBase::State_Deleted) { - // FIXME: A quick Workaround to support records which should not write - // NAME, including SKIL, MGEF and SCPT. If there are many more - // idcollection records that doesn't use NAME then a more generic - // solution may be required. - uint32_t name = mCollection.getRecord (stage).mModified.sRecordId; - mState.getWriter().startRecord (name); - - if(name != ESM::REC_SKIL && name != ESM::REC_MGEF && name != ESM::REC_SCPT) - mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); - mCollection.getRecord (stage).mModified.save (mState.getWriter()); - mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, state == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp index c8da860693..3c8c107ba0 100644 --- a/apps/opencs/model/doc/stage.cpp +++ b/apps/opencs/model/doc/stage.cpp @@ -1,5 +1,3 @@ #include "stage.hpp" CSMDoc::Stage::~Stage() {} - -void CSMDoc::Stage::updateUserSetting (const QString& name, const QStringList& value) {} diff --git a/apps/opencs/model/doc/stage.hpp b/apps/opencs/model/doc/stage.hpp index e0328a91a1..1eae27a982 100644 --- a/apps/opencs/model/doc/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -23,11 +23,7 @@ namespace CSMDoc virtual void perform (int stage, Messages& messages) = 0; ///< Messages resulting from this stage will be appended to \a messages. - - /// Default-implementation: ignore - virtual void updateUserSetting (const QString& name, const QStringList& value); }; } #endif - diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp new file mode 100644 index 0000000000..6c0babaf7d --- /dev/null +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -0,0 +1,47 @@ + +#include "boolsetting.hpp" + +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::BoolSetting::BoolSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, bool default_) +: Setting (parent, values, mutex, key, label), mDefault (default_) +{} + +CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::BoolSetting::makeWidgets (QWidget *parent) +{ + QCheckBox *widget = new QCheckBox (QString::fromUtf8 (getLabel().c_str()), parent); + widget->setCheckState (mDefault ? Qt::Checked : Qt::Unchecked); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (stateChanged (int)), this, SLOT (valueChanged (int))); + + return std::make_pair (static_cast (0), widget); +} + +void CSMPrefs::BoolSetting::valueChanged (int value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setBool (getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp new file mode 100644 index 0000000000..f8a321859f --- /dev/null +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -0,0 +1,31 @@ +#ifndef CSM_PREFS_BOOLSETTING_H +#define CSM_PREFS_BOOLSETTING_H + +#include "setting.hpp" + +namespace CSMPrefs +{ + class BoolSetting : public Setting + { + Q_OBJECT + + std::string mTooltip; + bool mDefault; + + public: + + BoolSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, bool default_); + + BoolSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (int value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp new file mode 100644 index 0000000000..6af0ac645b --- /dev/null +++ b/apps/opencs/model/prefs/category.cpp @@ -0,0 +1,51 @@ + +#include "category.hpp" + +#include + +#include "setting.hpp" +#include "state.hpp" + +CSMPrefs::Category::Category (State *parent, const std::string& key) +: mParent (parent), mKey (key) +{} + +const std::string& CSMPrefs::Category::getKey() const +{ + return mKey; +} + +CSMPrefs::State *CSMPrefs::Category::getState() const +{ + return mParent; +} + +void CSMPrefs::Category::addSetting (Setting *setting) +{ + mSettings.push_back (setting); +} + +CSMPrefs::Category::Iterator CSMPrefs::Category::begin() +{ + return mSettings.begin(); +} + +CSMPrefs::Category::Iterator CSMPrefs::Category::end() +{ + return mSettings.end(); +} + +CSMPrefs::Setting& CSMPrefs::Category::operator[] (const std::string& key) +{ + for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) + if ((*iter)->getKey()==key) + return **iter; + + throw std::logic_error ("Invalid user setting: " + key); +} + +void CSMPrefs::Category::update() +{ + for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) + mParent->update (**iter); +} diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp new file mode 100644 index 0000000000..b70716aa0e --- /dev/null +++ b/apps/opencs/model/prefs/category.hpp @@ -0,0 +1,45 @@ +#ifndef CSM_PREFS_CATEGORY_H +#define CSM_PREFS_CATEGORY_H + +#include +#include + +namespace CSMPrefs +{ + class State; + class Setting; + + class Category + { + public: + + typedef std::vector Container; + typedef Container::iterator Iterator; + + private: + + State *mParent; + std::string mKey; + Container mSettings; + + public: + + Category (State *parent, const std::string& key); + + const std::string& getKey() const; + + State *getState() const; + + void addSetting (Setting *setting); + + Iterator begin(); + + Iterator end(); + + Setting& operator[] (const std::string& key); + + void update(); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp new file mode 100644 index 0000000000..d51bfad567 --- /dev/null +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -0,0 +1,52 @@ + +#include "coloursetting.hpp" + +#include +#include + +#include + +#include "../../view/widget/coloreditor.hpp" + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::ColourSetting::ColourSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, QColor default_) +: Setting (parent, values, mutex, key, label), mDefault (default_) +{} + +CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::ColourSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + CSVWidget::ColorEditor *widget = new CSVWidget::ColorEditor (mDefault, parent); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (pickingFinished()), this, SLOT (valueChanged())); + + return std::make_pair (label, widget); +} + +void CSMPrefs::ColourSetting::valueChanged() +{ + CSVWidget::ColorEditor& widget = dynamic_cast (*sender()); + { + QMutexLocker lock (getMutex()); + getValues().setString (getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp new file mode 100644 index 0000000000..be58426f2a --- /dev/null +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_PREFS_COLOURSETTING_H +#define CSM_PREFS_COLOURSETTING_H + +#include "setting.hpp" + +#include + +namespace CSMPrefs +{ + class ColourSetting : public Setting + { + Q_OBJECT + + std::string mTooltip; + QColor mDefault; + + public: + + ColourSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, + QColor default_); + + ColourSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged(); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp new file mode 100644 index 0000000000..7c247777d2 --- /dev/null +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -0,0 +1,75 @@ + +#include "doublesetting.hpp" + +#include + +#include +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, double default_) +: Setting (parent, values, mutex, key, label), + mMin (0), mMax (std::numeric_limits::max()), + mDefault (default_) +{} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange (double min, double max) +{ + mMin = min; + mMax = max; + return *this; +} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMin (double min) +{ + mMin = min; + return *this; +} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMax (double max) +{ + mMax = max; + return *this; +} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::DoubleSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + QDoubleSpinBox *widget = new QDoubleSpinBox (parent); + widget->setRange (mMin, mMax); + widget->setValue (mDefault); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (valueChanged (double)), this, SLOT (valueChanged (double))); + + return std::make_pair (label, widget); +} + +void CSMPrefs::DoubleSetting::valueChanged (double value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setFloat (getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp new file mode 100644 index 0000000000..3868f014e2 --- /dev/null +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -0,0 +1,41 @@ +#ifndef CSM_PREFS_DOUBLESETTING_H +#define CSM_PREFS_DOUBLESETTING_H + +#include "setting.hpp" + +namespace CSMPrefs +{ + class DoubleSetting : public Setting + { + Q_OBJECT + + double mMin; + double mMax; + std::string mTooltip; + double mDefault; + + public: + + DoubleSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, + double default_); + + // defaults to [0, std::numeric_limits::max()] + DoubleSetting& setRange (double min, double max); + + DoubleSetting& setMin (double min); + + DoubleSetting& setMax (double max); + + DoubleSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (double value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp new file mode 100644 index 0000000000..e69f717ead --- /dev/null +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -0,0 +1,112 @@ + +#include "enumsetting.hpp" + +#include +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + + +CSMPrefs::EnumValue::EnumValue (const std::string& value, const std::string& tooltip) +: mValue (value), mTooltip (tooltip) +{} + +CSMPrefs::EnumValue::EnumValue (const char *value) +: mValue (value) +{} + + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValues& values) +{ + mValues.insert (mValues.end(), values.mValues.begin(), values.mValues.end()); + return *this; +} + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValue& value) +{ + mValues.push_back (value); + return *this; +} + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const std::string& value, const std::string& tooltip) +{ + mValues.push_back (EnumValue (value, tooltip)); + return *this; +} + + +CSMPrefs::EnumSetting::EnumSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_) +: Setting (parent, values, mutex, key, label), mDefault (default_) +{} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues (const EnumValues& values) +{ + mValues.add (values); + return *this; +} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const EnumValue& value) +{ + mValues.add (value); + return *this; +} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const std::string& value, const std::string& tooltip) +{ + mValues.add (value, tooltip); + return *this; +} + +std::pair CSMPrefs::EnumSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + QComboBox *widget = new QComboBox (parent); + + int index = 0; + + for (int i=0; i (mValues.mValues.size()); ++i) + { + if (mDefault.mValue==mValues.mValues[i].mValue) + index = i; + + widget->addItem (QString::fromUtf8 (mValues.mValues[i].mValue.c_str())); + + if (!mValues.mValues[i].mTooltip.empty()) + widget->setItemData (i, QString::fromUtf8 (mValues.mValues[i].mTooltip.c_str()), + Qt::ToolTipRole); + } + + widget->setCurrentIndex (index); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + } + + connect (widget, SIGNAL (currentIndexChanged (int)), this, SLOT (valueChanged (int))); + + return std::make_pair (label, widget); +} + +void CSMPrefs::EnumSetting::valueChanged (int value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setString (getKey(), getParent()->getKey(), mValues.mValues.at (value).mValue); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp new file mode 100644 index 0000000000..728de3acd2 --- /dev/null +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -0,0 +1,62 @@ +#ifndef CSM_PREFS_ENUMSETTING_H +#define CSM_PREFS_ENUMSETTING_H + +#include + +#include "setting.hpp" + +namespace CSMPrefs +{ + struct EnumValue + { + std::string mValue; + std::string mTooltip; + + EnumValue (const std::string& value, const std::string& tooltip = ""); + + EnumValue (const char *value); + }; + + struct EnumValues + { + std::vector mValues; + + EnumValues& add (const EnumValues& values); + + EnumValues& add (const EnumValue& value); + + EnumValues& add (const std::string& value, const std::string& tooltip); + }; + + class EnumSetting : public Setting + { + Q_OBJECT + + std::string mTooltip; + EnumValue mDefault; + EnumValues mValues; + + public: + + EnumSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, + const EnumValue& default_); + + EnumSetting& setTooltip (const std::string& tooltip); + + EnumSetting& addValues (const EnumValues& values); + + EnumSetting& addValue (const EnumValue& value); + + EnumSetting& addValue (const std::string& value, const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (int value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp new file mode 100644 index 0000000000..269a63af43 --- /dev/null +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -0,0 +1,74 @@ + +#include "intsetting.hpp" + +#include + +#include +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::IntSetting::IntSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, int default_) +: Setting (parent, values, mutex, key, label), mMin (0), mMax (std::numeric_limits::max()), + mDefault (default_) +{} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setRange (int min, int max) +{ + mMin = min; + mMax = max; + return *this; +} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMin (int min) +{ + mMin = min; + return *this; +} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMax (int max) +{ + mMax = max; + return *this; +} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::IntSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + QSpinBox *widget = new QSpinBox (parent); + widget->setRange (mMin, mMax); + widget->setValue (mDefault); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); + + return std::make_pair (label, widget); +} + +void CSMPrefs::IntSetting::valueChanged (int value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setInt (getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp new file mode 100644 index 0000000000..0ee6cf9e34 --- /dev/null +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -0,0 +1,40 @@ +#ifndef CSM_PREFS_INTSETTING_H +#define CSM_PREFS_INTSETTING_H + +#include "setting.hpp" + +namespace CSMPrefs +{ + class IntSetting : public Setting + { + Q_OBJECT + + int mMin; + int mMax; + std::string mTooltip; + int mDefault; + + public: + + IntSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, int default_); + + // defaults to [0, std::numeric_limits::max()] + IntSetting& setRange (int min, int max); + + IntSetting& setMin (int min); + + IntSetting& setMax (int max); + + IntSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (int value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp new file mode 100644 index 0000000000..75b58322d9 --- /dev/null +++ b/apps/opencs/model/prefs/setting.cpp @@ -0,0 +1,97 @@ + +#include "setting.hpp" + +#include +#include + +#include "category.hpp" +#include "state.hpp" + +Settings::Manager& CSMPrefs::Setting::getValues() +{ + return *mValues; +} + +QMutex *CSMPrefs::Setting::getMutex() +{ + return mMutex; +} + +CSMPrefs::Setting::Setting (Category *parent, Settings::Manager *values, QMutex *mutex, + const std::string& key, const std::string& label) +: QObject (parent->getState()), mParent (parent), mValues (values), mMutex (mutex), mKey (key), + mLabel (label) +{} + +CSMPrefs::Setting:: ~Setting() {} + +std::pair CSMPrefs::Setting::makeWidgets (QWidget *parent) +{ + return std::pair (0, 0); +} + +const CSMPrefs::Category *CSMPrefs::Setting::getParent() const +{ + return mParent; +} + +const std::string& CSMPrefs::Setting::getKey() const +{ + return mKey; +} + +const std::string& CSMPrefs::Setting::getLabel() const +{ + return mLabel; +} + +int CSMPrefs::Setting::toInt() const +{ + QMutexLocker lock (mMutex); + return mValues->getInt (mKey, mParent->getKey()); +} + +double CSMPrefs::Setting::toDouble() const +{ + QMutexLocker lock (mMutex); + return mValues->getFloat (mKey, mParent->getKey()); +} + +std::string CSMPrefs::Setting::toString() const +{ + QMutexLocker lock (mMutex); + return mValues->getString (mKey, mParent->getKey()); +} + +bool CSMPrefs::Setting::isTrue() const +{ + QMutexLocker lock (mMutex); + return mValues->getBool (mKey, mParent->getKey()); +} + +QColor CSMPrefs::Setting::toColor() const +{ + // toString() handles lock + return QColor (QString::fromUtf8 (toString().c_str())); +} + +bool CSMPrefs::operator== (const Setting& setting, const std::string& key) +{ + std::string fullKey = setting.getParent()->getKey() + "/" + setting.getKey(); + return fullKey==key; +} + +bool CSMPrefs::operator== (const std::string& key, const Setting& setting) +{ + return setting==key; +} + +bool CSMPrefs::operator!= (const Setting& setting, const std::string& key) +{ + return !(setting==key); +} + +bool CSMPrefs::operator!= (const std::string& key, const Setting& setting) +{ + return !(key==setting); +} diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp new file mode 100644 index 0000000000..00bcc638bb --- /dev/null +++ b/apps/opencs/model/prefs/setting.hpp @@ -0,0 +1,74 @@ +#ifndef CSM_PREFS_SETTING_H +#define CSM_PREFS_SETTING_H + +#include +#include + +#include + +class QWidget; +class QColor; +class QMutex; + +namespace Settings +{ + class Manager; +} + +namespace CSMPrefs +{ + class Category; + + class Setting : public QObject + { + Q_OBJECT + + Category *mParent; + Settings::Manager *mValues; + QMutex *mMutex; + std::string mKey; + std::string mLabel; + + protected: + + Settings::Manager& getValues(); + + QMutex *getMutex(); + + public: + + Setting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label); + + virtual ~Setting(); + + /// Return label, input widget. + /// + /// \note first can be a 0-pointer, which means that the label is part of the input + /// widget. + virtual std::pair makeWidgets (QWidget *parent); + + const Category *getParent() const; + + const std::string& getKey() const; + + const std::string& getLabel() const; + + int toInt() const; + + double toDouble() const; + + std::string toString() const; + + bool isTrue() const; + + QColor toColor() const; + }; + + // note: fullKeys have the format categoryKey/settingKey + bool operator== (const Setting& setting, const std::string& fullKey); + bool operator== (const std::string& fullKey, const Setting& setting); + bool operator!= (const Setting& setting, const std::string& fullKey); + bool operator!= (const std::string& fullKey, const Setting& setting); +} + +#endif diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp new file mode 100644 index 0000000000..8b827d0a2b --- /dev/null +++ b/apps/opencs/model/prefs/state.cpp @@ -0,0 +1,392 @@ + +#include "state.hpp" + +#include +#include +#include + +#include "intsetting.hpp" +#include "doublesetting.hpp" +#include "boolsetting.hpp" +#include "coloursetting.hpp" + +CSMPrefs::State *CSMPrefs::State::sThis = 0; + +void CSMPrefs::State::load() +{ + // default settings file + boost::filesystem::path local = mConfigurationManager.getLocalPath() / mConfigFile; + boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mConfigFile; + + if (boost::filesystem::exists (local)) + mSettings.loadDefault (local.string()); + else if (boost::filesystem::exists (global)) + mSettings.loadDefault (global.string()); + else + throw std::runtime_error ("No default settings file found! Make sure the file \"openmw-cs.cfg\" was properly installed."); + + // user settings file + boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; + + if (boost::filesystem::exists (user)) + mSettings.loadUser (user.string()); +} + +void CSMPrefs::State::declare() +{ + declareCategory ("Windows"); + declareInt ("default-width", "Default window width", 800). + setTooltip ("Newly opened top-level windows will open with this width."). + setMin (80); + declareInt ("default-height", "Default window height", 600). + setTooltip ("Newly opened top-level windows will open with this height."). + setMin (80); + declareBool ("show-statusbar", "Show Status Bar", true). + setTooltip ("If a newly open top level window is showing status bars or not. " + " Note that this does not affect existing windows."); + declareSeparator(); + declareBool ("reuse", "Reuse Subviews", true). + setTooltip ("When a new subview is requested and a matching subview already " + " exist, do not open a new subview and use the existing one instead."); + declareInt ("max-subviews", "Maximum number of subviews per top-level window", 256). + setTooltip ("If the maximum number is reached and a new subview is opened " + "it will be placed into a new top-level window."). + setRange (1, 256); + declareBool ("hide-subview", "Hide single subview", false). + setTooltip ("When a view contains only a single subview, hide the subview title " + "bar and if this subview is closed also close the view (unless it is the last " + "view for this document)"); + declareInt ("minimum-width", "Minimum subview width", 325). + setTooltip ("Minimum width of subviews."). + setRange (50, 10000); + declareSeparator(); + EnumValue scrollbarOnly ("Scrollbar Only", "Simple addition of scrollbars, the view window " + "does not grow automatically."); + declareEnum ("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly). + addValue (scrollbarOnly). + addValue ("Grow Only", "The view window grows as subviews are added. No scrollbars."). + addValue ("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); + declareBool ("grow-limit", "Grow Limit Screen", false). + setTooltip ("When \"Grow then Scroll\" option is selected, the window size grows to" + " the width of the virtual desktop. \nIf this option is selected the the window growth" + "is limited to the current screen."); + + declareCategory ("Records"); + EnumValue iconAndText ("Icon and Text"); + EnumValues recordValues; + recordValues.add (iconAndText).add ("Icon Only").add ("Text Only"); + declareEnum ("status-format", "Modification status display format", iconAndText). + addValues (recordValues); + declareEnum ("type-format", "ID type display format", iconAndText). + addValues (recordValues); + + declareCategory ("ID Tables"); + EnumValue inPlaceEdit ("Edit in Place", "Edit the clicked cell"); + EnumValue editRecord ("Edit Record", "Open a dialogue subview for the clicked record"); + EnumValue view ("View", "Open a scene subview for the clicked record (not available everywhere)"); + EnumValue editRecordAndClose ("Edit Record and Close"); + EnumValues doubleClickValues; + doubleClickValues.add (inPlaceEdit).add (editRecord).add (view).add ("Revert"). + add ("Delete").add (editRecordAndClose). + add ("View and Close", "Open a scene subview for the clicked record and close the table subview"); + declareEnum ("double", "Double Click", inPlaceEdit).addValues (doubleClickValues); + declareEnum ("double-s", "Shift Double Click", editRecord).addValues (doubleClickValues); + declareEnum ("double-c", "Control Double Click", view).addValues (doubleClickValues); + declareEnum ("double-sc", "Shift Control Double Click", editRecordAndClose).addValues (doubleClickValues); + declareSeparator(); + EnumValue jumpAndSelect ("Jump and Select", "Scroll new record into view and make it the selection"); + declareEnum ("jump-to-added", "Action on adding or cloning a record", jumpAndSelect). + addValue (jumpAndSelect). + addValue ("Jump Only", "Scroll new record into view"). + addValue ("No Jump", "No special action"); + declareBool ("extended-config", + "Manually specify affected record types for an extended delete/revert", false). + setTooltip ("Delete and revert commands have an extended form that also affects " + "associated records.\n\n" + "If this option is enabled, types of affected records are selected " + "manually before a command execution.\nOtherwise, all associated " + "records are deleted/reverted immediately."); + + declareCategory ("ID Dialogues"); + declareBool ("toolbar", "Show toolbar", true); + + declareCategory ("Reports"); + EnumValue actionNone ("None"); + EnumValue actionEdit ("Edit", "Open a table or dialogue suitable for addressing the listed report"); + EnumValue actionRemove ("Remove", "Remove the report from the report table"); + EnumValue actionEditAndRemove ("Edit And Remove", "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table"); + EnumValues reportValues; + reportValues.add (actionNone).add (actionEdit).add (actionRemove).add (actionEditAndRemove); + declareEnum ("double", "Double Click", actionEdit).addValues (reportValues); + declareEnum ("double-s", "Shift Double Click", actionRemove).addValues (reportValues); + declareEnum ("double-c", "Control Double Click", actionEditAndRemove).addValues (reportValues); + declareEnum ("double-sc", "Shift Control Double Click", actionNone).addValues (reportValues); + + declareCategory ("Search & Replace"); + declareInt ("char-before", "Characters before search string", 10). + setTooltip ("Maximum number of character to display in search result before the searched text"); + declareInt ("char-after", "Characters after search string", 10). + setTooltip ("Maximum number of character to display in search result after the searched text"); + declareBool ("auto-delete", "Delete row from result table after a successful replace", true); + + declareCategory ("Scripts"); + declareBool ("show-linenum", "Show Line Numbers", true). + setTooltip ("Show line numbers to the left of the script editor window." + "The current row and column numbers of the text cursor are shown at the bottom."); + declareBool ("mono-font", "Use monospace font", true); + EnumValue warningsNormal ("Normal", "Report warnings as warning"); + declareEnum ("warnings", "Warning Mode", warningsNormal). + addValue ("Ignore", "Do not report warning"). + addValue (warningsNormal). + addValue ("Strcit", "Promote warning to an error"); + declareBool ("toolbar", "Show toolbar", true); + declareInt ("compile-delay", "Delay between updating of source errors", 100). + setTooltip ("Delay in milliseconds"). + setRange (0, 10000); + declareInt ("error-height", "Initial height of the error panel", 100). + setRange (100, 10000); + declareSeparator(); + declareColour ("colour-int", "Highlight Colour: Integer Literals", QColor ("darkmagenta")); + declareColour ("colour-float", "Highlight Colour: Float Literals", QColor ("magenta")); + declareColour ("colour-name", "Highlight Colour: Names", QColor ("grey")); + declareColour ("colour-keyword", "Highlight Colour: Keywords", QColor ("red")); + declareColour ("colour-special", "Highlight Colour: Special Characters", QColor ("darkorange")); + declareColour ("colour-comment", "Highlight Colour: Comments", QColor ("green")); + declareColour ("colour-id", "Highlight Colour: IDs", QColor ("blue")); + + declareCategory ("General Input"); + declareBool ("cycle", "Cyclic next/previous", false). + setTooltip ("When using next/previous functions at the last/first item of a " + "list go to the first/last item"); + + declareCategory ("3D Scene Input"); + EnumValue left ("Left Mouse-Button"); + EnumValue cLeft ("Ctrl-Left Mouse-Button"); + EnumValue right ("Right Mouse-Button"); + EnumValue cRight ("Ctrl-Right Mouse-Button"); + EnumValue middle ("Middle Mouse-Button"); + EnumValue cMiddle ("Ctrl-Middle Mouse-Button"); + EnumValues inputButtons; + inputButtons.add (left).add (cLeft).add (right).add (cRight).add (middle).add (cMiddle); + declareEnum ("p-navi", "Primary Camera Navigation Button", left).addValues (inputButtons); + declareEnum ("s-navi", "Secondary Camera Navigation Button", cLeft).addValues (inputButtons); + declareEnum ("p-edit", "Primary Editing Button", right).addValues (inputButtons); + declareEnum ("s-edit", "Secondary Editing Button", cRight).addValues (inputButtons); + declareEnum ("p-select", "Primary Selection Button", middle).addValues (inputButtons); + declareEnum ("s-select", "Secondary Selection Button", cMiddle).addValues (inputButtons); + declareSeparator(); + declareBool ("context-select", "Context Sensitive Selection", false); + declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). + setRange (0.001, 100.0); + declareDouble ("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0). + setRange (0.001, 100.0); + declareDouble ("drag-shift-factor", + "Shift-acceleration factor during drag operations", 4.0). + setTooltip ("Acceleration factor during drag operations while holding down shift"). + setRange (0.001, 100.0); + + declareCategory ("Tooltips"); + declareBool ("scene", "Show Tooltips in 3D scenes", true); + declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); + declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). + setMin (1); +} + +void CSMPrefs::State::declareCategory (const std::string& key) +{ + std::map::iterator iter = mCategories.find (key); + + if (iter!=mCategories.end()) + { + mCurrentCategory = iter; + } + else + { + mCurrentCategory = + mCategories.insert (std::make_pair (key, Category (this, key))).first; + } +} + +CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key, + const std::string& label, int default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + std::ostringstream stream; + stream << default_; + setDefault (key, stream.str()); + + default_ = mSettings.getInt (key, mCurrentCategory->second.getKey()); + + CSMPrefs::IntSetting *setting = + new CSMPrefs::IntSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key, + const std::string& label, double default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + std::ostringstream stream; + stream << default_; + setDefault (key, stream.str()); + + default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); + + CSMPrefs::DoubleSetting *setting = + new CSMPrefs::DoubleSetting (&mCurrentCategory->second, &mSettings, &mMutex, + key, label, default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool (const std::string& key, + const std::string& label, bool default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + setDefault (key, default_ ? "true" : "false"); + + default_ = mSettings.getBool (key, mCurrentCategory->second.getKey()); + + CSMPrefs::BoolSetting *setting = + new CSMPrefs::BoolSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum (const std::string& key, + const std::string& label, EnumValue default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + setDefault (key, default_.mValue); + + default_.mValue = mSettings.getString (key, mCurrentCategory->second.getKey()); + + CSMPrefs::EnumSetting *setting = + new CSMPrefs::EnumSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour (const std::string& key, + const std::string& label, QColor default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + setDefault (key, default_.name().toUtf8().data()); + + default_.setNamedColor (QString::fromUtf8 (mSettings.getString (key, mCurrentCategory->second.getKey()).c_str())); + + CSMPrefs::ColourSetting *setting = + new CSMPrefs::ColourSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +void CSMPrefs::State::declareSeparator() +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + CSMPrefs::Setting *setting = + new CSMPrefs::Setting (&mCurrentCategory->second, &mSettings, &mMutex, "", ""); + + mCurrentCategory->second.addSetting (setting); +} + +void CSMPrefs::State::setDefault (const std::string& key, const std::string& default_) +{ + Settings::CategorySetting fullKey (mCurrentCategory->second.getKey(), key); + + Settings::CategorySettingValueMap::iterator iter = + mSettings.mDefaultSettings.find (fullKey); + + if (iter==mSettings.mDefaultSettings.end()) + mSettings.mDefaultSettings.insert (std::make_pair (fullKey, default_)); +} + +CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) +: mConfigFile ("openmw-cs.cfg"), mConfigurationManager (configurationManager), + mCurrentCategory (mCategories.end()) +{ + if (sThis) + throw std::logic_error ("An instance of CSMPRefs::State already exists"); + + load(); + declare(); + + sThis = this; +} + +CSMPrefs::State::~State() +{ + sThis = 0; +} + +void CSMPrefs::State::save() +{ + boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; + mSettings.saveUser (user.string()); +} + +CSMPrefs::State::Iterator CSMPrefs::State::begin() +{ + return mCategories.begin(); +} + +CSMPrefs::State::Iterator CSMPrefs::State::end() +{ + return mCategories.end(); +} + +CSMPrefs::Category& CSMPrefs::State::operator[] (const std::string& key) +{ + Iterator iter = mCategories.find (key); + + if (iter==mCategories.end()) + throw std::logic_error ("Invalid user settings category: " + key); + + return iter->second; +} + +void CSMPrefs::State::update (const Setting& setting) +{ + emit (settingChanged (&setting)); +} + +CSMPrefs::State& CSMPrefs::State::get() +{ + if (!sThis) + throw std::logic_error ("No instance of CSMPrefs::State"); + + return *sThis; +} + + +CSMPrefs::State& CSMPrefs::get() +{ + return State::get(); +} diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp new file mode 100644 index 0000000000..bcd76c6712 --- /dev/null +++ b/apps/opencs/model/prefs/state.hpp @@ -0,0 +1,105 @@ +#ifndef CSV_PREFS_STATE_H +#define CSM_PREFS_STATE_H + +#include +#include + +#include +#include + +#ifndef Q_MOC_RUN +#include +#endif + +#include + +#include "category.hpp" +#include "setting.hpp" +#include "enumsetting.hpp" + +class QColor; + +namespace CSMPrefs +{ + class IntSetting; + class DoubleSetting; + class BoolSetting; + class ColourSetting; + + /// \brief User settings state + /// + /// \note Access to the user settings is thread-safe once all declarations and loading has + /// been completed. + class State : public QObject + { + Q_OBJECT + + static State *sThis; + + public: + + typedef std::map Collection; + typedef Collection::iterator Iterator; + + private: + + const std::string mConfigFile; + const Files::ConfigurationManager& mConfigurationManager; + Settings::Manager mSettings; + Collection mCategories; + Iterator mCurrentCategory; + QMutex mMutex; + + // not implemented + State (const State&); + State& operator= (const State&); + + private: + + void load(); + + void declare(); + + void declareCategory (const std::string& key); + + IntSetting& declareInt (const std::string& key, const std::string& label, int default_); + DoubleSetting& declareDouble (const std::string& key, const std::string& label, double default_); + + BoolSetting& declareBool (const std::string& key, const std::string& label, bool default_); + + EnumSetting& declareEnum (const std::string& key, const std::string& label, EnumValue default_); + + ColourSetting& declareColour (const std::string& key, const std::string& label, QColor default_); + + void declareSeparator(); + + void setDefault (const std::string& key, const std::string& default_); + + public: + + State (const Files::ConfigurationManager& configurationManager); + + ~State(); + + void save(); + + Iterator begin(); + + Iterator end(); + + Category& operator[](const std::string& key); + + void update (const Setting& setting); + + static State& get(); + + signals: + + void settingChanged (const CSMPrefs::Setting *setting); + }; + + // convenience function + State& get(); +} + +#endif diff --git a/apps/opencs/model/settings/connector.cpp b/apps/opencs/model/settings/connector.cpp deleted file mode 100644 index 3cf21123c5..0000000000 --- a/apps/opencs/model/settings/connector.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "connector.hpp" -#include "../../view/settings/view.hpp" -#include "../../view/settings/page.hpp" - -CSMSettings::Connector::Connector(CSVSettings::View *master, - QObject *parent) - : QObject(parent), mMasterView (master) -{} - -void CSMSettings::Connector::addSlaveView (CSVSettings::View *view, - QList &masterProxyValues) -{ - mSlaveViews.append (view); - - mProxyListMap[view->viewKey()].append (masterProxyValues); -} - -QList CSMSettings::Connector::getSlaveViewValues() const -{ - QList list; - - foreach (const CSVSettings::View *view, mSlaveViews) - list.append (view->selectedValues()); - - return list; -} - -bool CSMSettings::Connector::proxyListsMatch ( - const QList &list1, - const QList &list2) const -{ - bool success = true; - - for (int i = 0; i < list1.size(); i++) - { - success = stringListsMatch (list1.at(i), list2.at(i)); - - if (!success) - break; - } - return success; -} - -void CSMSettings::Connector::slotUpdateMaster() const -{ - //list of the current values for each slave. - QList slaveValueList = getSlaveViewValues(); - - int masterColumn = -1; - - /* - * A row in the master view is one of the values in the - * master view's data model. This corresponds directly to the number of - * values in a proxy list contained in the ProxyListMap member. - * Thus, we iterate each "column" in the master proxy list - * (one for each vlaue in the master. Each column represents - * one master value's corresponding list of slave values. We examine - * each master value's list, comparing it to the current slave value list, - * stopping when we find a match using proxyListsMatch(). - * - * If no match is found, clear the master view's value - */ - - for (int i = 0; i < mMasterView->rowCount(); i++) - { - QList proxyValueList; - - foreach (const QString &settingKey, mProxyListMap.keys()) - { - // append the proxy value list stored in the i'th column - // for each setting key. A setting key is the id of the setting - // in page.name format. - proxyValueList.append (mProxyListMap.value(settingKey).at(i)); - } - - if (proxyListsMatch (slaveValueList, proxyValueList)) - { - masterColumn = i; - break; - } - } - - QString masterValue = mMasterView->value (masterColumn); - - mMasterView->setSelectedValue (masterValue); -} - -void CSMSettings::Connector::slotUpdateSlaves() const -{ - int row = mMasterView->currentIndex(); - - if (row == -1) - return; - - //iterate the proxy lists for the chosen master index - //and pass the list to each slave for updating - for (int i = 0; i < mSlaveViews.size(); i++) - { - QList proxyList = - mProxyListMap.value(mSlaveViews.at(i)->viewKey()); - - mSlaveViews.at(i)->setSelectedValues (proxyList.at(row)); - } -} - -bool CSMSettings::Connector::stringListsMatch ( - const QStringList &list1, - const QStringList &list2) const -{ - //returns a "sloppy" match, verifying that each list contains all the same - //items, though not necessarily in the same order. - - if (list1.size() != list2.size()) - return false; - - QStringList tempList(list2); - - //iterate each value in the list, removing one occurrence of the value in - //the other list. If no corresponding value is found, test fails - foreach (const QString &value, list1) - { - if (!tempList.contains(value)) - return false; - - tempList.removeOne(value); - } - return true; -} diff --git a/apps/opencs/model/settings/connector.hpp b/apps/opencs/model/settings/connector.hpp deleted file mode 100644 index aaf9936d54..0000000000 --- a/apps/opencs/model/settings/connector.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef CSMSETTINGS_CONNECTOR_HPP -#define CSMSETTINGS_CONNECTOR_HPP - -#include -#include -#include -#include - -#include "support.hpp" - -namespace CSVSettings { - class View; -} - -namespace CSMSettings { - - class Connector : public QObject - { - Q_OBJECT - - CSVSettings::View *mMasterView; - - ///map using the view pointer as a key to it's index value - QList mSlaveViews; - - ///list of proxy values for each master value. - ///value list order is indexed to the master value index. - QMap < QString, QList > mProxyListMap; - - public: - explicit Connector(CSVSettings::View *master, - QObject *parent = 0); - - ///Set the view which acts as a proxy for other setting views - void setMasterView (CSVSettings::View *view); - - ///Add a view to be updated / update to the master - void addSlaveView (CSVSettings::View *view, - QList &masterProxyValues); - - private: - - ///loosely matches lists of proxy values across registered slaves - ///against a proxy value list for a given master value - bool proxyListsMatch (const QList &list1, - const QList &list2) const; - - ///loosely matches two string lists - bool stringListsMatch (const QStringList &list1, - const QStringList &list2) const; - - ///retrieves current values of registered slave views - QList getSlaveViewValues() const; - - public slots: - - ///updates slave views with proxy values associated with current - ///master value - void slotUpdateSlaves() const; - - ///updates master value associated with the currently selected - ///slave values, if applicable. - void slotUpdateMaster() const; - }; -} - -#endif // CSMSETTINGS_CONNECTOR_HPP diff --git a/apps/opencs/model/settings/setting.cpp b/apps/opencs/model/settings/setting.cpp deleted file mode 100644 index 9e33ab9168..0000000000 --- a/apps/opencs/model/settings/setting.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include "setting.hpp" -#include "support.hpp" - -CSMSettings::Setting::Setting(SettingType typ, const QString &settingName, - const QString &pageName, const QString& label) -: mIsEditorSetting (true) -{ - buildDefaultSetting(); - - int settingType = static_cast (typ); - - //even-numbered setting types are multi-valued - if ((settingType % 2) == 0) - setProperty (Property_IsMultiValue, QVariant(true).toString()); - - //view type is related to setting type by an order of magnitude - setProperty (Property_SettingType, QVariant (settingType).toString()); - setProperty (Property_Page, pageName); - setProperty (Property_Name, settingName); - setProperty (Property_Label, label.isEmpty() ? settingName : label); -} - -void CSMSettings::Setting::buildDefaultSetting() -{ - int arrLen = sizeof(sPropertyDefaults) / sizeof (*sPropertyDefaults); - - for (int i = 0; i < arrLen; i++) - { - QStringList propertyList; - - if (i list; - - foreach (const QString &val, vals) - list << (QStringList() << val); - - mProxies [setting->page() + '/' + setting->name()] = list; -} - -void CSMSettings::Setting::addProxy (const Setting *setting, - const QList &list) -{ - if (serializable()) - setProperty (Property_Serializable, false); - - mProxies [setting->page() + '/' + setting->name()] = list; -} - -void CSMSettings::Setting::setColumnSpan (int value) -{ - setProperty (Property_ColumnSpan, value); -} - -int CSMSettings::Setting::columnSpan() const -{ - return property (Property_ColumnSpan).at(0).toInt(); -} - -void CSMSettings::Setting::setDeclaredValues (QStringList list) -{ - setProperty (Property_DeclaredValues, list); -} - -QStringList CSMSettings::Setting::declaredValues() const -{ - return property (Property_DeclaredValues); -} - -QStringList CSMSettings::Setting::property (SettingProperty prop) const -{ - if (prop >= mProperties.size()) - return QStringList(); - - return mProperties.at(prop); -} - -void CSMSettings::Setting::setDefaultValue (int value) -{ - setDefaultValues (QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setDefaultValue (double value) -{ - setDefaultValues (QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setDefaultValue (const QString &value) -{ - setDefaultValues (QStringList() << value); -} - -void CSMSettings::Setting::setDefaultValues (const QStringList &values) -{ - setProperty (Property_DefaultValues, values); -} - -QStringList CSMSettings::Setting::defaultValues() const -{ - return property (Property_DefaultValues); -} - -void CSMSettings::Setting::setDelimiter (const QString &value) -{ - setProperty (Property_Delimiter, value); -} - -QString CSMSettings::Setting::delimiter() const -{ - return property (Property_Delimiter).at(0); -} - -void CSMSettings::Setting::setEditorSetting(bool state) -{ - mIsEditorSetting = true; -} - -bool CSMSettings::Setting::isEditorSetting() const -{ - return mIsEditorSetting; -} -void CSMSettings::Setting::setIsMultiLine (bool state) -{ - setProperty (Property_IsMultiLine, state); -} - -bool CSMSettings::Setting::isMultiLine() const -{ - return (property (Property_IsMultiLine).at(0) == "true"); -} - -void CSMSettings::Setting::setIsMultiValue (bool state) -{ - setProperty (Property_IsMultiValue, state); -} - -bool CSMSettings::Setting::isMultiValue() const -{ - return (property (Property_IsMultiValue).at(0) == "true"); -} - -const CSMSettings::ProxyValueMap &CSMSettings::Setting::proxyLists() const -{ - return mProxies; -} - -void CSMSettings::Setting::setSerializable (bool state) -{ - setProperty (Property_Serializable, state); -} - -bool CSMSettings::Setting::serializable() const -{ - return (property (Property_Serializable).at(0) == "true"); -} - -void CSMSettings::Setting::setSpecialValueText(const QString &text) -{ - setProperty (Property_SpecialValueText, text); -} - -QString CSMSettings::Setting::specialValueText() const -{ - return property (Property_SpecialValueText).at(0); -} - -void CSMSettings::Setting::setName (const QString &value) -{ - setProperty (Property_Name, value); -} - -QString CSMSettings::Setting::name() const -{ - return property (Property_Name).at(0); -} - -void CSMSettings::Setting::setPage (const QString &value) -{ - setProperty (Property_Page, value); -} - -QString CSMSettings::Setting::page() const -{ - return property (Property_Page).at(0); -} - -void CSMSettings::Setting::setStyleSheet (const QString &value) -{ - setProperty (Property_StyleSheet, value); -} - -QString CSMSettings::Setting::styleSheet() const -{ - return property (Property_StyleSheet).at(0); -} - -void CSMSettings::Setting::setPrefix (const QString &value) -{ - setProperty (Property_Prefix, value); -} - -QString CSMSettings::Setting::prefix() const -{ - return property (Property_Prefix).at(0); -} - -void CSMSettings::Setting::setRowSpan (const int value) -{ - setProperty (Property_RowSpan, value); -} - -int CSMSettings::Setting::rowSpan () const -{ - return property (Property_RowSpan).at(0).toInt(); -} - -void CSMSettings::Setting::setSingleStep (int value) -{ - setProperty (Property_SingleStep, value); -} - -void CSMSettings::Setting::setSingleStep (double value) -{ - setProperty (Property_SingleStep, value); -} - -QString CSMSettings::Setting::singleStep() const -{ - return property (Property_SingleStep).at(0); -} - -void CSMSettings::Setting::setSuffix (const QString &value) -{ - setProperty (Property_Suffix, value); -} - -QString CSMSettings::Setting::suffix() const -{ - return property (Property_Suffix).at(0); -} - -void CSMSettings::Setting::setTickInterval (int value) -{ - setProperty (Property_TickInterval, value); -} - -int CSMSettings::Setting::tickInterval () const -{ - return property (Property_TickInterval).at(0).toInt(); -} - -void CSMSettings::Setting::setTicksAbove (bool state) -{ - setProperty (Property_TicksAbove, state); -} - -bool CSMSettings::Setting::ticksAbove() const -{ - return (property (Property_TicksAbove).at(0) == "true"); -} - -void CSMSettings::Setting::setTicksBelow (bool state) -{ - setProperty (Property_TicksBelow, state); -} - -bool CSMSettings::Setting::ticksBelow() const -{ - return (property (Property_TicksBelow).at(0) == "true"); -} - -void CSMSettings::Setting::setType (int settingType) -{ - setProperty (Property_SettingType, settingType); -} - -CSMSettings::SettingType CSMSettings::Setting::type() const -{ - return static_cast ( property ( - Property_SettingType).at(0).toInt()); -} - -void CSMSettings::Setting::setRange (int min, int max) -{ - setProperty (Property_Minimum, min); - setProperty (Property_Maximum, max); -} - -void CSMSettings::Setting::setRange (double min, double max) -{ - setProperty (Property_Minimum, min); - setProperty (Property_Maximum, max); -} - -QString CSMSettings::Setting::maximum() const -{ - return property (Property_Maximum).at(0); -} - -QString CSMSettings::Setting::minimum() const -{ - return property (Property_Minimum).at(0); -} - -CSVSettings::ViewType CSMSettings::Setting::viewType() const -{ - return static_cast ( property ( - Property_SettingType).at(0).toInt() / 10); -} - -void CSMSettings::Setting::setViewColumn (int value) -{ - setProperty (Property_ViewColumn, value); -} - -int CSMSettings::Setting::viewColumn() const -{ - return property (Property_ViewColumn).at(0).toInt(); -} - -void CSMSettings::Setting::setViewLocation (int row, int column) -{ - setViewRow (row); - setViewColumn (column); -} - -void CSMSettings::Setting::setViewRow (int value) -{ - setProperty (Property_ViewRow, value); -} - -int CSMSettings::Setting::viewRow() const -{ - return property (Property_ViewRow).at(0).toInt(); -} - -void CSMSettings::Setting::setWidgetWidth (int value) -{ - setProperty (Property_WidgetWidth, value); -} - -int CSMSettings::Setting::widgetWidth() const -{ - return property (Property_WidgetWidth).at(0).toInt(); -} - -void CSMSettings::Setting::setWrapping (bool state) -{ - setProperty (Property_Wrapping, state); -} - -bool CSMSettings::Setting::wrapping() const -{ - return (property (Property_Wrapping).at(0) == "true"); -} - -void CSMSettings::Setting::setLabel (const QString& label) -{ - setProperty (Property_Label, label); -} - -QString CSMSettings::Setting::getLabel() const -{ - return property (Property_Label).at (0); -} - -void CSMSettings::Setting::setToolTip (const QString& toolTip) -{ - setProperty (Property_ToolTip, toolTip); -} - -QString CSMSettings::Setting::getToolTip() const -{ - return property (Property_ToolTip).at (0); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, bool value) -{ - setProperty (prop, QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, int value) -{ - setProperty (prop, QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, double value) -{ - setProperty (prop, QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, - const QString &value) -{ - setProperty (prop, QStringList() << value); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, - const QStringList &value) -{ - if (prop < mProperties.size()) - mProperties.replace (prop, value); -} diff --git a/apps/opencs/model/settings/setting.hpp b/apps/opencs/model/settings/setting.hpp deleted file mode 100644 index be51a531a9..0000000000 --- a/apps/opencs/model/settings/setting.hpp +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef CSMSETTINGS_SETTING_HPP -#define CSMSETTINGS_SETTING_HPP - -#include -#include -#include "support.hpp" - -namespace CSMSettings -{ - //QString is the setting id in the form of "page/name" - //QList is a list of stringlists of proxy values. - //Order is important! Proxy stringlists are matched against - //master values by their position in the QList. - typedef QMap > ProxyValueMap; - - ///Setting class is the interface for the User Settings. It contains - ///a great deal of boiler plate to provide the core API functions, as - ///well as the property() functions which use enumeration to be iterable. - ///This makes the Setting class capable of being manipulated by script. - ///See CSMSettings::support.hpp for enumerations / string values. - class Setting - { - QList mProperties; - QStringList mDefaults; - - bool mIsEditorSetting; - - ProxyValueMap mProxies; - - public: - - Setting(SettingType typ, const QString &settingName, - const QString &pageName, const QString& label = ""); - - void addProxy (const Setting *setting, const QStringList &vals); - void addProxy (const Setting *setting, const QList &list); - - const QList &properties() const { return mProperties; } - const ProxyValueMap &proxies() const { return mProxies; } - - void setColumnSpan (int value); - int columnSpan() const; - - void setDeclaredValues (QStringList list); - QStringList declaredValues() const; - - void setDefaultValue (int value); - void setDefaultValue (double value); - void setDefaultValue (const QString &value); - - void setDefaultValues (const QStringList &values); - QStringList defaultValues() const; - - void setDelimiter (const QString &value); - QString delimiter() const; - - void setEditorSetting (bool state); - bool isEditorSetting() const; - - void setIsMultiLine (bool state); - bool isMultiLine() const; - - void setIsMultiValue (bool state); - bool isMultiValue() const; - - void setMask (const QString &value); - QString mask() const; - - void setRange (int min, int max); - void setRange (double min, double max); - - QString maximum() const; - - QString minimum() const; - - void setName (const QString &value); - QString name() const; - - void setPage (const QString &value); - QString page() const; - - void setStyleSheet (const QString &value); - QString styleSheet() const; - - void setPrefix (const QString &value); - QString prefix() const; - - void setRowSpan (const int value); - int rowSpan() const; - - const ProxyValueMap &proxyLists() const; - - void setSerializable (bool state); - bool serializable() const; - - void setSpecialValueText (const QString &text); - QString specialValueText() const; - - void setSingleStep (int value); - void setSingleStep (double value); - QString singleStep() const; - - void setSuffix (const QString &value); - QString suffix() const; - - void setTickInterval (int value); - int tickInterval() const; - - void setTicksAbove (bool state); - bool ticksAbove() const; - - void setTicksBelow (bool state); - bool ticksBelow() const; - - void setViewColumn (int value); - int viewColumn() const; - - void setViewLocation (int row = -1, int column = -1); - - void setViewRow (int value); - int viewRow() const; - - void setType (int settingType); - CSMSettings::SettingType type() const; - - CSVSettings::ViewType viewType() const; - - void setWrapping (bool state); - bool wrapping() const; - - void setWidgetWidth (int value); - int widgetWidth() const; - - /// This is the text the user gets to see. - void setLabel (const QString& label); - QString getLabel() const; - - void setToolTip (const QString& toolTip); - QString getToolTip() const; - - ///returns the specified property value - QStringList property (SettingProperty prop) const; - - ///boilerplate code to convert setting values of common types - void setProperty (SettingProperty prop, bool value); - void setProperty (SettingProperty prop, int value); - void setProperty (SettingProperty prop, double value); - void setProperty (SettingProperty prop, const QString &value); - void setProperty (SettingProperty prop, const QStringList &value); - - void addProxy (Setting* setting, - QMap &proxyMap); - - protected: - void buildDefaultSetting(); - }; -} - -#endif // CSMSETTINGS_SETTING_HPP diff --git a/apps/opencs/model/settings/support.hpp b/apps/opencs/model/settings/support.hpp deleted file mode 100644 index ab0e5088ce..0000000000 --- a/apps/opencs/model/settings/support.hpp +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef SETTING_SUPPORT_HPP -#define SETTING_SUPPORT_HPP - -#include -#include -#include -#include -#include - -//Enums -namespace CSMSettings -{ - ///Enumerated properties for scripting - enum SettingProperty - { - Property_Name = 0, - Property_Page = 1, - Property_SettingType = 2, - Property_IsMultiValue = 3, - Property_IsMultiLine = 4, - Property_WidgetWidth = 5, - Property_ViewRow = 6, - Property_ViewColumn = 7, - Property_Delimiter = 8, - Property_Serializable = 9, - Property_ColumnSpan = 10, - Property_RowSpan = 11, - Property_Minimum = 12, - Property_Maximum = 13, - Property_SpecialValueText = 14, - Property_Prefix = 15, - Property_Suffix = 16, - Property_SingleStep = 17, - Property_Wrapping = 18, - Property_TickInterval = 19, - Property_TicksAbove = 20, - Property_TicksBelow = 21, - Property_StyleSheet = 22, - Property_Label = 23, - Property_ToolTip = 24, - - //Stringlists should always be the last items - Property_DefaultValues = 25, - Property_DeclaredValues = 26, - Property_DefinedValues = 27, - Property_Proxies = 28 - }; - - ///Basic setting widget types. - enum SettingType - { - /* - * 0 - 9 - Boolean widgets - * 10-19 - List widgets - * 21-29 - Range widgets - * 31-39 - Text widgets - * - * Each range corresponds to a View_Type enum by a factor of 10. - * - * Even-numbered values are single-value widgets - * Odd-numbered values are multi-valued widgets - */ - - Type_CheckBox = 0, - Type_RadioButton = 1, - Type_ListView = 10, - Type_ComboBox = 11, - Type_SpinBox = 21, - Type_DoubleSpinBox = 23, - Type_Slider = 25, - Type_Dial = 27, - Type_TextArea = 30, - Type_LineEdit = 31, - Type_Undefined = 40 - }; - -} - -namespace CSVSettings -{ - ///Categorical view types which encompass the setting widget types - enum ViewType - { - ViewType_Boolean = 0, - ViewType_List = 1, - ViewType_Range = 2, - ViewType_Text = 3, - ViewType_Undefined = 4 - }; -} - - -namespace CSMSettings -{ - ///used to construct default settings in the Setting class - struct PropertyDefaultValues - { - int id; - QString name; - QVariant value; - }; - - ///strings which correspond to setting values. These strings represent - ///the script language keywords which would be used to declare setting - ///views for 3rd party addons - const QString sPropertyNames[] = - { - "name", "page", "setting_type", "is_multi_value", - "is_multi_line", "widget_width", "view_row", "view_column", "delimiter", - "is_serializable","column_span", "row_span", "minimum", "maximum", - "special_value_text", "prefix", "suffix", "single_step", "wrapping", - "tick_interval", "ticks_above", "ticks_below", "stylesheet", - "defaults", "declarations", "definitions", "proxies" - }; - - ///Default values for a setting. Used in setting creation. - const QString sPropertyDefaults[] = - { - "", //name - "", //page - "40", //setting type - "false", //multivalue - "false", //multiline - "7", //widget width - "-1", //view row - "-1", //view column - ",", //delimiter - "true", //serialized - "1", //column span - "1", //row span - "0", //value range - "0", //value minimum - "0", //value maximum - "", //special text - "", //prefix - "", //suffix - "false", //wrapping - "1", //tick interval - "false", //ticks above - "true", //ticks below - "", //StyleSheet - "", //default values - "", //declared values - "", //defined values - "" //proxy values - }; -} - -#endif // VIEW_SUPPORT_HPP diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp deleted file mode 100644 index 3e5ab24d13..0000000000 --- a/apps/opencs/model/settings/usersettings.cpp +++ /dev/null @@ -1,801 +0,0 @@ -#include "usersettings.hpp" - -#include -#include - -#include -#include -#include - -#include "setting.hpp" -#include "support.hpp" -#include -#include - -/** - * Workaround for problems with whitespaces in paths in older versions of Boost library - */ -#if (BOOST_VERSION <= 104600) -namespace boost -{ - - template<> - inline boost::filesystem::path lexical_cast(const std::string& arg) - { - return boost::filesystem::path(arg); - } - -} /* namespace boost */ -#endif /* (BOOST_VERSION <= 104600) */ - -CSMSettings::UserSettings *CSMSettings::UserSettings::sUserSettingsInstance = 0; - - CSMSettings::UserSettings::UserSettings (const Files::ConfigurationManager& configurationManager) - : mCfgMgr (configurationManager) - , mSettingDefinitions(NULL) -{ - assert(!sUserSettingsInstance); - sUserSettingsInstance = this; - - buildSettingModelDefaults(); -} - -void CSMSettings::UserSettings::buildSettingModelDefaults() -{ - /* - declareSection ("3d-render", "3D Rendering"); - { - Setting *farClipDist = createSetting (Type_DoubleSpinBox, "far-clip-distance", "Far clipping distance"); - farClipDist->setDefaultValue (300000); - farClipDist->setRange (0, 1000000); - farClipDist->setToolTip ("The maximum distance objects are still rendered at."); - - QString defaultValue = "None"; - Setting *antialiasing = createSetting (Type_ComboBox, "antialiasing", "Antialiasing"); - antialiasing->setDeclaredValues (QStringList() - << defaultValue << "MSAA 2" << "MSAA 4" << "MSAA 8" << "MSAA 16"); - antialiasing->setDefaultValue (defaultValue); - } - */ - - /* - declareSection ("scene-input", "Scene Input"); - { - Setting *fastFactor = createSetting (Type_SpinBox, "fast-factor", - "Fast movement factor"); - fastFactor->setDefaultValue (4); - fastFactor->setRange (1, 100); - fastFactor->setToolTip ( - "Factor by which movement is speed up while the shift key is held down."); - } - */ - - declareSection ("window", "Window"); - { - Setting *preDefined = createSetting (Type_ComboBox, "pre-defined", - "Default window size"); - preDefined->setEditorSetting (false); - preDefined->setDeclaredValues ( - QStringList() << "640 x 480" << "800 x 600" << "1024 x 768" << "1440 x 900"); - preDefined->setViewLocation (1, 1); - preDefined->setColumnSpan (2); - preDefined->setToolTip ("Newly opened top-level windows will open with this size " - "(picked from a list of pre-defined values)"); - - Setting *width = createSetting (Type_LineEdit, "default-width", - "Default window width"); - width->setDefaultValues (QStringList() << "1024"); - width->setViewLocation (2, 1); - width->setColumnSpan (1); - width->setToolTip ("Newly opened top-level windows will open with this width."); - preDefined->addProxy (width, QStringList() << "640" << "800" << "1024" << "1440"); - - Setting *height = createSetting (Type_LineEdit, "default-height", - "Default window height"); - height->setDefaultValues (QStringList() << "768"); - height->setViewLocation (2, 2); - height->setColumnSpan (1); - height->setToolTip ("Newly opened top-level windows will open with this height."); - preDefined->addProxy (height, QStringList() << "480" << "600" << "768" << "900"); - - Setting *reuse = createSetting (Type_CheckBox, "reuse", "Reuse Subviews"); - reuse->setDefaultValue ("true"); - reuse->setToolTip ("When a new subview is requested and a matching subview already " - " exist, do not open a new subview and use the existing one instead."); - - Setting *statusBar = createSetting (Type_CheckBox, "show-statusbar", "Show Status Bar"); - statusBar->setDefaultValue ("true"); - statusBar->setToolTip ("If a newly open top level window is showing status bars or not. " - " Note that this does not affect existing windows."); - - Setting *maxSubView = createSetting (Type_SpinBox, "max-subviews", - "Maximum number of subviews per top-level window"); - maxSubView->setDefaultValue (256); - maxSubView->setRange (1, 256); - maxSubView->setToolTip ("If the maximum number is reached and a new subview is opened " - "it will be placed into a new top-level window."); - - Setting *hide = createSetting (Type_CheckBox, "hide-subview", "Hide single subview"); - hide->setDefaultValue ("false"); - hide->setToolTip ("When a view contains only a single subview, hide the subview title " - "bar and if this subview is closed also close the view (unless it is the last " - "view for this document)"); - - Setting *minWidth = createSetting (Type_SpinBox, "minimum-width", - "Minimum subview width"); - minWidth->setDefaultValue (325); - minWidth->setRange (50, 10000); - minWidth->setToolTip ("Minimum width of subviews."); - - QString defaultScroll = "Scrollbar Only"; - QStringList scrollValues = QStringList() << defaultScroll << "Grow Only" << "Grow then Scroll"; - - Setting *mainwinScroll = createSetting (Type_RadioButton, "mainwindow-scrollbar", - "Add a horizontal scrollbar to the main view window."); - mainwinScroll->setDefaultValue (defaultScroll); - mainwinScroll->setDeclaredValues (scrollValues); - mainwinScroll->setToolTip ("Scrollbar Only: Simple addition of scrollbars, the view window does not grow" - " automatically.\n" - "Grow Only: Original Editor behaviour. The view window grows as subviews are added. No scrollbars.\n" - "Grow then Scroll: The view window grows. The scrollbar appears once it cannot grow any further."); - - Setting *grow = createSetting (Type_CheckBox, "grow-limit", "Grow Limit Screen"); - grow->setDefaultValue ("false"); - grow->setToolTip ("When \"Grow then Scroll\" option is selected, the window size grows to" - " the width of the virtual desktop. \nIf this option is selected the the window growth" - "is limited to the current screen."); - } - - declareSection ("records", "Records"); - { - QString defaultValue = "Icon and Text"; - QStringList values = QStringList() << defaultValue << "Icon Only" << "Text Only"; - - Setting *rsd = createSetting (Type_RadioButton, "status-format", - "Modification status display format"); - rsd->setDefaultValue (defaultValue); - rsd->setDeclaredValues (values); - - Setting *ritd = createSetting (Type_RadioButton, "type-format", - "ID type display format"); - ritd->setDefaultValue (defaultValue); - ritd->setDeclaredValues (values); - } - - declareSection ("table-input", "ID Tables"); - { - QString inPlaceEdit ("Edit in Place"); - QString editRecord ("Edit Record"); - QString view ("View"); - QString editRecordAndClose ("Edit Record and Close"); - - QStringList values; - values - << "None" << inPlaceEdit << editRecord << view << "Revert" << "Delete" - << editRecordAndClose << "View and Close"; - - QString toolTip = "
    " - "
  • None
  • " - "
  • Edit in Place: Edit the clicked cell
  • " - "
  • Edit Record: Open a dialogue subview for the clicked record
  • " - "
  • View: Open a scene subview for the clicked record (not available everywhere)
  • " - "
  • Revert: Revert record
  • " - "
  • Delete: Delete recordy
  • " - "
  • Edit Record and Close: Open a dialogue subview for the clicked record and close the table subview
  • " - "
  • View And Close: Open a scene subview for the clicked record and close the table subview
  • " - "
"; - - Setting *doubleClick = createSetting (Type_ComboBox, "double", "Double Click"); - doubleClick->setDeclaredValues (values); - doubleClick->setDefaultValue (inPlaceEdit); - doubleClick->setToolTip ("Action on double click in table:

" + toolTip); - - Setting *shiftDoubleClick = createSetting (Type_ComboBox, "double-s", - "Shift Double Click"); - shiftDoubleClick->setDeclaredValues (values); - shiftDoubleClick->setDefaultValue (editRecord); - shiftDoubleClick->setToolTip ("Action on shift double click in table:

" + toolTip); - - Setting *ctrlDoubleClick = createSetting (Type_ComboBox, "double-c", - "Control Double Click"); - ctrlDoubleClick->setDeclaredValues (values); - ctrlDoubleClick->setDefaultValue (view); - ctrlDoubleClick->setToolTip ("Action on control double click in table:

" + toolTip); - - Setting *shiftCtrlDoubleClick = createSetting (Type_ComboBox, "double-sc", - "Shift Control Double Click"); - shiftCtrlDoubleClick->setDeclaredValues (values); - shiftCtrlDoubleClick->setDefaultValue (editRecordAndClose); - shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in table:

" + toolTip); - - QString defaultValue = "Jump and Select"; - QStringList jumpValues = QStringList() << defaultValue << "Jump Only" << "No Jump"; - - Setting *jumpToAdded = createSetting (Type_RadioButton, "jump-to-added", - "Jump to the added or cloned record."); - jumpToAdded->setDefaultValue (defaultValue); - jumpToAdded->setDeclaredValues (jumpValues); - - Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config", - "Manually specify affected record types for an extended delete/revert"); - extendedConfig->setDefaultValue("false"); - extendedConfig->setToolTip("Delete and revert commands have an extended form that also affects " - "associated records.\n\n" - "If this option is enabled, types of affected records are selected " - "manually before a command execution.\nOtherwise, all associated " - "records are deleted/reverted immediately."); - } - - declareSection ("dialogues", "ID Dialogues"); - { - Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); - toolbar->setDefaultValue ("true"); - } - - declareSection ("report-input", "Reports"); - { - QString none ("None"); - QString edit ("Edit"); - QString remove ("Remove"); - QString editAndRemove ("Edit And Remove"); - - QStringList values; - values << none << edit << remove << editAndRemove; - - QString toolTip = "

    " - "
  • None
  • " - "
  • Edit: Open a table or dialogue suitable for addressing the listed report
  • " - "
  • Remove: Remove the report from the report table
  • " - "
  • Edit and Remove: Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table
  • " - "
"; - - Setting *doubleClick = createSetting (Type_ComboBox, "double", "Double Click"); - doubleClick->setDeclaredValues (values); - doubleClick->setDefaultValue (edit); - doubleClick->setToolTip ("Action on double click in report table:

" + toolTip); - - Setting *shiftDoubleClick = createSetting (Type_ComboBox, "double-s", - "Shift Double Click"); - shiftDoubleClick->setDeclaredValues (values); - shiftDoubleClick->setDefaultValue (remove); - shiftDoubleClick->setToolTip ("Action on shift double click in report table:

" + toolTip); - - Setting *ctrlDoubleClick = createSetting (Type_ComboBox, "double-c", - "Control Double Click"); - ctrlDoubleClick->setDeclaredValues (values); - ctrlDoubleClick->setDefaultValue (editAndRemove); - ctrlDoubleClick->setToolTip ("Action on control double click in report table:

" + toolTip); - - Setting *shiftCtrlDoubleClick = createSetting (Type_ComboBox, "double-sc", - "Shift Control Double Click"); - shiftCtrlDoubleClick->setDeclaredValues (values); - shiftCtrlDoubleClick->setDefaultValue (none); - shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:

" + toolTip); - } - - declareSection ("search", "Search & Replace"); - { - Setting *before = createSetting (Type_SpinBox, "char-before", - "Characters before search string"); - before->setDefaultValue (10); - before->setRange (0, 1000); - before->setToolTip ("Maximum number of character to display in search result before the searched text"); - - Setting *after = createSetting (Type_SpinBox, "char-after", - "Characters after search string"); - after->setDefaultValue (10); - after->setRange (0, 1000); - after->setToolTip ("Maximum number of character to display in search result after the searched text"); - - Setting *autoDelete = createSetting (Type_CheckBox, "auto-delete", "Delete row from result table after a successful replace"); - autoDelete->setDefaultValue ("true"); - } - - declareSection ("script-editor", "Scripts"); - { - Setting *lineNum = createSetting (Type_CheckBox, "show-linenum", "Show Line Numbers"); - lineNum->setDefaultValue ("true"); - lineNum->setToolTip ("Show line numbers to the left of the script editor window." - "The current row and column numbers of the text cursor are shown at the bottom."); - - Setting *monoFont = createSetting (Type_CheckBox, "mono-font", "Use monospace font"); - monoFont->setDefaultValue ("true"); - monoFont->setToolTip ("Whether to use monospaced fonts on script edit subview."); - - QString tooltip = - "\n#RGB (each of R, G, and B is a single hex digit)" - "\n#RRGGBB" - "\n#RRRGGGBBB" - "\n#RRRRGGGGBBBB" - "\nA name from the list of colors defined in the list of SVG color keyword names." - "\nX11 color names may also work."; - - QString modeNormal ("Normal"); - - QStringList modes; - modes << "Ignore" << modeNormal << "Strict"; - - Setting *warnings = createSetting (Type_ComboBox, "warnings", - "Warning Mode"); - warnings->setDeclaredValues (modes); - warnings->setDefaultValue (modeNormal); - warnings->setToolTip ("

    How to handle warning messages during compilation:

    " - "

  • Ignore: Do not report warning
  • " - "
  • Normal: Report warning as a warning
  • " - "
  • Strict: Promote warning to an error
  • " - "
"); - - Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); - toolbar->setDefaultValue ("true"); - - Setting *delay = createSetting (Type_SpinBox, "compile-delay", - "Delay between updating of source errors"); - delay->setDefaultValue (100); - delay->setRange (0, 10000); - delay->setToolTip ("Delay in milliseconds"); - - Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); - formatInt->setDefaultValues (QStringList() << "Dark magenta"); - formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); - - Setting *formatFloat = createSetting (Type_LineEdit, "colour-float", "Highlight Colour: Float"); - formatFloat->setDefaultValues (QStringList() << "Magenta"); - formatFloat->setToolTip ("(Default: Magenta) Use one of the following formats:" + tooltip); - - Setting *formatName = createSetting (Type_LineEdit, "colour-name", "Highlight Colour: Name"); - formatName->setDefaultValues (QStringList() << "Gray"); - formatName->setToolTip ("(Default: Gray) Use one of the following formats:" + tooltip); - - Setting *formatKeyword = createSetting (Type_LineEdit, "colour-keyword", "Highlight Colour: Keyword"); - formatKeyword->setDefaultValues (QStringList() << "Red"); - formatKeyword->setToolTip ("(Default: Red) Use one of the following formats:" + tooltip); - - Setting *formatSpecial = createSetting (Type_LineEdit, "colour-special", "Highlight Colour: Special"); - formatSpecial->setDefaultValues (QStringList() << "Dark yellow"); - formatSpecial->setToolTip ("(Default: Dark yellow) Use one of the following formats:" + tooltip); - - Setting *formatComment = createSetting (Type_LineEdit, "colour-comment", "Highlight Colour: Comment"); - formatComment->setDefaultValues (QStringList() << "Green"); - formatComment->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); - - Setting *formatId = createSetting (Type_LineEdit, "colour-id", "Highlight Colour: Id"); - formatId->setDefaultValues (QStringList() << "Blue"); - formatId->setToolTip ("(Default: Blue) Use one of the following formats:" + tooltip); - } - - declareSection ("general-input", "General Input"); - { - Setting *cycle = createSetting (Type_CheckBox, "cycle", "Cyclic next/previous"); - cycle->setDefaultValue ("false"); - cycle->setToolTip ("When using next/previous functions at the last/first item of a " - "list go to the first/last item"); - } - - declareSection ("scene-input", "3D Scene Input"); - { - QString left ("Left Mouse-Button"); - QString cLeft ("Ctrl-Left Mouse-Button"); - QString right ("Right Mouse-Button"); - QString cRight ("Ctrl-Right Mouse-Button"); - QString middle ("Middle Mouse-Button"); - QString cMiddle ("Ctrl-Middle Mouse-Button"); - - QStringList values; - values << left << cLeft << right << cRight << middle << cMiddle; - - Setting *primaryNavigation = createSetting (Type_ComboBox, "p-navi", "Primary Camera Navigation Button"); - primaryNavigation->setDeclaredValues (values); - primaryNavigation->setDefaultValue (left); - - Setting *secondaryNavigation = createSetting (Type_ComboBox, "s-navi", "Secondary Camera Navigation Button"); - secondaryNavigation->setDeclaredValues (values); - secondaryNavigation->setDefaultValue (cLeft); - - Setting *primaryEditing = createSetting (Type_ComboBox, "p-edit", "Primary Editing Button"); - primaryEditing->setDeclaredValues (values); - primaryEditing->setDefaultValue (right); - - Setting *secondaryEditing = createSetting (Type_ComboBox, "s-edit", "Secondary Editing Button"); - secondaryEditing->setDeclaredValues (values); - secondaryEditing->setDefaultValue (cRight); - - Setting *selection = createSetting (Type_ComboBox, "select", "Selection Button"); - selection->setDeclaredValues (values); - selection->setDefaultValue (middle); - - Setting *contextSensitive = createSetting (Type_CheckBox, "context-select", "Context Sensitive Selection"); - contextSensitive->setDefaultValue ("false"); - - Setting *dragMouseSensitivity = createSetting (Type_DoubleSpinBox, "drag-factor", - "Mouse sensitivity during drag operations"); - dragMouseSensitivity->setDefaultValue (1.0); - dragMouseSensitivity->setRange (0.001, 100.0); - - Setting *dragWheelSensitivity = createSetting (Type_DoubleSpinBox, "drag-wheel-factor", - "Mouse wheel sensitivity during drag operations"); - dragWheelSensitivity->setDefaultValue (1.0); - dragWheelSensitivity->setRange (0.001, 100.0); - - Setting *dragShiftFactor = createSetting (Type_DoubleSpinBox, "drag-shift-factor", - "Acceleration factor during drag operations while holding down shift"); - dragShiftFactor->setDefaultValue (4.0); - dragShiftFactor->setRange (0.001, 100.0); - } - - { - /****************************************************************** - * There are three types of values: - * - * Declared values - * - * Pre-determined values, typically for - * combobox drop downs and boolean (radiobutton / checkbox) labels. - * These values represent the total possible list of values that - * may define a setting. No other values are allowed. - * - * Defined values - * - * Values which represent the actual, current value of - * a setting. For settings with declared values, this must be one - * or several declared values, as appropriate. - * - * Proxy values - * Values the proxy master updates the proxy slave when - * it's own definition is set / changed. These are definitions for - * proxy slave settings, but must match any declared values the - * proxy slave has, if any. - *******************************************************************/ -/* - //create setting objects, specifying the basic widget type, - //the page name, and the view name - - Setting *masterBoolean = createSetting (Type_RadioButton, section, - "Master Proxy"); - - Setting *slaveBoolean = createSetting (Type_CheckBox, section, - "Proxy Checkboxes"); - - Setting *slaveSingleText = createSetting (Type_LineEdit, section, - "Proxy TextBox 1"); - - Setting *slaveMultiText = createSetting (Type_LineEdit, section, - "ProxyTextBox 2"); - - Setting *slaveAlphaSpinbox = createSetting (Type_SpinBox, section, - "Alpha Spinbox"); - - Setting *slaveIntegerSpinbox = createSetting (Type_SpinBox, section, - "Int Spinbox"); - - Setting *slaveDoubleSpinbox = createSetting (Type_DoubleSpinBox, - section, "Double Spinbox"); - - Setting *slaveSlider = createSetting (Type_Slider, section, "Slider"); - - Setting *slaveDial = createSetting (Type_Dial, section, "Dial"); - - //set declared values for selected views - masterBoolean->setDeclaredValues (QStringList() - << "Profile One" << "Profile Two" - << "Profile Three" << "Profile Four"); - - slaveBoolean->setDeclaredValues (QStringList() - << "One" << "Two" << "Three" << "Four" << "Five"); - - slaveAlphaSpinbox->setDeclaredValues (QStringList() - << "One" << "Two" << "Three" << "Four"); - - - masterBoolean->addProxy (slaveBoolean, QList () - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three" << "Five") - << (QStringList() << "Two" << "Four") - ); - - masterBoolean->addProxy (slaveSingleText, QList () - << (QStringList() << "Text A") - << (QStringList() << "Text B") - << (QStringList() << "Text A") - << (QStringList() << "Text C") - ); - - masterBoolean->addProxy (slaveMultiText, QList () - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three" << "Five") - << (QStringList() << "Two" << "Four") - ); - - masterBoolean->addProxy (slaveAlphaSpinbox, QList () - << (QStringList() << "Four") - << (QStringList() << "Three") - << (QStringList() << "Two") - << (QStringList() << "One")); - - masterBoolean->addProxy (slaveIntegerSpinbox, QList () - << (QStringList() << "0") - << (QStringList() << "7") - << (QStringList() << "14") - << (QStringList() << "21")); - - masterBoolean->addProxy (slaveDoubleSpinbox, QList () - << (QStringList() << "0.17") - << (QStringList() << "0.34") - << (QStringList() << "0.51") - << (QStringList() << "0.68")); - - masterBoolean->addProxy (slaveSlider, QList () - << (QStringList() << "25") - << (QStringList() << "50") - << (QStringList() << "75") - << (QStringList() << "100") - ); - - masterBoolean->addProxy (slaveDial, QList () - << (QStringList() << "25") - << (QStringList() << "50") - << (QStringList() << "75") - << (QStringList() << "100") - ); - - //settings with proxies are not serialized by default - //other settings non-serialized for demo purposes - slaveBoolean->setSerializable (false); - slaveSingleText->setSerializable (false); - slaveMultiText->setSerializable (false); - slaveAlphaSpinbox->setSerializable (false); - slaveIntegerSpinbox->setSerializable (false); - slaveDoubleSpinbox->setSerializable (false); - slaveSlider->setSerializable (false); - slaveDial->setSerializable (false); - - slaveBoolean->setDefaultValues (QStringList() - << "One" << "Three" << "Five"); - - slaveSingleText->setDefaultValue ("Text A"); - - slaveMultiText->setDefaultValues (QStringList() - << "One" << "Three" << "Five"); - - slaveSingleText->setWidgetWidth (24); - slaveMultiText->setWidgetWidth (24); - - slaveAlphaSpinbox->setDefaultValue ("Two"); - slaveAlphaSpinbox->setWidgetWidth (20); - //slaveAlphaSpinbox->setPrefix ("No. "); - //slaveAlphaSpinbox->setSuffix ("!"); - slaveAlphaSpinbox->setWrapping (true); - - slaveIntegerSpinbox->setDefaultValue (14); - slaveIntegerSpinbox->setMinimum (0); - slaveIntegerSpinbox->setMaximum (58); - slaveIntegerSpinbox->setPrefix ("$"); - slaveIntegerSpinbox->setSuffix (".00"); - slaveIntegerSpinbox->setWidgetWidth (10); - slaveIntegerSpinbox->setSpecialValueText ("Nothing!"); - - slaveDoubleSpinbox->setDefaultValue (0.51); - slaveDoubleSpinbox->setSingleStep(0.17); - slaveDoubleSpinbox->setMaximum(4.0); - - slaveSlider->setMinimum (0); - slaveSlider->setMaximum (100); - slaveSlider->setDefaultValue (75); - slaveSlider->setWidgetWidth (100); - slaveSlider->setTicksAbove (true); - slaveSlider->setTickInterval (25); - - slaveDial->setMinimum (0); - slaveDial->setMaximum (100); - slaveDial->setSingleStep (5); - slaveDial->setDefaultValue (75); - slaveDial->setTickInterval (25); -*/ - } -} - -CSMSettings::UserSettings::~UserSettings() -{ - sUserSettingsInstance = 0; -} - -void CSMSettings::UserSettings::loadSettings (const QString &fileName) -{ - QString userFilePath = QString::fromUtf8 - (mCfgMgr.getUserConfigPath().string().c_str()); - - QString globalFilePath = QString::fromUtf8 - (mCfgMgr.getGlobalPath().string().c_str()); - - QString otherFilePath = globalFilePath; - - //test for local only if global fails (uninstalled copy) - if (!QFile (globalFilePath + fileName).exists()) - { - //if global is invalid, use the local path - otherFilePath = QString::fromUtf8 - (mCfgMgr.getLocalPath().string().c_str()); - } - - QSettings::setPath - (QSettings::IniFormat, QSettings::UserScope, userFilePath); - - QSettings::setPath - (QSettings::IniFormat, QSettings::SystemScope, otherFilePath); - - mSettingDefinitions = new QSettings - (QSettings::IniFormat, QSettings::UserScope, "opencs", QString(), this); -} - -// if the key is not found create one with a default value -QString CSMSettings::UserSettings::setting(const QString &viewKey, const QString &value) -{ - if(mSettingDefinitions->contains(viewKey)) - return settingValue(viewKey); - else if(value != QString()) - { - mSettingDefinitions->setValue (viewKey, QStringList() << value); - return value; - } - - return QString(); -} - -bool CSMSettings::UserSettings::hasSettingDefinitions (const QString &viewKey) const -{ - return (mSettingDefinitions->contains (viewKey)); -} - -void CSMSettings::UserSettings::setDefinitions - (const QString &key, const QStringList &list) -{ - mSettingDefinitions->setValue (key, list); -} - -void CSMSettings::UserSettings::saveDefinitions() const -{ - mSettingDefinitions->sync(); -} - -QString CSMSettings::UserSettings::settingValue (const QString &settingKey) -{ - QStringList defs; - - if (!mSettingDefinitions->contains (settingKey)) - return QString(); - - defs = mSettingDefinitions->value (settingKey).toStringList(); - - if (defs.isEmpty()) - return QString(); - - return defs.at(0); -} - -CSMSettings::UserSettings& CSMSettings::UserSettings::instance() -{ - assert(sUserSettingsInstance); - return *sUserSettingsInstance; -} - -void CSMSettings::UserSettings::updateUserSetting(const QString &settingKey, - const QStringList &list) -{ - mSettingDefinitions->setValue (settingKey ,list); - - emit userSettingUpdated (settingKey, list); -} - -CSMSettings::Setting *CSMSettings::UserSettings::findSetting - (const QString &pageName, const QString &settingName) -{ - foreach (Setting *setting, mSettings) - { - if (setting->name() == settingName) - { - if (setting->page() == pageName) - return setting; - } - } - return 0; -} - -void CSMSettings::UserSettings::removeSetting - (const QString &pageName, const QString &settingName) -{ - if (mSettings.isEmpty()) - return; - - QList ::iterator removeIterator = mSettings.begin(); - - while (removeIterator != mSettings.end()) - { - if ((*removeIterator)->name() == settingName) - { - if ((*removeIterator)->page() == pageName) - { - mSettings.erase (removeIterator); - break; - } - } - removeIterator++; - } -} - -CSMSettings::SettingPageMap CSMSettings::UserSettings::settingPageMap() const -{ - SettingPageMap pageMap; - - foreach (Setting *setting, mSettings) - { - SettingPageMap::iterator iter = pageMap.find (setting->page()); - - if (iter==pageMap.end()) - { - QPair > value; - - std::map::const_iterator iter2 = - mSectionLabels.find (setting->page()); - - value.first = iter2!=mSectionLabels.end() ? iter2->second : ""; - - iter = pageMap.insert (setting->page(), value); - } - - iter->second.append (setting); - } - - return pageMap; -} - -CSMSettings::Setting *CSMSettings::UserSettings::createSetting - (CSMSettings::SettingType type, const QString &name, const QString& label) -{ - Setting *setting = new Setting (type, name, mSection, label); - - // set useful defaults - int row = 1; - - if (!mSettings.empty()) - row = mSettings.back()->viewRow()+1; - - setting->setViewLocation (row, 1); - - setting->setColumnSpan (3); - - int width = 10; - - if (type==Type_CheckBox) - width = 40; - - setting->setWidgetWidth (width); - - if (type==Type_CheckBox) - setting->setStyleSheet ("QGroupBox { border: 0px; }"); - - if (type==Type_CheckBox) - setting->setDeclaredValues(QStringList() << "true" << "false"); - - if (type==Type_CheckBox) - setting->setSpecialValueText (setting->getLabel()); - - //add declaration to the model - mSettings.append (setting); - - return setting; -} - -void CSMSettings::UserSettings::declareSection (const QString& page, const QString& label) -{ - mSection = page; - mSectionLabels[page] = label; -} - -QStringList CSMSettings::UserSettings::definitions (const QString &viewKey) const -{ - if (mSettingDefinitions->contains (viewKey)) - return mSettingDefinitions->value (viewKey).toStringList(); - - return QStringList(); -} diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp deleted file mode 100644 index 5188a98429..0000000000 --- a/apps/opencs/model/settings/usersettings.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef USERSETTINGS_HPP -#define USERSETTINGS_HPP - -#include - -#include -#include -#include -#include -#include - -#include -#include "support.hpp" - -#ifndef Q_MOC_RUN -#include -#endif - -namespace Files { typedef std::vector PathContainer; - struct ConfigurationManager;} - -class QFile; -class QSettings; - -namespace CSMSettings { - - class Setting; - typedef QMap > > SettingPageMap; - - class UserSettings: public QObject - { - - Q_OBJECT - - static UserSettings *sUserSettingsInstance; - const Files::ConfigurationManager& mCfgMgr; - - QSettings *mSettingDefinitions; - QList mSettings; - QString mSection; - std::map mSectionLabels; - - public: - - /// Singleton implementation - static UserSettings& instance(); - - UserSettings (const Files::ConfigurationManager& configurationManager); - ~UserSettings(); - - UserSettings (UserSettings const &); //not implemented - UserSettings& operator= (UserSettings const &); //not implemented - - /// Retrieves the settings file at all three levels (global, local and user). - void loadSettings (const QString &fileName); - - /// Updates QSettings and syncs with the ini file - void setDefinitions (const QString &key, const QStringList &defs); - - QString settingValue (const QString &settingKey); - - ///retrieve a setting object from a given page and setting name - Setting *findSetting - (const QString &pageName, const QString &settingName = QString()); - - ///remove a setting from the list - void removeSetting - (const QString &pageName, const QString &settingName); - - ///Retrieve a map of the settings, keyed by page name - SettingPageMap settingPageMap() const; - - ///Returns a string list of defined vlaues for the specified setting - ///in "page/name" format. - QStringList definitions (const QString &viewKey) const; - - ///Test to indicate whether or not a setting has any definitions - bool hasSettingDefinitions (const QString &viewKey) const; - - ///Save any unsaved changes in the QSettings object - void saveDefinitions() const; - - QString setting(const QString &viewKey, const QString &value = QString()); - - private: - - void buildSettingModelDefaults(); - - ///add a new setting to the model and return it - Setting *createSetting (CSMSettings::SettingType type, const QString &name, - const QString& label); - - /// Set the section for createSetting calls. - /// - /// Sections can be declared multiple times. - void declareSection (const QString& page, const QString& label); - - signals: - - void userSettingUpdated (const QString &, const QStringList &); - - public slots: - - void updateUserSetting (const QString &, const QStringList &); - }; -} -#endif // USERSETTINGS_HPP diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 6b323547f0..ce78e52b22 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -397,6 +397,9 @@ void CSMTools::ReferenceableCheckStage::containerCheck( //checking for name if (container.mName.empty()) messages.push_back (std::make_pair (id, container.mId + " has an empty name")); + + //checking contained items + inventoryListCheck(container.mInventory.mList, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(container, messages, id.toString()); @@ -468,6 +471,12 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures messages.push_back (std::make_pair (id, creature.mId + " has negative gold ")); + if (creature.mScale == 0) + messages.push_back (std::make_pair (id, creature.mId + " has zero scale value")); + + // Check inventory + inventoryListCheck(creature.mInventory.mList, messages, id.toString()); + // Check that mentioned scripts exist scriptCheck(creature, messages, id.toString()); } @@ -739,6 +748,9 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( //TODO: reputation, Disposition, rank, everything else + // Check inventory + inventoryListCheck(npc.mInventory.mList, messages, id.toString()); + // Check that mentioned scripts exist scriptCheck(npc, messages, id.toString()); } @@ -888,6 +900,45 @@ void CSMTools::ReferenceableCheckStage::finalCheck (CSMDoc::Messages& messages) "There is no player record")); } +void CSMTools::ReferenceableCheckStage::inventoryListCheck( + const std::vector& itemList, + CSMDoc::Messages& messages, + const std::string& id) +{ + for (size_t i = 0; i < itemList.size(); ++i) + { + std::string itemName = itemList[i].mItem.toString(); + CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(itemName); + + if (localIndex.first == -1) + messages.push_back (std::make_pair (id, + id + " contains non-existing item (" + itemName + ")")); + else + { + // Needs to accomodate Containers, Creatures, and NPCs + switch (localIndex.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + default: + messages.push_back (std::make_pair(id, + id + " contains item of invalid type (" + itemName + ")")); + } + } + } +} //Templates begins here diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp index a34f3a7891..4356e50b22 100644 --- a/apps/opencs/model/tools/referenceablecheck.hpp +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -47,7 +47,9 @@ namespace CSMTools //FINAL CHECK void finalCheck (CSMDoc::Messages& messages); - //TEMPLATE CHECKS + //Convenience functions + void inventoryListCheck(const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id); + template void inventoryItemCheck(const ITEM& someItem, CSMDoc::Messages& messages, const std::string& someID, diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index d7c41cfcf3..268aea3798 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -10,6 +10,8 @@ #include "../world/data.hpp" +#include "../prefs/state.hpp" + CSMDoc::Message::Severity CSMTools::ScriptCheckStage::getSeverity (Type type) { switch (type) @@ -46,7 +48,7 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) std::ostringstream stream; stream << "script " << mFile << ": " << message; - + mMessages->add (id, stream.str(), "", getSeverity (type)); } @@ -62,6 +64,15 @@ CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) int CSMTools::ScriptCheckStage::setup() { + std::string warnings = CSMPrefs::get()["Scripts"]["warnings"].toString(); + + if (warnings=="Ignore") + mWarningMode = Mode_Ignore; + else if (warnings=="Normal") + mWarningMode = Mode_Normal; + else if (warnings=="Strict") + mWarningMode = Mode_Strict; + mContext.clear(); mMessages = 0; mId.clear(); @@ -110,22 +121,9 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) std::ostringstream stream; stream << "script " << mFile << ": " << error.what(); - + messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } mMessages = 0; } - -void CSMTools::ScriptCheckStage::updateUserSetting (const QString& name, const QStringList& value) -{ - if (name=="script-editor/warnings" && !value.isEmpty()) - { - if (value.at (0)=="Ignore") - mWarningMode = Mode_Ignore; - else if (value.at (0)=="Normal") - mWarningMode = Mode_Normal; - else if (value.at (0)=="Strict") - mWarningMode = Mode_Strict; - } -} diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index 3f02766526..f58215800f 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -34,7 +34,7 @@ namespace CSMTools WarningMode mWarningMode; CSMDoc::Message::Severity getSeverity (Type type); - + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); ///< Report error to the user. @@ -50,8 +50,6 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. - - virtual void updateUserSetting (const QString& name, const QStringList& value); }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index fdbf406f1c..608ed9922f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -53,11 +53,6 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() { mVerifierOperation = new CSMDoc::Operation (CSMDoc::State_Verifying, false); - std::vector settings; - settings.push_back ("script-editor/warnings"); - - mVerifierOperation->configureSettings (settings); - connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 91becdb74e..93f3c500d7 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -2,18 +2,15 @@ #include -void CSMWorld::Cell::load (ESM::ESMReader &esm) +void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted) { - mName = mId; + ESM::Cell::load (esm, isDeleted, false); - ESM::Cell::load (esm, false); - - if (!(mData.mFlags & Interior)) + mId = mName; + if (isExterior()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index f393e2cf97..160610874c 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -16,7 +16,7 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index b0571bbed1..16f5ce51f4 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -43,6 +43,12 @@ namespace CSMWorld template > class Collection : public CollectionBase { + public: + + typedef ESXRecordT ESXRecord; + + private: + std::vector > mRecords; std::map mIndex; std::vector *> mColumns; diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 39232d4423..1f16c9695b 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -86,6 +86,10 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_InfoCondVar, Display_InfoCondComp, + Display_EffectSkill, + Display_EffectAttribute, + Display_IngredEffectId, + Display_None }; diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index e2871d4d85..c75a3c2a12 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -14,6 +14,13 @@ namespace CSMWorld { struct ColumnBase { + enum TableEditModes + { + TableEdit_None, // no editing + TableEdit_Full, // edit cells and add/remove rows + TableEdit_FixedRows // edit cells only + }; + enum Roles { Role_Flags = Qt::UserRole, @@ -124,6 +131,10 @@ namespace CSMWorld Display_String32, Display_LongString256, + Display_EffectSkill, // must display at least one, unlike Display_Skill + Display_EffectAttribute, // must display at least one, unlike Display_Attribute + Display_IngredEffectId, // display none allowed, unlike Display_EffectId + //top level columns that nest other columns Display_NestedHeader }; @@ -186,8 +197,8 @@ namespace CSMWorld template struct NestedParentColumn : public Column { - NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue) : Column (id, - ColumnBase::Display_NestedHeader, flags) + NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) + : Column (id, ColumnBase::Display_NestedHeader, flags), mFixedRows(fixedRows) {} virtual void set (Record& record, const QVariant& data) @@ -198,13 +209,20 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return true; // required by IdTree::hasChildren() + // by default editable; also see IdTree::hasChildren() + if (mFixedRows) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + else + return QVariant::fromValue(ColumnBase::TableEdit_Full); } virtual bool isEditable() const { return true; } + + private: + bool mFixedRows; }; struct NestedChildColumn : public NestableColumn @@ -219,4 +237,6 @@ namespace CSMWorld }; } +Q_DECLARE_METATYPE(CSMWorld::ColumnBase::TableEditModes) + #endif diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 0b1af0e840..a1fc980eb5 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -93,7 +93,7 @@ void CSMWorld::CommandDispatcher::setEditLock (bool locked) void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; - std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (mSelection.begin(), mSelection.end()); } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 8acdac84fd..1f98b24757 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -136,7 +136,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); // Race attributes - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( @@ -147,7 +148,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( @@ -213,9 +215,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mSpells.getNestableColumn(index)->addColumn( @@ -329,9 +331,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mEnchantments.getNestableColumn(index)->addColumn( @@ -1008,41 +1010,43 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) case ESM::REC_DIAL: { - std::string id = mReader->getHNOString ("NAME"); - ESM::Dialogue record; - record.mId = id; - record.load (*mReader); + bool isDeleted = false; - if (record.mType==ESM::Dialogue::Journal) - { - mJournals.load (record, mBase); - mDialogue = &mJournals.getRecord (id).get(); - } - else if (record.mType==ESM::Dialogue::Deleted) + record.load (*mReader, isDeleted); + + if (isDeleted) { - mDialogue = 0; // record vector can be shuffled around which would make pointer - // to record invalid + // record vector can be shuffled around which would make pointer to record invalid + mDialogue = 0; - if (mJournals.tryDelete (id)) + if (mJournals.tryDelete (record.mId)) { - /// \todo handle info records + mJournalInfos.removeDialogueInfos(record.mId); } - else if (mTopics.tryDelete (id)) + else if (mTopics.tryDelete (record.mId)) { - /// \todo handle info records + mTopicInfos.removeDialogueInfos(record.mId); } else { messages.add (UniversalId::Type_None, - "Trying to delete dialogue record " + id + " which does not exist", + "Trying to delete dialogue record " + record.mId + " which does not exist", "", CSMDoc::Message::Severity_Warning); } } else { - mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (id).get(); + if (record.mType == ESM::Dialogue::Journal) + { + mJournals.load (record, mBase); + mDialogue = &mJournals.getRecord (record.mId).get(); + } + else + { + mTopics.load (record, mBase); + mDialogue = &mTopics.getRecord (record.mId).get(); + } } break; diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 4eafc59bd5..ea6eefb882 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -11,7 +11,7 @@ namespace CSMWorld template > class IdCollection : public Collection { - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -33,77 +33,46 @@ namespace CSMWorld template void IdCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader); + record.load (reader, isDeleted); } template int IdCollection::load (ESM::ESMReader& reader, bool base) { - std::string id = reader.getHNOString ("NAME"); + ESXRecordT record; + bool isDeleted = false; - if (reader.isNextSub ("DELE")) - { - int index = Collection::searchId (id); + loadRecord (record, reader, isDeleted); - reader.skipRecord(); + std::string id = IdAccessorT().getId (record); + int index = this->searchId (id); + if (isDeleted) + { if (index==-1) { // deleting a record that does not exist - // ignore it for now - /// \todo report the problem to the user - } - else if (base) - { - Collection::removeRows (index, 1); - } - else - { - Record record = Collection::getRecord (index); - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + return -1; } - return -1; - } - else - { - ESXRecordT record; - - // Sometimes id (i.e. NAME of the cell) may be different to the id we stored - // earlier. e.g. NAME == "Vivec, Arena" but id == "#-4 11". Sometime NAME is - // missing altogether for scripts or cells. - // - // In such cases the returned index will be -1. We then try updating the - // IdAccessor's id manually (e.g. set mId of the record to "Vivec, Arena") - // and try getting the index once more after loading the record. The mId of the - // record would have changed to "#-4 11" after the load, and searchId() should find - // it (if this is a modify) - int index = this->searchId (id); - - if (index==-1) - IdAccessorT().getId (record) = id; - else - { - record = this->getRecord (index).get(); - } - - loadRecord (record, reader); - - if (index==-1) + if (base) { - std::string newId = IdAccessorT().getId(record); - int newIndex = this->searchId(newId); - if (newIndex != -1 && id != newId) - index = newIndex; + this->removeRows (index, 1); + return -1; } - return load (record, base, index); + Record baseRecord = this->getRecord (index); + baseRecord.mState = RecordBase::State_Deleted; + this->setRecord (index, baseRecord); + return index; } + + return load (record, base, index); } template diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 60c6130416..f5ec4d4587 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -106,21 +106,20 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector erasedRecords; + + std::map::const_iterator current = getIdMap().lower_bound(id); + std::map::const_iterator end = getIdMap().end(); + for (; current != end; ++current) + { + Record record = getRecord(current->second); + + if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) + { + if (record.mState == RecordBase::State_ModifiedOnly) + { + erasedRecords.push_back(current->second); + } + else + { + record.mState = RecordBase::State_Deleted; + setRecord(current->second, record); + } + } + else + { + break; + } + } + + while (!erasedRecords.empty()) + { + removeRows(erasedRecords.back(), 1); + erasedRecords.pop_back(); + } +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 6db47373d0..e5a5575c78 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -44,6 +44,8 @@ namespace CSMWorld Range getTopicRange (const std::string& topic) const; ///< Return iterators that point to the beginning and past the end of the range for /// the given topic. + + void removeDialogueInfos(const std::string& dialogueId); }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 222f9bc023..80f86c7467 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -4,13 +4,12 @@ namespace CSMWorld { - void Land::load(ESM::ESMReader &esm) + void Land::load(ESM::ESMReader &esm, bool &isDeleted) { - ESM::Land::load(esm); + ESM::Land::load(esm, isDeleted); std::ostringstream stream; stream << "#" << mX << " " << mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index 22cedb56db..e5f25c1d3d 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -10,13 +10,12 @@ namespace CSMWorld /// \brief Wrapper for Land record. Encodes X and Y cell index in the ID. /// /// \todo Add worldspace support to the Land record. - /// \todo Add a proper copy constructor (currently worked around using shared_ptr) struct Land : public ESM::Land { std::string mId; /// Loads the metadata and ID - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index e7772129cd..266377d0f6 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -4,10 +4,9 @@ namespace CSMWorld { - - void LandTexture::load(ESM::ESMReader &esm) + void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) { - ESM::LandTexture::load(esm); + ESM::LandTexture::load(esm, isDeleted); mPluginIndex = esm.getIndex(); } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index c0b6eeba9c..91459eee28 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -12,7 +12,7 @@ namespace CSMWorld { int mPluginIndex; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 002838c67d..bfacc15dc9 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -606,7 +606,7 @@ namespace CSMWorld funcMap["09"] = "PC Fatigue"; funcMap["10"] = "PC Strength"; funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armoror"; + funcMap["12"] = "PC Armorer"; funcMap["13"] = "PC Medium Armor"; funcMap["14"] = "PC Heavy Armor"; funcMap["15"] = "PC Blunt Weapon"; diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 81c52588bb..2fd569bd0d 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -317,8 +317,34 @@ namespace CSMWorld else throw std::runtime_error("Magic effects ID unexpected value"); } - case 1: return effect.mSkill; - case 2: return effect.mAttribute; + case 1: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return effect.mSkill; + default: + return QVariant(); + } + } + case 2: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return effect.mAttribute; + default: + return QVariant(); + } + } case 3: { if (effect.mRange >=0 && effect.mRange <=2) diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index 5c66e7d8ea..c995bd8f09 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -4,33 +4,28 @@ #include -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, const IdCollection& cells) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells) { - load (esm); + load (esm, isDeleted); // correct ID if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted) { - ESM::Pathgrid::load (esm); + ESM::Pathgrid::load (esm, isDeleted); + mId = mCell; if (mCell.empty()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } - else - mId = mCell; } diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 7e7b7c3bb6..22d01b0710 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -20,9 +20,8 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm, const IdCollection& cells); - - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index f8818807bc..65251a81db 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -19,12 +19,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; - - bool deleted = false; ESM::MovedCellRef mref; + bool isDeleted = false; // hack to initialise mindex - while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, deleted, true, &mref)) + while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). @@ -49,17 +48,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; - if (deleted) - { - // FIXME: how to mark the record deleted? - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, - mCells.getId (cellIndex)); - - messages.add (id, "Moved reference "+ref.mRefID+" is in DELE state"); - - continue; - } - // It is not always possibe to ignore moved references sub-record and // calculate from coordinates. Some mods may place the ref in positions // outside normal bounds, resulting in non sensical cell id's. This often @@ -91,7 +79,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool break; } - if (deleted) + if (isDeleted) { if (iter==cache.end()) { @@ -99,7 +87,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool mCells.getId (cellIndex)); messages.add (id, "Attempt to delete a non-existing reference"); - continue; } @@ -107,7 +94,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Record record = getRecord (index); - if (record.mState==RecordBase::State_BaseOnly) + if (base) { removeRows (index, 1); cache.erase (iter); diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index da0cc07600..90a710fc89 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "nestedtablewrapper.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) @@ -25,8 +27,9 @@ QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const if (column==mAutoCalc) return record.get().mData.mAutoCalc!=0; + // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column==mColumns.mEffects) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return InventoryRefIdAdapter::getData (column, data, index); } @@ -52,6 +55,156 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData } +CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) +: InventoryColumns (columns) {} + +CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) +: InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), + mColumns(columns) +{} + +QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + if (column==mColumns.mEffects) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + + return InventoryRefIdAdapter::getData (column, data, index); +} + +void CSMWorld::IngredientRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const +{ + InventoryRefIdAdapter::setData (column, data, index, value); + + return; +} + + +CSMWorld::IngredEffectRefIdAdapter::IngredEffectRefIdAdapter() +: mType(UniversalId::Type_Ingredient) +{} + +CSMWorld::IngredEffectRefIdAdapter::~IngredEffectRefIdAdapter() +{} + +void CSMWorld::IngredEffectRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Ingredient ingredient = record.get(); + + ingredient.mData = + static_cast >&>(nestedTable).mNestedTable.at(0); + + record.setModified (ingredient); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return record.get().mData.mEffectID[subRowIndex]; + case 1: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return record.get().mData.mSkills[subRowIndex]; + default: + return QVariant(); + } + } + case 2: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return record.get().mData.mAttributes[subRowIndex]; + default: + return QVariant(); + } + } + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Ingredient ingredient = record.get(); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch(subColIndex) + { + case 0: ingredient.mData.mEffectID[subRowIndex] = value.toInt(); break; + case 1: ingredient.mData.mSkills[subRowIndex] = value.toInt(); break; + case 2: ingredient.mData.mAttributes[subRowIndex] = value.toInt(); break; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } + + record.setModified (ingredient); +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; // effect, skill, attribute +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 4; // up to 4 effects +} + + CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type, const RefIdColumn *quality) : InventoryRefIdAdapter (UniversalId::Type_Apparatus, columns), @@ -118,7 +271,7 @@ QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mArmor; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -206,7 +359,7 @@ QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mType; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -254,7 +407,7 @@ QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, return (record.get().mFlags & ESM::Container::Respawn)!=0; if (column==mContent) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(ColumnBase::TableEdit_Full); return NameRefIdAdapter::getData (column, data, index); } @@ -323,13 +476,13 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con return QString::fromUtf8 (record.get().mOriginal.c_str()); if (column==mColumns.mAttributes) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); if (column==mColumns.mAttacks) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); if (column==mColumns.mMisc) - return true; // Required to show nested items in dialogue subview + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -569,13 +722,13 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re if (column==mColumns.mAttributes || column==mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant(QVariant::UserType); + return QVariant::fromValue(ColumnBase::TableEdit_None); else - return true; + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); } if (column==mColumns.mMisc) - return true; + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 4ac27b6e90..eff7167def 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -11,6 +11,7 @@ #include #include +#include "columnbase.hpp" #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" @@ -340,6 +341,66 @@ namespace CSMWorld ///< If the data type does not match an exception is thrown. }; + struct IngredientColumns : public InventoryColumns + { + const RefIdColumn *mEffects; + + IngredientColumns (const InventoryColumns& columns); + }; + + class IngredientRefIdAdapter : public InventoryRefIdAdapter + { + IngredientColumns mColumns; + + public: + + IngredientRefIdAdapter (const IngredientColumns& columns); + + virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) + const; + + virtual void setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const; + ///< If the data type does not match an exception is thrown. + }; + + class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase + { + UniversalId::Type mType; + + // not implemented + IngredEffectRefIdAdapter (const IngredEffectRefIdAdapter&); + IngredEffectRefIdAdapter& operator= (const IngredEffectRefIdAdapter&); + + public: + + IngredEffectRefIdAdapter(); + + virtual ~IngredEffectRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + struct EnchantableColumns : public InventoryColumns { const RefIdColumn *mEnchantment; @@ -536,16 +597,16 @@ namespace CSMWorld return record.get().mAiData.mAlarm; if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mSpells) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); @@ -2020,7 +2081,7 @@ namespace CSMWorld int index) const { if (column==mLevList.mLevList || column == mLevList.mNestedListLevList) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return BaseRefIdAdapter::getData (column, data, index); } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 3b316bea3a..c4c8f8605d 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -71,6 +71,21 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer)); inventoryColumns.mValue = &mColumns.back(); + IngredientColumns ingredientColumns (inventoryColumns); + mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + ingredientColumns.mEffects = &mColumns.back(); + std::map ingredientEffectsMap; + ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, + new IngredEffectRefIdAdapter ())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), ingredientEffectsMap)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + // nested table PotionColumns potionColumns (inventoryColumns); mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, @@ -83,9 +98,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mColumns.back().addColumn( @@ -651,7 +666,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Door, new DoorRefIdAdapter (nameColumns, openSound, closeSound))); mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient, - new InventoryRefIdAdapter (UniversalId::Type_Ingredient, inventoryColumns))); + new IngredientRefIdAdapter (ingredientColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, new LevelledListRefIdAdapter ( UniversalId::Type_CreatureLevelledList, levListColumns))); @@ -822,61 +837,7 @@ const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) con void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type) { - std::string id = reader.getHNOString ("NAME"); - - int index = searchId (id); - - if (reader.isNextSub ("DELE")) - { - reader.skipRecord(); - - if (index==-1) - { - // deleting a record that does not exist - - // ignore it for now - - /// \todo report the problem to the user - } - else if (base) - { - mData.erase (index, 1); - } - else - { - mData.getRecord (mData.globalToLocalIndex (index)).mState = RecordBase::State_Deleted; - } - } - else - { - if (index==-1) - { - // new record - int index = mData.getAppendIndex (type); - mData.appendRecord (type, id, base); - - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - mData.load (localIndex, reader, base); - - mData.getRecord (localIndex).mState = - base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - } - else - { - // old record - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - if (!base) - if (mData.getRecord (localIndex).mState==RecordBase::State_Erased) - throw std::logic_error ("attempt to access a deleted record"); - - mData.load (localIndex, reader, base); - - if (!base) - mData.getRecord (localIndex).mState = RecordBase::State_Modified; - } - } + mData.load(reader, base, type); } int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7696c3763b..2d8c9ac108 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -3,10 +3,20 @@ #include #include -#include - CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} + +std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const +{ + std::map::const_iterator found = + mRecordContainers.find (index.second); + + if (found == mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + return found->second->getId(index.first); +} + CSMWorld::RefIdData::RefIdData() { mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); @@ -161,15 +171,27 @@ int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const return index; } -void CSMWorld::RefIdData::load (const LocalIndex& index, ESM::ESMReader& reader, bool base) +void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) { - std::map::iterator iter = - mRecordContainers.find (index.second); + std::map::iterator found = + mRecordContainers.find (type); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (found == mRecordContainers.end()) + throw std::logic_error ("Invalid Referenceable ID type"); - iter->second->load (index.first, reader, base); + int index = found->second->load(reader, base); + if (index != -1) + { + LocalIndex localIndex = LocalIndex(index, type); + if (base && getRecord(localIndex).mState == RecordBase::State_Deleted) + { + erase(localIndex, 1); + } + else + { + mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex; + } + } } void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 8909ae4fb0..59cad6a661 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include "record.hpp" #include "universalid.hpp" @@ -49,7 +51,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record) = 0; - virtual void load (int index, ESM::ESMReader& reader, bool base) = 0; + virtual int load (ESM::ESMReader& reader, bool base) = 0; + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count) = 0; @@ -73,7 +76,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record); - virtual void load (int index, ESM::ESMReader& reader, bool base); + virtual int load (ESM::ESMReader& reader, bool base); + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count); @@ -122,9 +126,58 @@ namespace CSMWorld } template - void RefIdDataContainer::load (int index, ESM::ESMReader& reader, bool base) + int RefIdDataContainer::load (ESM::ESMReader& reader, bool base) { - (base ? mContainer.at (index).mBase : mContainer.at (index).mModified).load (reader); + RecordT record; + bool isDeleted = false; + + record.load(reader, isDeleted); + + int index = 0; + int numRecords = static_cast(mContainer.size()); + for (; index < numRecords; ++index) + { + if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId)) + { + break; + } + } + + if (isDeleted) + { + if (index == numRecords) + { + // deleting a record that does not exist + // ignore it for now + /// \todo report the problem to the user + return -1; + } + + // Flag the record as Deleted even for a base content file. + // RefIdData is responsible for its erasure. + mContainer[index].mState = RecordBase::State_Deleted; + } + else + { + if (index == numRecords) + { + appendRecord(record.mId, base); + if (base) + { + mContainer.back().mBase = record; + } + else + { + mContainer.back().mModified = record; + } + } + else if (!base) + { + mContainer[index].setModified(record); + } + } + + return index; } template @@ -145,19 +198,14 @@ namespace CSMWorld template void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { - CSMWorld::RecordBase::State state = mContainer.at (index).mState; + Record record = mContainer.at(index); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == RecordBase::State_Deleted) { - writer.startRecord (mContainer.at (index).mModified.sRecordId); - writer.writeHNCString ("NAME", getId (index)); - mContainer.at (index).mModified.save (writer); - writer.endRecord (mContainer.at (index).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + RecordT esmRecord = record.get(); + writer.startRecord(esmRecord.sRecordId); + esmRecord.save(writer, record.mState == RecordBase::State_Deleted); + writer.endRecord(esmRecord.sRecordId); } } @@ -198,6 +246,8 @@ namespace CSMWorld void erase (const LocalIndex& index, int count); ///< Must not spill over into another type. + std::string getRecordId(const LocalIndex &index) const; + public: RefIdData(); @@ -221,7 +271,7 @@ namespace CSMWorld int getAppendIndex (UniversalId::Type type) const; - void load (const LocalIndex& index, ESM::ESMReader& reader, bool base); + void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); int getSize() const; diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 10c67c909d..6dbbac97fb 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -168,7 +168,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions { std::vector regions2 (regions); - std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); + std::for_each (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (regions2.begin(), regions2.end()); for (std::map::const_iterator iter (mMap.begin()); diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 2ec661cb1a..016799be35 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -19,7 +19,9 @@ void CSMWorld::ResourcesManager::setVFS(const VFS::Manager *vfs) mVFS = vfs; mResources.clear(); - static const char * const sMeshTypes[] = { "nif", 0 }; + // maybe we could go over the osgDB::Registry to list all supported node formats + + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; addResources (Resources (vfs, "meshes", UniversalId::Type_Mesh, sMeshTypes)); addResources (Resources (vfs, "icons", UniversalId::Type_Icon)); diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index f644ad37ad..344ae322e9 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -93,7 +93,7 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const { mIds = mData.getIds(); - std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::toLower); + std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCaseInPlace); std::sort (mIds.begin(), mIds.end()); mIdsUpdated = true; diff --git a/apps/opencs/model/world/subcellcollection.hpp b/apps/opencs/model/world/subcellcollection.hpp index df1f8a12ea..496cb06430 100644 --- a/apps/opencs/model/world/subcellcollection.hpp +++ b/apps/opencs/model/world/subcellcollection.hpp @@ -20,7 +20,7 @@ namespace CSMWorld { const IdCollection& mCells; - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -29,9 +29,10 @@ namespace CSMWorld template void SubCellCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader, mCells); + record.load (reader, isDeleted, mCells); } template diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index 101bbf9ba2..1268a7389d 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -179,7 +179,7 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::Univers } } - throw std::runtime_error ("TableMimeData object does not hold object of the seeked type"); + throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); } CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const @@ -201,7 +201,7 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnB } } - throw std::runtime_error ("TableMimeData object does not hold object of the seeked type"); + throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); } bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index 110d561c17..9e9acdfbe6 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -16,7 +16,6 @@ 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]*$"))); layout->addWidget (mInput, 1); diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index a9d697f1c2..67ff50dab9 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -104,14 +104,16 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) layout->addWidget (createButtons()); layout->addWidget (createTools()); - /// \todo remove this label once loading and saving are fully implemented - QLabel *warning = new QLabel ("WARNING:

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

Please note:
If you lose data and come to the OpenMW forum to complain,
we will mock you.
"); + /// \todo remove this label once we are feature complete and convinced that this thing is + /// working properly. + QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advice to make backups regularly if you are working with live data.
"); QFont font; font.setPointSize (12); font.setBold (true); warning->setFont (font); + warning->setWordWrap (true); layout->addWidget (warning, 1); diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index e0c2fbc46a..67a8f8c705 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -16,7 +16,7 @@ bool CSVDoc::SubView::event (QEvent *event) emit closeRequest(); return true; } - + return QDockWidget::event (event); } @@ -38,9 +38,6 @@ void CSVDoc::SubView::setStatusBar (bool show) {} void CSVDoc::SubView::useHint (const std::string& hint) {} -void CSVDoc::SubView::updateUserSetting (const QString &, const QStringList &) -{} - void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) { mUniversalId = id; diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 189bb40ebe..1c5f8a7866 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -52,8 +52,6 @@ namespace CSVDoc virtual std::string getTitle() const; - virtual void updateUserSetting (const QString& name, const QStringList& value); - private: void closeEvent (QCloseEvent *event); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 38088c6d70..3491c9de2b 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -15,7 +15,7 @@ #include #include "../../model/doc/document.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "../../model/world/idtable.hpp" @@ -121,10 +121,9 @@ void CSVDoc::View::setupViewMenu() mShowStatusBar = new QAction (tr ("Show Status Bar"), this); mShowStatusBar->setCheckable (true); connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); - std::string showStatusBar = - CSMSettings::UserSettings::instance().settingValue("window/show-statusbar").toStdString(); - if(showStatusBar == "true") - mShowStatusBar->setChecked(true); + + mShowStatusBar->setChecked (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); + view->addAction (mShowStatusBar); QAction *filters = new QAction (tr ("Filters"), this); @@ -333,9 +332,9 @@ void CSVDoc::View::updateTitle() if (mViewTotal>1) stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]"; - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - bool hideTitle = userSettings.setting ("window/hide-subview", QString ("false"))=="true" && + bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); if (hideTitle) @@ -346,19 +345,18 @@ void CSVDoc::View::updateTitle() void CSVDoc::View::updateSubViewIndicies(SubView *view) { + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; + if(view && mSubViews.contains(view)) { mSubViews.removeOne(view); // adjust (reduce) the scroll area (even floating), except when it is "Scrollbar Only" - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - if(settings.settingValue ("window/mainwindow-scrollbar") == "Grow then Scroll") + if (windows["mainwindow-scrollbar"].toString() == "Grow then Scroll") updateScrollbar(); } - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - - bool hideTitle = userSettings.setting ("window/hide-subview", QString ("false"))=="true" && + bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); updateTitle(); @@ -406,21 +404,16 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews), mScroll(0), mScrollbarOnly(false) { - int width = CSMSettings::UserSettings::instance().settingValue - ("window/default-width").toInt(); - - int height = CSMSettings::UserSettings::instance().settingValue - ("window/default-height").toInt(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - width = std::max(width, 300); - height = std::max(height, 300); + int width = std::max (windows["default-width"].toInt(), 300); + int height = std::max (windows["default-height"].toInt(), 300); resize (width, height); mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - if(settings.settingValue ("window/mainwindow-scrollbar") == "Grow Only") + if (windows["mainwindow-scrollbar"].toString() == "Grow Only") { setCentralWidget (&mSubViewWindow); } @@ -449,6 +442,9 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); } CSVDoc::View::~View() @@ -503,14 +499,12 @@ void CSVDoc::View::updateProgress (int current, int max, int type, int threads) void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::string& hint) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; bool isReferenceable = id.getClass() == CSMWorld::UniversalId::Class_RefRecord; // User setting to reuse sub views (on a per top level view basis) - bool reuse = - userSettings.setting ("window/reuse", QString("true")) == "true" ? true : false; - if(reuse) + if (windows["reuse"].isTrue()) { foreach(SubView *sb, mSubViews) { @@ -538,8 +532,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // // If the sub view limit setting is one, the sub view title bar is hidden and the // text in the main title bar is adjusted accordingly - int maxSubView = userSettings.setting("window/max-subviews", QString("256")).toInt(); - if(mSubViews.size() >= maxSubView) // create a new top level view + if(mSubViews.size() >= windows["max-subviews"].toInt()) // create a new top level view { mViewManager.addView(mDocument, id, hint); @@ -559,8 +552,8 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin view->setParent(this); mSubViews.append(view); // only after assert - int minWidth = userSettings.setting ("window/minimum-width", QString("325")).toInt(); - view->setMinimumWidth(minWidth); + int minWidth = windows["minimum-width"].toInt(); + view->setMinimumWidth (minWidth); view->setStatusBar (mShowStatusBar->isChecked()); @@ -575,13 +568,11 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // should become visible) // - Move the scroll bar to the newly added subview // - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - QString mainwinScroll = settings.settingValue ("window/mainwindow-scrollbar"); - mScrollbarOnly = mainwinScroll.isEmpty() || mainwinScroll == "Scrollbar Only"; + mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only"; QDesktopWidget *dw = QApplication::desktop(); QRect rect; - if(settings.settingValue ("window/grow-limit") == "true") + if (windows["grow-limit"].isTrue()) rect = dw->screenGeometry(this); else rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); @@ -637,6 +628,45 @@ void CSVDoc::View::moveScrollBarToEnd(int min, int max) } } +void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="Windows/hide-subview") + updateSubViewIndicies (0); + else if (*setting=="Windows/mainwindow-scrollbar") + { + if (setting->toString()!="Grow Only") + { + if (mScroll) + { + if (setting->toString()=="Scrollbar Only") + { + mScrollbarOnly = true; + mSubViewWindow.setMinimumWidth(0); + } + else if (mScrollbarOnly) + { + mScrollbarOnly = false; + updateScrollbar(); + } + } + else + { + mScroll = new QScrollArea(this); + mScroll->setWidgetResizable(true); + mScroll->setWidget(&mSubViewWindow); + setCentralWidget(mScroll); + } + } + else if (mScroll) + { + mScroll->takeWidget(); + setCentralWidget (&mSubViewWindow); + mScroll->deleteLater(); + mScroll = 0; + } + } +} + void CSVDoc::View::newView() { mViewManager.addView (mDocument); @@ -860,59 +890,6 @@ void CSVDoc::View::resizeViewHeight (int height) resize (geometry().width(), height); } -void CSVDoc::View::updateUserSetting (const QString &name, const QStringList &list) -{ - if (name=="window/hide-subview") - updateSubViewIndicies (0); - - foreach (SubView *subView, mSubViews) - { - subView->updateUserSetting (name, list); - } - - if (name=="window/mainwindow-scrollbar") - { - if(list.at(0) != "Grow Only") - { - if (mScroll) - { - if (list.at(0).isEmpty() || list.at(0) == "Scrollbar Only") - { - mScrollbarOnly = true; - mSubViewWindow.setMinimumWidth(0); - } - else - { - if(!mScrollbarOnly) - return; - - mScrollbarOnly = false; - updateScrollbar(); - } - } - else - { - mScroll = new QScrollArea(this); - mScroll->setWidgetResizable(true); - mScroll->setWidget(&mSubViewWindow); - setCentralWidget(mScroll); - } - } - else - { - if (mScroll) - { - mScroll->takeWidget(); - setCentralWidget (&mSubViewWindow); - mScroll->deleteLater(); - mScroll = 0; - } - else - return; - } - } -} - void CSVDoc::View::toggleShowStatusBar (bool show) { foreach (QObject *view, mSubViewWindow.children()) @@ -944,10 +921,9 @@ void CSVDoc::View::stop() void CSVDoc::View::closeRequest (SubView *subView) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - if (mSubViews.size()>1 || mViewTotal<=1 || - userSettings.setting ("window/hide-subview", QString ("false"))!="true") + if (mSubViews.size()>1 || mViewTotal<=1 || !windows["hide-subview"].isTrue()) { subView->deleteLater(); mSubViews.removeOne (subView); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 2dd7174407..7d53042693 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -22,6 +22,11 @@ namespace CSMWorld class UniversalId; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVDoc { class ViewManager; @@ -83,8 +88,6 @@ namespace CSVDoc void exitApplication(); - void loadUserSettings(); - /// User preference function void resizeViewWidth (int width); @@ -137,8 +140,6 @@ namespace CSVDoc void abortOperation (int type); - void updateUserSetting (const QString &, const QStringList &); - void updateTitle(); // called when subviews are added or removed @@ -146,6 +147,8 @@ namespace CSVDoc private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void newView(); void save(); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 728e69a7a6..024636f34b 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -14,6 +14,8 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/prefs/state.hpp" + #include "../world/util.hpp" #include "../world/enumdelegate.hpp" #include "../world/vartypedelegate.hpp" @@ -22,8 +24,6 @@ #include "../world/idcompletiondelegate.hpp" #include "../world/colordelegate.hpp" -#include "../../model/settings/usersettings.hpp" - #include "view.hpp" void CSVDoc::ViewManager::updateIndices() @@ -104,6 +104,9 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, + { CSMWorld::ColumnBase::Display_IngredEffectId, CSMWorld::Columns::ColumnId_EffectId, true }, + { CSMWorld::ColumnBase::Display_EffectSkill, CSMWorld::Columns::ColumnId_Skill, false }, + { CSMWorld::ColumnBase::Display_EffectAttribute, CSMWorld::Columns::ColumnId_Attribute, false }, }; for (std::size_t i=0; itoggleStatusBar (showStatusBar == "true"); + view->toggleStatusBar (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); view->show(); connect (view, SIGNAL (newGameRequest ()), this, SIGNAL (newGameRequest())); @@ -174,11 +174,6 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); connect (view, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SIGNAL (mergeDocument (CSMDoc::Document *))); - connect (&CSMSettings::UserSettings::instance(), - SIGNAL (userSettingUpdated(const QString &, const QStringList &)), - view, - SLOT (updateUserSetting (const QString &, const QStringList &))); - updateIndices(); return view; diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp new file mode 100644 index 0000000000..f040926530 --- /dev/null +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -0,0 +1,126 @@ + +#include "dialogue.hpp" + +#include +#include +#include +#include +#include +#include + +#include "../../model/prefs/state.hpp" + +#include "page.hpp" + +void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) +{ + mList = new QListWidget (main); + mList->setMinimumWidth (50); + mList->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); + + mList->setSelectionBehavior (QAbstractItemView::SelectItems); + + main->addWidget (mList); + + QFontMetrics metrics (QApplication::font()); + + int maxWidth = 1; + + for (CSMPrefs::State::Iterator iter = CSMPrefs::get().begin(); iter!=CSMPrefs::get().end(); + ++iter) + { + QString label = QString::fromUtf8 (iter->second.getKey().c_str()); + maxWidth = std::max (maxWidth, metrics.width (label)); + + mList->addItem (label); + } + + mList->setMaximumWidth (maxWidth + 10); + + connect (mList, SIGNAL (currentItemChanged (QListWidgetItem *, QListWidgetItem *)), + this, SLOT (selectionChanged (QListWidgetItem *, QListWidgetItem *))); +} + +void CSVPrefs::Dialogue::buildContentArea (QSplitter *main) +{ + mContent = new QStackedWidget (main); + mContent->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); + + main->addWidget (mContent); +} + +CSVPrefs::PageBase *CSVPrefs::Dialogue::makePage (const std::string& key) +{ + // special case page code goes here + + return new Page (CSMPrefs::get()[key], mContent); +} + +CSVPrefs::Dialogue::Dialogue() +{ + setWindowTitle ("User Settings"); + + setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + setMinimumSize (600, 400); + + QSplitter *main = new QSplitter (this); + + setCentralWidget (main); + buildCategorySelector (main); + buildContentArea (main); +} + +CSVPrefs::Dialogue::~Dialogue() +{ + if (isVisible()) + CSMPrefs::State::get().save(); +} + +void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event) +{ + QMainWindow::closeEvent (event); + + CSMPrefs::State::get().save(); +} + +void CSVPrefs::Dialogue::show() +{ + if (QWidget *active = QApplication::activeWindow()) + { + // place at the centre of the window with focus + QSize size = active->size(); + move (active->geometry().x()+(size.width() - frameGeometry().width())/2, + active->geometry().y()+(size.height() - frameGeometry().height())/2); + } + else + { + // otherwise place at the centre of the screen + QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); + move (screenCenter - QPoint(frameGeometry().width()/2, frameGeometry().height()/2)); + } + + QWidget::show(); +} + +void CSVPrefs::Dialogue::selectionChanged (QListWidgetItem *current, QListWidgetItem *previous) +{ + if (current) + { + std::string key = current->text().toUtf8().data(); + + for (int i=0; icount(); ++i) + { + PageBase& page = dynamic_cast (*mContent->widget (i)); + + if (page.getCategory().getKey()==key) + { + mContent->setCurrentIndex (i); + return; + } + } + + PageBase *page = makePage (key); + mContent->setCurrentIndex (mContent->addWidget (page)); + } +} diff --git a/apps/opencs/view/prefs/dialogue.hpp b/apps/opencs/view/prefs/dialogue.hpp new file mode 100644 index 0000000000..fc66892c8b --- /dev/null +++ b/apps/opencs/view/prefs/dialogue.hpp @@ -0,0 +1,50 @@ +#ifndef CSV_PREFS_DIALOGUE_H +#define CSV_PREFS_DIALOGUE_H + +#include + +class QSplitter; +class QListWidget; +class QStackedWidget; +class QListWidgetItem; + +namespace CSVPrefs +{ + class PageBase; + + class Dialogue : public QMainWindow + { + Q_OBJECT + + QListWidget *mList; + QStackedWidget *mContent; + + private: + + void buildCategorySelector (QSplitter *main); + + void buildContentArea (QSplitter *main); + + PageBase *makePage (const std::string& key); + + public: + + Dialogue(); + + virtual ~Dialogue(); + + protected: + + void closeEvent (QCloseEvent *event); + + public slots: + + void show(); + + private slots: + + void selectionChanged (QListWidgetItem *current, QListWidgetItem *previous); + }; +} + +#endif diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp new file mode 100644 index 0000000000..c23e9f64fe --- /dev/null +++ b/apps/opencs/view/prefs/page.cpp @@ -0,0 +1,40 @@ + +#include "page.hpp" + +#include + +#include "../../model/prefs/setting.hpp" +#include "../../model/prefs/category.hpp" + +CSVPrefs::Page::Page (CSMPrefs::Category& category, QWidget *parent) +: PageBase (category, parent) +{ + QWidget *widget = new QWidget (parent); + mGrid = new QGridLayout (widget); + + for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) + addSetting (*iter); + + setWidget (widget); +} + +void CSVPrefs::Page::addSetting (CSMPrefs::Setting *setting) +{ + std::pair widgets = setting->makeWidgets (this); + + int next = mGrid->rowCount(); + + if (widgets.first) + { + mGrid->addWidget (widgets.first, next, 0); + mGrid->addWidget (widgets.second, next, 1); + } + else if (widgets.second) + { + mGrid->addWidget (widgets.second, next, 0, 1, 2); + } + else + { + mGrid->addWidget (new QWidget (this), next, 0); + } +} diff --git a/apps/opencs/view/prefs/page.hpp b/apps/opencs/view/prefs/page.hpp new file mode 100644 index 0000000000..ce13e5d9b9 --- /dev/null +++ b/apps/opencs/view/prefs/page.hpp @@ -0,0 +1,29 @@ +#ifndef CSV_PREFS_PAGE_H +#define CSV_PREFS_PAGE_H + +#include "pagebase.hpp" + +class QGridLayout; + +namespace CSMPrefs +{ + class Setting; +} + +namespace CSVPrefs +{ + class Page : public PageBase + { + Q_OBJECT + + QGridLayout *mGrid; + + public: + + Page (CSMPrefs::Category& category, QWidget *parent); + + void addSetting (CSMPrefs::Setting *setting); + }; +} + +#endif diff --git a/apps/opencs/view/prefs/pagebase.cpp b/apps/opencs/view/prefs/pagebase.cpp new file mode 100644 index 0000000000..16684a69d4 --- /dev/null +++ b/apps/opencs/view/prefs/pagebase.cpp @@ -0,0 +1,13 @@ + +#include "pagebase.hpp" + +#include "../../model/prefs/category.hpp" + +CSVPrefs::PageBase::PageBase (CSMPrefs::Category& category, QWidget *parent) +: QScrollArea (parent), mCategory (category) +{} + +CSMPrefs::Category& CSVPrefs::PageBase::getCategory() +{ + return mCategory; +} diff --git a/apps/opencs/view/prefs/pagebase.hpp b/apps/opencs/view/prefs/pagebase.hpp new file mode 100644 index 0000000000..affe49f4a9 --- /dev/null +++ b/apps/opencs/view/prefs/pagebase.hpp @@ -0,0 +1,27 @@ +#ifndef CSV_PREFS_PAGEBASE_H +#define CSV_PREFS_PAGEBASE_H + +#include + +namespace CSMPrefs +{ + class Category; +} + +namespace CSVPrefs +{ + class PageBase : public QScrollArea + { + Q_OBJECT + + CSMPrefs::Category& mCategory; + + public: + + PageBase (CSMPrefs::Category& category, QWidget *parent); + + CSMPrefs::Category& getCategory(); + }; +} + +#endif diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index fce5ffda5c..1356aa0fbd 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -18,6 +18,33 @@ CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const return mArrow; } +QString CSVRender::CellArrowTag::getToolTip (bool hideBasics) const +{ + QString text ("Direction: "); + + switch (mArrow->getDirection()) + { + case CellArrow::Direction_North: text += "North"; break; + case CellArrow::Direction_West: text += "West"; break; + case CellArrow::Direction_South: text += "South"; break; + case CellArrow::Direction_East: text += "East"; break; + } + + if (!hideBasics) + { + text += + "

" + "Modify which cells are shown" + "

  • Primary-Edit: Add cell in given direction
  • " + "
  • Secondary-Edit: Add cell and remove old cell
  • " + "
  • Shift Primary-Edit: Add cells in given direction
  • " + "
  • Shift Secondary-Edit: Add cells and remove old cells
  • " + "
"; + } + + return text; +} + void CSVRender::CellArrow::adjustTransform() { diff --git a/apps/opencs/view/render/cellarrow.hpp b/apps/opencs/view/render/cellarrow.hpp index cbbcc9d5ee..4523561946 100644 --- a/apps/opencs/view/render/cellarrow.hpp +++ b/apps/opencs/view/render/cellarrow.hpp @@ -26,6 +26,8 @@ namespace CSVRender CellArrowTag (CellArrow *arrow); CellArrow *getCellArrow() const; + + virtual QString getToolTip (bool hideBasics) const; }; diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index 4235faf768..b325e31fb0 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -24,11 +24,6 @@ void CSVRender::EditMode::activate (CSVWidget::SceneToolbar *toolbar) mWorldspaceWidget->clearSelection (~mMask); } -void CSVRender::EditMode::updateUserSetting (const QString& name, const QStringList& value) -{ - -} - void CSVRender::EditMode::setEditLock (bool locked) { @@ -38,7 +33,9 @@ void CSVRender::EditMode::primaryEditPressed (osg::ref_ptr tag) {} void CSVRender::EditMode::secondaryEditPressed (osg::ref_ptr tag) {} -void CSVRender::EditMode::selectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primarySelectPressed (osg::ref_ptr tag) {} + +void CSVRender::EditMode::secondarySelectPressed (osg::ref_ptr tag) {} bool CSVRender::EditMode::primaryEditStartDrag (osg::ref_ptr tag) { @@ -50,7 +47,12 @@ bool CSVRender::EditMode::secondaryEditStartDrag (osg::ref_ptr tag) return false; } -bool CSVRender::EditMode::selectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primarySelectStartDrag (osg::ref_ptr tag) +{ + return false; +} + +bool CSVRender::EditMode::secondarySelectStartDrag (osg::ref_ptr tag) { return false; } @@ -62,3 +64,9 @@ void CSVRender::EditMode::dragCompleted() {} void CSVRender::EditMode::dragAborted() {} void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} + +void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} + +void CSVRender::EditMode::dropEvent (QDropEvent* event) {} + +void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index 77676d6a3f..3ba97cf007 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -5,6 +5,10 @@ #include "../widget/modebutton.hpp" +class QDragEnterEvent; +class QDropEvent; +class QDragMoveEvent; + namespace CSVRender { class WorldspaceWidget; @@ -30,9 +34,6 @@ namespace CSVRender virtual void activate (CSVWidget::SceneToolbar *toolbar); - /// Default-implementation: Do nothing. - virtual void updateUserSetting (const QString& name, const QStringList& value); - /// Default-implementation: Ignored. virtual void setEditLock (bool locked); @@ -43,7 +44,10 @@ namespace CSVRender virtual void secondaryEditPressed (osg::ref_ptr tag); /// Default-implementation: Ignored. - virtual void selectPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (osg::ref_ptr tag); + + /// Default-implementation: Ignored. + virtual void secondarySelectPressed (osg::ref_ptr tag); /// Default-implementation: ignore and return false /// @@ -58,7 +62,12 @@ namespace CSVRender /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool selectStartDrag (osg::ref_ptr tag); + virtual bool primarySelectStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool secondarySelectStartDrag (osg::ref_ptr tag); /// Default-implementation: ignored virtual void drag (int diffX, int diffY, double speedFactor); @@ -74,6 +83,15 @@ namespace CSVRender /// Default-implementation: ignored virtual void dragWheel (int diff, double speedFactor); + + /// Default-implementation: ignored + virtual void dragEnterEvent (QDragEnterEvent *event); + + /// Default-implementation: ignored + virtual void dropEvent (QDropEvent* event); + + /// Default-implementation: ignored + virtual void dragMoveEvent (QDragMoveEvent *event); }; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 333d916565..a4d147bb4d 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -1,7 +1,7 @@ #include "instancemode.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "elements.hpp" #include "object.hpp" @@ -9,37 +9,40 @@ CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", - parent), mContextSelect (false) + parent) { } -void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) +void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) { - EditMode::activate (toolbar); - - mContextSelect = CSMSettings::UserSettings::instance().setting ("scene-input/context-select")=="true"; + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + primarySelectPressed (tag); } -void CSVRender::InstanceMode::updateUserSetting (const QString& name, const QStringList& value) +void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) { - if (name=="scene-input/context-select") - mContextSelect = value.at (0)=="true"; + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + secondarySelectPressed (tag); } -void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) { - if (mContextSelect) - selectPressed (tag); -} + getWorldspaceWidget().clearSelection (Element_Reference); -void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) -{ - if (mContextSelect) - selectPressed (tag); + if (tag) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + // hit an Object, select it + CSVRender::Object* object = objectTag->mObject; + object->setSelected (true); + return; + } + } } -void CSVRender::InstanceMode::selectPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) { if (tag) { @@ -51,6 +54,4 @@ void CSVRender::InstanceMode::selectPressed (osg::ref_ptr tag) return; } } - - getWorldspaceWidget().clearSelection (Element_Reference); } diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index cc4fd54349..66451bd996 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -9,21 +9,17 @@ namespace CSVRender { Q_OBJECT - bool mContextSelect; - public: InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0); - virtual void activate (CSVWidget::SceneToolbar *toolbar); - - virtual void updateUserSetting (const QString& name, const QStringList& value); - virtual void primaryEditPressed (osg::ref_ptr tag); virtual void secondaryEditPressed (osg::ref_ptr tag); - virtual void selectPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (osg::ref_ptr tag); + + virtual void secondarySelectPressed (osg::ref_ptr tag); }; } diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index ac96cb2835..c295a023a8 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -42,6 +42,11 @@ CSVRender::ObjectTag::ObjectTag (Object* object) : TagBase (Element_Reference), mObject (object) {} +QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const +{ + return QString::fromUtf8 (mObject->getReferenceableId().c_str()); +} + void CSVRender::Object::clear() { diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 0858a2edb2..e7638e7a99 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -46,6 +46,8 @@ namespace CSVRender ObjectTag (Object* object); Object* mObject; + + virtual QString getToolTip (bool hideBasics) const; }; diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 76b3db348d..b1300a991f 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -16,7 +16,6 @@ #include #include "../widget/scenetoolmode.hpp" -#include "../../model/settings/usersettings.hpp" #include "lighting.hpp" @@ -126,7 +125,7 @@ CompositeViewer::CompositeViewer() // Qt5 is currently crashing and reporting "Cannot make QOpenGLContext current in a different thread" when the viewer is run multi-threaded, this is regression from Qt4 osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::SingleThreaded; #else - osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::CullDrawThreadPerContext; + osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::DrawThreadPerContext; #endif setThreadingModel(threadingModel); diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp index af9a376243..79412c1321 100644 --- a/apps/opencs/view/render/tagbase.cpp +++ b/apps/opencs/view/render/tagbase.cpp @@ -7,3 +7,8 @@ CSVRender::Elements CSVRender::TagBase::getElement() const { return mElement; } + +QString CSVRender::TagBase::getToolTip (bool hideBasics) const +{ + return ""; +} diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp index 874b856c63..9f169c3b1a 100644 --- a/apps/opencs/view/render/tagbase.hpp +++ b/apps/opencs/view/render/tagbase.hpp @@ -3,6 +3,8 @@ #include +#include + #include "elements.hpp" namespace CSVRender @@ -16,6 +18,9 @@ namespace CSVRender TagBase (Elements element); Elements getElement() const; + + virtual QString getToolTip (bool hideBasics) const; + }; } diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 860ed00805..2be4efd73e 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -37,9 +37,7 @@ namespace CSVRender return ltex; } - std::stringstream error; - error << "Can't find LandTexture " << index << " from plugin " << plugin; - throw std::runtime_error(error.str()); + return NULL; } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 46c5867ebf..f0d3986414 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -19,7 +20,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" @@ -30,20 +31,10 @@ #include "editmode.hpp" #include "instancemode.hpp" -namespace -{ - static const char * const sMappingSettings[] = - { - "p-navi", "s-navi", - "p-edit", "s-edit", - "select", - 0 - }; -} - CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), - mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false) + mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), + mToolTipPos (-1, -1) { setAcceptDrops(true); @@ -75,22 +66,36 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); - for (int i=0; sMappingSettings[i]; ++i) - { - QString key ("scene-input/"); - key += sMappingSettings[i]; - storeMappingSetting (key, CSMSettings::UserSettings::instance().settingValue (key)); - } + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["3D Scene Input"].update(); + CSMPrefs::get()["Tooltips"].update(); - mDragFactor = CSMSettings::UserSettings::instance().settingValue ("scene-input/drag-factor").toDouble(); - mDragWheelFactor = CSMSettings::UserSettings::instance().settingValue ("scene-input/drag-wheel-factor").toDouble(); - mDragShiftFactor = CSMSettings::UserSettings::instance().settingValue ("scene-input/drag-shift-factor").toDouble(); + mToolTipDelayTimer.setSingleShot (true); + connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); } CSVRender::WorldspaceWidget::~WorldspaceWidget () { } +void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) +{ + if (storeMappingSetting (setting)) + return; + + if (*setting=="3D Scene Input/drag-factor") + mDragFactor = setting->toDouble(); + else if (*setting=="3D Scene Input/drag-wheel-factor") + mDragWheelFactor = setting->toDouble(); + else if (*setting=="3D Scene Input/drag-shift-factor") + mDragShiftFactor = setting->toDouble(); + else if (*setting=="Tooltips/scene-delay") + mToolTipDelay = setting->toInt(); + else if (*setting=="Tooltips/scene") + mShowToolTips = setting->isTrue(); +} + void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) { if (mode=="1st") @@ -283,21 +288,6 @@ unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const return mInteractionMask & getVisibilityMask(); } -void CSVRender::WorldspaceWidget::updateUserSetting (const QString& name, const QStringList& value) -{ - if (!value.isEmpty() && storeMappingSetting (name, value.first())) - return; - - if (name=="scene-input/drag-factor") - mDragFactor = value.at (0).toDouble(); - else if (name=="scene-input/drag-wheel-factor") - mDragWheelFactor = value.at (0).toDouble(); - else if (name=="scene-input/drag-shift-factor") - mDragShiftFactor = value.at (0).toDouble(); - else - dynamic_cast (*mEditMode->getCurrent()).updateUserSetting (name, value); -} - void CSVRender::WorldspaceWidget::setEditLock (bool locked) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (locked); @@ -327,52 +317,87 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { - event->accept(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + + if (mime->fromDocument (mDocument)) + { + if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || + mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || + mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + { + // These drops are handled through the subview object. + event->accept(); + } + else + dynamic_cast (*mEditMode->getCurrent()).dragEnterEvent (event); + } } void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) { - event->accept(); -} + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + if (mime->fromDocument (mDocument)) + { + if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || + mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || + mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + { + // These drops are handled through the subview object. + event->accept(); + } + else + dynamic_cast (*mEditMode->getCurrent()).dragMoveEvent (event); + } +} -bool CSVRender::WorldspaceWidget::storeMappingSetting (const QString& key, const QString& value) +bool CSVRender::WorldspaceWidget::storeMappingSetting (const CSMPrefs::Setting *setting) { - const QString prefix = "scene-input/"; + if (setting->getParent()->getKey()!="3D Scene Input") + return false; - if (key.startsWith (prefix)) + static const char * const sMappingSettings[] = { - QString key2 (key.mid (prefix.length())); + "p-navi", "s-navi", + "p-edit", "s-edit", + "p-select", "s-select", + 0 + }; - for (int i=0; sMappingSettings[i]; ++i) - if (key2==sMappingSettings[i]) - { - Qt::MouseButton button = Qt::NoButton; + for (int i=0; sMappingSettings[i]; ++i) + if (setting->getKey()==sMappingSettings[i]) + { + QString value = QString::fromUtf8 (setting->toString().c_str()); - if (value.endsWith ("Left Mouse-Button")) - button = Qt::LeftButton; - else if (value.endsWith ("Right Mouse-Button")) - button = Qt::RightButton; - else if (value.endsWith ("Middle Mouse-Button")) - button = Qt::MiddleButton; - else - return false; + Qt::MouseButton button = Qt::NoButton; - bool ctrl = value.startsWith ("Ctrl-"); + if (value.endsWith ("Left Mouse-Button")) + button = Qt::LeftButton; + else if (value.endsWith ("Right Mouse-Button")) + button = Qt::RightButton; + else if (value.endsWith ("Middle Mouse-Button")) + button = Qt::MiddleButton; + else + return false; - mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; - return true; - } - } + bool ctrl = value.startsWith ("Ctrl-"); + + mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; + return true; + } return false; } -osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (QMouseEvent *event) +osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos) { // (0,0) is considered the lower left corner of an OpenGL window - int x = event->x(); - int y = height() - event->y(); + int x = localPos.x(); + int y = height() - localPos.y(); osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); @@ -432,8 +457,15 @@ void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) if (mime->fromDocument (mDocument)) { - emit dataDropped(mime->getData()); - } //not handling drops from different documents at the moment + if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || + mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || + mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + { + emit dataDropped(mime->getData()); + } + else + dynamic_cast (*mEditMode->getCurrent()).dropEvent (event); + } } void CSVRender::WorldspaceWidget::runRequest (const std::string& profile) @@ -494,6 +526,20 @@ void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) mDragging = false; } +void CSVRender::WorldspaceWidget::showToolTip() +{ + if (mShowToolTips) + { + QPoint pos = QCursor::pos(); + + if (osg::ref_ptr tag = mousePick (mapFromGlobal (pos))) + { + bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); + QToolTip::showText (pos, tag->getToolTip (hideBasics), this); + } + } +} + void CSVRender::WorldspaceWidget::elementSelectionChanged() { setVisibilityMask (getVisibilityMask()); @@ -509,13 +555,23 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { if (!mDragging) { - if (mDragMode=="p-navi" || mDragMode=="s-navi") + if (mDragMode.empty()) + { + if (event->globalPos()!=mToolTipPos) + { + mToolTipPos = event->globalPos(); + + if (mShowToolTips) + mToolTipDelayTimer.start (mToolTipDelay); + } + } + else if (mDragMode=="p-navi" || mDragMode=="s-navi") { } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="select") + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") { - osg::ref_ptr tag = mousePick (event); + osg::ref_ptr tag = mousePick (event->pos()); EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); @@ -523,8 +579,10 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) mDragging = editMode.primaryEditStartDrag (tag); else if (mDragMode=="s-edit") mDragging = editMode.secondaryEditStartDrag (tag); - else if (mDragMode=="select") - mDragging = editMode.selectStartDrag (tag); + else if (mDragMode=="p-select") + mDragging = editMode.primarySelectStartDrag (tag); + else if (mDragMode=="s-select") + mDragging = editMode.secondarySelectStartDrag (tag); if (mDragging) { @@ -575,7 +633,8 @@ void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="select") + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || + mDragMode=="p-select" || mDragMode=="s-select") { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); @@ -589,9 +648,10 @@ void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { } - else if (button=="p-edit" || button=="s-edit" || button=="select") + else if (button=="p-edit" || button=="s-edit" || + button=="p-select" || button=="s-select") { - osg::ref_ptr tag = mousePick (event); + osg::ref_ptr tag = mousePick (event->pos()); handleMouseClick (tag, button, event->modifiers() & Qt::ShiftModifier); } @@ -647,6 +707,8 @@ void CSVRender::WorldspaceWidget::handleMouseClick (osg::ref_ptr tag, c editMode.primaryEditPressed (tag); else if (button=="s-edit") editMode.secondaryEditPressed (tag); - else if (button=="select") - editMode.selectPressed (tag); + else if (button=="p-select") + editMode.primarySelectPressed (tag); + else if (button=="s-select") + editMode.secondarySelectPressed (tag); } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index c2d61e75be..54376cee45 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -5,12 +5,19 @@ #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" #include "scenewidget.hpp" #include "elements.hpp" +namespace CSMPrefs +{ + class Setting; +} + namespace CSMWorld { class UniversalId; @@ -47,6 +54,10 @@ namespace CSVRender double mDragFactor; double mDragWheelFactor; double mDragShiftFactor; + QTimer mToolTipDelayTimer; + QPoint mToolTipPos; + bool mShowToolTips; + int mToolTipDelay; public: @@ -109,8 +120,6 @@ namespace CSVRender /// marked for interaction. unsigned int getInteractionMask() const; - virtual void updateUserSetting (const QString& name, const QStringList& value); - virtual void setEditLock (bool locked); CSMDoc::Document& getDocument(); @@ -145,9 +154,9 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); /// \return Is \a key a button mapping setting? (ignored otherwise) - bool storeMappingSetting (const QString& key, const QString& value); + bool storeMappingSetting (const CSMPrefs::Setting *setting); - osg::ref_ptr mousePick (QMouseEvent *event); + osg::ref_ptr mousePick (const QPoint& localPos); std::string mapButton (QMouseEvent *event); @@ -155,6 +164,8 @@ namespace CSVRender private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void selectNavigationMode (const std::string& mode); virtual void referenceableDataChanged (const QModelIndex& topLeft, @@ -179,6 +190,8 @@ namespace CSVRender void editModeChanged (const std::string& id); + void showToolTip(); + protected slots: void elementSelectionChanged(); diff --git a/apps/opencs/view/settings/booleanview.cpp b/apps/opencs/view/settings/booleanview.cpp deleted file mode 100644 index 8c759cabb0..0000000000 --- a/apps/opencs/view/settings/booleanview.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include - -#include -#include -#include - -#include - -#include "booleanview.hpp" -#include "../../model/settings/setting.hpp" - -CSVSettings::BooleanView::BooleanView (CSMSettings::Setting *setting, - Page *parent) - : View (setting, parent), mType(setting->type()) -{ - foreach (const QString &value, setting->declaredValues()) - { - QAbstractButton *button = 0; - - switch (mType) - { - case CSMSettings::Type_CheckBox: { - if(mButtons.empty()) // show only one for checkboxes - { - button = new QCheckBox (value, this); - button->setChecked (setting->defaultValues().at(0) == "true" ? true : false); - - // special visual treatment option for checkboxes - if(setting->specialValueText() != "") { - Frame::setTitle(""); - button->setText(setting->specialValueText()); - } - } - } - break; - - case CSMSettings::Type_RadioButton: - button = new QRadioButton (value, this); - break; - - default: - break; - } - - if(button && (mType != CSMSettings::Type_CheckBox || mButtons.empty())) - { - connect (button, SIGNAL (clicked (bool)), - this, SLOT (slotToggled (bool))); - - button->setObjectName (value); - - addWidget (button); - - mButtons[value] = button; - } - } -} - -void CSVSettings::BooleanView::slotToggled (bool state) -{ - //test only for true to avoid multiple selection updates with radiobuttons - if (!isMultiValue() && !state) - return; - - QStringList values; - - foreach (QString key, mButtons.keys()) - { - // checkbox values are true/false unlike radio buttons - if(mType == CSMSettings::Type_CheckBox) - values.append(mButtons.value(key)->isChecked() ? "true" : "false"); - else - { - if (mButtons.value(key)->isChecked()) - values.append (key); - } - } - setSelectedValues (values, false); - - View::updateView(); -} - -void CSVSettings::BooleanView::updateView (bool signalUpdate) const -{ - - QStringList values = selectedValues(); - - foreach (const QString &buttonName, mButtons.keys()) - { - QAbstractButton *button = mButtons[buttonName]; - - //if the value is not found in the list, the widget is checked false - bool buttonValue = values.contains(buttonName); - - //skip if the butotn value will not change - if (button->isChecked() == buttonValue) - continue; - - //disable autoexclusive if it's enabled and we're setting - //the button value to false - bool switchExclusive = (!buttonValue && button->autoExclusive()); - - if (switchExclusive) - button->setAutoExclusive (false); - - button->setChecked (buttonValue); - - if (switchExclusive) - button->setAutoExclusive(true); - } - View::updateView (signalUpdate); -} - -CSVSettings::BooleanView *CSVSettings::BooleanViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new BooleanView (setting, parent); -} diff --git a/apps/opencs/view/settings/booleanview.hpp b/apps/opencs/view/settings/booleanview.hpp deleted file mode 100644 index 53198234a5..0000000000 --- a/apps/opencs/view/settings/booleanview.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CSVSETTINGS_BOOLEANVIEW_HPP -#define CSVSETTINGS_BOOLEANVIEW_HPP - -#include -#include - -#include "view.hpp" -#include "../../model/settings/support.hpp" - -class QStringListModel; - -namespace CSVSettings -{ - class BooleanView : public View - { - Q_OBJECT - - QMap mButtons; - enum CSMSettings::SettingType mType; - - public: - explicit BooleanView (CSMSettings::Setting *setting, - Page *parent); - - protected: - void updateView (bool signalUpdate = true) const; - - private slots: - void slotToggled (bool state); - }; - - class BooleanViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit BooleanViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - BooleanView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_BOOLEANVIEW_HPP diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp deleted file mode 100644 index 38eb7bbc7f..0000000000 --- a/apps/opencs/view/settings/dialog.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "dialog.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../model/settings/usersettings.hpp" - -#include "page.hpp" - - -CSVSettings::Dialog::Dialog(QMainWindow *parent) - : SettingWindow (parent), mStackedWidget (0), mDebugMode (false) -{ - setWindowTitle(QString::fromUtf8 ("User Settings")); - - setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - - setMinimumSize (600, 400); - - setupDialog(); - - connect (mPageListWidget, - SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), - this, - SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); -} - -void CSVSettings::Dialog::slotChangePage - (QListWidgetItem *cur, QListWidgetItem *prev) -{ - mStackedWidget->changePage - (mPageListWidget->row (cur), mPageListWidget->row (prev)); -} - -void CSVSettings::Dialog::setupDialog() -{ - QSplitter *centralWidget = new QSplitter (this); - centralWidget->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - - setCentralWidget (centralWidget); - - buildPageListWidget (centralWidget); - buildStackedWidget (centralWidget); -} - -void CSVSettings::Dialog::buildPages() -{ - SettingWindow::createPages (); - - QFontMetrics fm (QApplication::font()); - - int maxWidth = 1; - - foreach (Page *page, SettingWindow::pages()) - { - maxWidth = std::max (maxWidth, fm.width(page->getLabel())); - - new QListWidgetItem (page->getLabel(), mPageListWidget); - - mStackedWidget->addWidget (page); - } - - mPageListWidget->setMaximumWidth (maxWidth + 10); - - resize (mStackedWidget->sizeHint()); -} - -void CSVSettings::Dialog::buildPageListWidget (QSplitter *centralWidget) -{ - mPageListWidget = new QListWidget (centralWidget); - mPageListWidget->setMinimumWidth(50); - mPageListWidget->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); - - mPageListWidget->setSelectionBehavior (QAbstractItemView::SelectItems); - - centralWidget->addWidget(mPageListWidget); -} - -void CSVSettings::Dialog::buildStackedWidget (QSplitter *centralWidget) -{ - mStackedWidget = new ResizeableStackedWidget (centralWidget); - mStackedWidget->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); - - centralWidget->addWidget (mStackedWidget); -} - -void CSVSettings::Dialog::closeEvent (QCloseEvent *event) -{ - //SettingWindow::closeEvent() must be called first to ensure - //model is updated - SettingWindow::closeEvent (event); - - saveSettings(); -} - -void CSVSettings::Dialog::show() -{ - if (pages().isEmpty()) - { - buildPages(); - setViewValues(); - } - - 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/settings/dialog.hpp b/apps/opencs/view/settings/dialog.hpp deleted file mode 100644 index e3a3f575ac..0000000000 --- a/apps/opencs/view/settings/dialog.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef CSVSETTINGS_DIALOG_H -#define CSVSETTINGS_DIALOG_H - -#include "settingwindow.hpp" -#include "resizeablestackedwidget.hpp" - -class QStackedWidget; -class QListWidget; -class QListWidgetItem; -class QSplitter; - -namespace CSVSettings { - - class Page; - - class Dialog : public SettingWindow - { - Q_OBJECT - - QListWidget *mPageListWidget; - ResizeableStackedWidget *mStackedWidget; - bool mDebugMode; - - public: - - explicit Dialog (QMainWindow *parent = 0); - - protected: - - /// Settings are written on close - void closeEvent (QCloseEvent *event); - - void setupDialog(); - - private: - - void buildPages(); - void buildPageListWidget (QSplitter *centralWidget); - void buildStackedWidget (QSplitter *centralWidget); - - public slots: - - void show(); - - private slots: - - void slotChangePage (QListWidgetItem *, QListWidgetItem *); - }; -} -#endif // CSVSETTINGS_DIALOG_H diff --git a/apps/opencs/view/settings/frame.cpp b/apps/opencs/view/settings/frame.cpp deleted file mode 100644 index 454d3fefa4..0000000000 --- a/apps/opencs/view/settings/frame.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "frame.hpp" - -#include - -const QString CSVSettings::Frame::sInvisibleBoxStyle = - QString::fromUtf8("Frame { border:2px; padding: 2px; margin: 2px;}"); - -CSVSettings::Frame::Frame (bool isVisible, const QString &title, - QWidget *parent) - : QGroupBox (title, parent), mIsHorizontal (true), - mLayout (new SettingLayout()) -{ - setFlat (true); - mVisibleBoxStyle = styleSheet(); - - if (!isVisible) - { - // must be Page, not a View - setStyleSheet (sInvisibleBoxStyle); - } - - setLayout (mLayout); -} - -void CSVSettings::Frame::hideWidgets() -{ - for (int i = 0; i < children().size(); i++) - { - QObject *obj = children().at(i); - - Frame *widgFrame = dynamic_cast (obj); - - if (widgFrame) - { - widgFrame->hideWidgets(); - continue; - } - - QWidget *widg = static_cast (obj); - if (widg->property("sizePolicy").isValid()) - widg->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); - } - - layout()->activate(); - setFixedSize(minimumSizeHint()); - -} - -void CSVSettings::Frame::showWidgets() -{ - for (int i = 0; i < children().size(); i++) - { - QObject *obj = children().at(i); - - Frame *widgFrame = dynamic_cast (obj); - - if (widgFrame) - { - widgFrame->showWidgets(); - continue; - } - - QWidget *widg = static_cast (obj); - - if (widg->property("sizePolicy").isValid()) - { - widg->setSizePolicy - (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - } - } - layout()->activate(); - setFixedSize(minimumSizeHint()); -} - -void CSVSettings::Frame::addWidget (QWidget *widget, int row, int column, - int rowSpan, int columnSpan) -{ - if (row == -1) - row = getNextRow(); - - if (column == -1) - column = getNextColumn(); - - mLayout->addWidget (widget, row, column, rowSpan, columnSpan); - //, Qt::AlignLeft | Qt::AlignTop); - - widget->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored); -} - -int CSVSettings::Frame::getNextRow () const -{ - int row = mLayout->rowCount(); - - if (mIsHorizontal && row > 0) - row--; - - return row; -} - -int CSVSettings::Frame::getNextColumn () const -{ - int column = 0; - - if (mIsHorizontal) - column = mLayout->columnCount(); - - return column; -} diff --git a/apps/opencs/view/settings/frame.hpp b/apps/opencs/view/settings/frame.hpp deleted file mode 100644 index bbb92f34f7..0000000000 --- a/apps/opencs/view/settings/frame.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef CSVSETTINGS_FRAME_HPP -#define CSVSETTINGS_FRAME_HPP - -#include -#include -#include -#include "../../model/settings/support.hpp" - -namespace CSVSettings -{ - class SettingLayout : public QGridLayout - { - public: - explicit SettingLayout (QWidget *parent = 0) - : QGridLayout (parent) - { - setContentsMargins(0,0,0,0); - setAlignment(Qt::AlignLeft | Qt::AlignTop); - } - }; - - /// Custom implementation of QGroupBox to act as a base for view classes - class Frame : public QGroupBox - { - static const QString sInvisibleBoxStyle; - - QString mVisibleBoxStyle; - - bool mIsHorizontal; - - SettingLayout *mLayout; - - public: - explicit Frame (bool isVisible, const QString &title = "", - QWidget *parent = 0); - - ///Adds a widget to the grid layout, setting the position - ///relative to the last added widgets, or absolutely for positive - ///row / column values - void addWidget (QWidget *widget, int row = -1, int column = -1, - int rowSpan = 1, int columnSpan = 1); - - ///Force the grid to lay out in horizontal or vertical alignments - void setHLayout() { mIsHorizontal = true; } - void setVLayout() { mIsHorizontal = false; } - - ///show / hide widgets (when stacked widget page changes) - void showWidgets(); - void hideWidgets(); - - private: - - ///functions which return the index for the next layout row / column - int getNextColumn() const; - int getNextRow() const; - - }; -} - -#endif // CSVSETTINGS_FRAME_HPP diff --git a/apps/opencs/view/settings/listview.cpp b/apps/opencs/view/settings/listview.cpp deleted file mode 100644 index 0876b39820..0000000000 --- a/apps/opencs/view/settings/listview.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "listview.hpp" -#include "../../model/settings/setting.hpp" - -#include -#include -#include - -CSVSettings::ListView::ListView(CSMSettings::Setting *setting, - Page *parent) - : View(setting, parent), mAbstractItemView (0), mComboBox (0) -{ - QWidget *widget = - buildWidget(setting->isMultiLine(), setting->widgetWidth()); - - addWidget (widget, setting->viewRow(), setting->viewColumn()); - - if (mComboBox) - buildComboBoxModel(); - - else if (mAbstractItemView) - buildAbstractItemViewModel(); -} - -void CSVSettings::ListView::buildComboBoxModel() -{ - mComboBox->setModel (dataModel()); - mComboBox->setModelColumn (0); - mComboBox->view()->setSelectionModel (selectionModel()); - - int curIdx = -1; - - if (!selectionModel()->selection().isEmpty()) - curIdx = selectionModel()->selectedIndexes().at(0).row(); - - mComboBox->setCurrentIndex (curIdx); - - connect (mComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(emitItemViewUpdate(int))); -} - -void CSVSettings::ListView::buildAbstractItemViewModel() -{ - mAbstractItemView->setModel (dataModel()); - mAbstractItemView->setSelectionModel (selectionModel()); - - //connection needs to go here for list view update to signal to - //the outside -} - -void CSVSettings::ListView::emitItemViewUpdate (int idx) -{ - updateView(); -} - -QWidget *CSVSettings::ListView::buildWidget(bool isMultiLine, int width) -{ - QWidget *widget = 0; - - if (isMultiLine) - { - mAbstractItemView = new QListView (this); - widget = mAbstractItemView; - - if (width > 0) - widget->setFixedWidth (widgetWidth (width)); - } - else - { - mComboBox = new QComboBox (this); - widget = mComboBox; - - if (width > 0) - mComboBox->setMinimumContentsLength (width); - } - - return widget; -} - -void CSVSettings::ListView::showEvent ( QShowEvent * event ) -{ - View::showEvent (event); -} - -void CSVSettings::ListView::updateView (bool signalUpdate) const -{ - QStringList values = selectedValues(); - - if (mComboBox) - { - int idx = -1; - - if (values.size() > 0) - idx = (mComboBox->findText(values.at(0))); - - mComboBox->setCurrentIndex (idx); - } - - View::updateView (signalUpdate); -} - -CSVSettings::ListView *CSVSettings::ListViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new ListView(setting, parent); -} diff --git a/apps/opencs/view/settings/listview.hpp b/apps/opencs/view/settings/listview.hpp deleted file mode 100644 index c2860d769a..0000000000 --- a/apps/opencs/view/settings/listview.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef CSVSETTINGS_LISTVIEW_HPP -#define CSVSETTINGS_LISTVIEW_HPP - -#include "view.hpp" - - -class QStringListModel; -class QComboBox; -class QAbstractItemView; - -namespace CSVSettings -{ - class ListView : public View - { - Q_OBJECT - - QAbstractItemView *mAbstractItemView; - QComboBox *mComboBox; - - public: - explicit ListView (CSMSettings::Setting *setting, - Page *parent); - - protected: - - void updateView (bool signalUpdate = true) const; - void showEvent ( QShowEvent * event ); - - ///Receives signal from widget and signals viwUpdated() - void slotTextEdited (QString value); - - private: - - ///Helper function to construct a model for an AbstractItemView - void buildAbstractItemViewModel(); - - ///Helper function to construct a model for a combobox - void buildComboBoxModel(); - - ///Helper function to build the view widget - QWidget *buildWidget (bool isMultiLine, int width); - - private slots: - - ///Receives updates from single-select widgets (like combobox) and - ///signals viewUpdated with the selected values. - void emitItemViewUpdate (int idx); - }; - - class ListViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit ListViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - ListView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_LISTVIEW_HPP diff --git a/apps/opencs/view/settings/page.cpp b/apps/opencs/view/settings/page.cpp deleted file mode 100644 index c009cdd7a5..0000000000 --- a/apps/opencs/view/settings/page.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "page.hpp" - -#include - -#include "view.hpp" -#include "booleanview.hpp" -#include "textview.hpp" -#include "listview.hpp" -#include "rangeview.hpp" - -#include "../../model/settings/usersettings.hpp" -#include "../../model/settings/connector.hpp" -#include "../../model/settings/support.hpp" - -#include "settingwindow.hpp" - -QMap - CSVSettings::Page::mViewFactories; - -CSVSettings::Page::Page (const QString &pageName, QList settingList, - SettingWindow *parent, const QString& label) -: Frame(false, "", parent), mParent(parent), mIsEditorPage (false), mLabel (label) -{ - setObjectName (pageName); - - if (mViewFactories.size() == 0) - buildFactories(); - - setVLayout(); - setupViews (settingList); -} - -void CSVSettings::Page::setupViews - (QList &settingList) -{ - foreach (CSMSettings::Setting *setting, settingList) - addView (setting); -} - -void CSVSettings::Page::addView (CSMSettings::Setting *setting) -{ - if (setting->viewType() == ViewType_Undefined) - { - if(setting->specialValueText() != "") - { - // hack to put a label - addWidget(new QLabel(setting->specialValueText()), - setting->viewRow(), setting->viewColumn(), - setting->rowSpan(), setting->columnSpan()); - return; - } - else - return; - } - - View *view = mViewFactories[setting->viewType()]->createView(setting, this); - - if (!view) - return; - - mViews.append (view); - - addWidget (view, setting->viewRow(), setting->viewColumn(), - setting->rowSpan(), setting->columnSpan() ); - - //if this page is an editor page, connect each of it's views up to the - //UserSettings singleton for signaling back to OpenCS - if (setting->isEditorSetting()) { - connect (view, SIGNAL (viewUpdated(const QString&, const QStringList&)), - &CSMSettings::UserSettings::instance(), - SLOT (updateUserSetting (const QString &, const QStringList &))); - } -} - -CSVSettings::View *CSVSettings::Page::findView (const QString &page, - const QString &setting) const -{ - - //if this is not the page we're looking for, - //appeal to the parent setting window to find the appropriate view - if (page != objectName()) - return mParent->findView (page, setting); - - //otherwise, return the matching view - for (int i = 0; i < mViews.size(); i++) - { - View *view = mViews.at(i); - - if (view->parentPage()->objectName() != page) - continue; - - if (view->objectName() == setting) - return view; - } - - return 0; -} - -void CSVSettings::Page::buildFactories() -{ - mViewFactories[ViewType_Boolean] = new BooleanViewFactory (this); - mViewFactories[ViewType_Text] = new TextViewFactory (this); - mViewFactories[ViewType_List] = new ListViewFactory (this); - mViewFactories[ViewType_Range] = new RangeViewFactory (this); -} - -QString CSVSettings::Page::getLabel() const -{ - return mLabel; -} diff --git a/apps/opencs/view/settings/page.hpp b/apps/opencs/view/settings/page.hpp deleted file mode 100644 index caf2eec3fe..0000000000 --- a/apps/opencs/view/settings/page.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef CSVSETTINGS_PAGE_HPP -#define CSVSETTINGS_PAGE_HPP - -#include -#include -#include - -#include "frame.hpp" - -#include "../../model/settings/support.hpp" - -namespace CSMSettings { class Setting; } - -namespace CSVSettings -{ - class View; - class IViewFactory; - class SettingWindow; - - class Page : public Frame - { - Q_OBJECT - - QList mViews; - SettingWindow *mParent; - static QMap mViewFactories; - bool mIsEditorPage; - QString mLabel; - - public: - Page (const QString &pageName, QList settingList, - SettingWindow *parent, const QString& label); - - ///Creates a new view based on the passed setting and adds it to - ///the page. - void addView (CSMSettings::Setting *setting); - - ///Iterates the views created for this page based on the passed setting - ///and returns it. - View *findView (const QString &page, const QString &setting) const; - - ///returns the list of views associated with the page - const QList &views () const { return mViews; } - - QString getLabel() const; - - private: - - ///Creates views based on the passed setting list - void setupViews (QList &settingList); - - ///Creates factory objects for view construction - void buildFactories(); - }; -} -#endif // CSVSETTINGS_PAGE_HPP diff --git a/apps/opencs/view/settings/rangeview.cpp b/apps/opencs/view/settings/rangeview.cpp deleted file mode 100644 index 5893c5d0da..0000000000 --- a/apps/opencs/view/settings/rangeview.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "rangeview.hpp" -#include "spinbox.hpp" -#include "../../model/settings/setting.hpp" -#include "../../model/settings/support.hpp" - -CSVSettings::RangeView::RangeView (CSMSettings::Setting *setting, - Page *parent) - : View (setting, parent), mRangeWidget (0), mRangeType (setting->type()) -{ - - mRangeWidget = 0; - - if (isMultiValue()) - return; - - switch (mRangeType) - { - case CSMSettings::Type_SpinBox: - case CSMSettings::Type_DoubleSpinBox: - buildSpinBox (setting); - break; - - case CSMSettings::Type_Dial: - case CSMSettings::Type_Slider: - buildSlider (setting); - break; - - default: - break; - } - - if(mRangeWidget) - { - mRangeWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); - mRangeWidget->setObjectName (setting->name()); - } - - addWidget (mRangeWidget); -} - -void CSVSettings::RangeView::buildSlider (CSMSettings::Setting *setting) -{ - switch (setting->type()) - { - case CSMSettings::Type_Slider: - mRangeWidget = new QSlider (Qt::Horizontal, this); - mRangeWidget->setProperty ("tickInterval", setting->tickInterval()); - - if (setting->ticksAbove()) - { - if (setting->ticksBelow()) - mRangeWidget->setProperty ("tickPosition", QSlider::TicksBothSides); - else - mRangeWidget->setProperty ("tickPosition", QSlider::TicksAbove); - } - else if (setting->ticksBelow()) - mRangeWidget->setProperty ("tickPosition", QSlider::TicksBelow); - else - mRangeWidget->setProperty ("tickPosition", QSlider::NoTicks); - - break; - - case CSMSettings::Type_Dial: - mRangeWidget = new QDial (this); - mRangeWidget->setProperty ("wrapping", setting->wrapping()); - mRangeWidget->setProperty ("notchesVisible", - (setting->ticksAbove() || setting->ticksBelow())); - break; - - default: - break; - } - - if(mRangeWidget) - { - mRangeWidget->setProperty ("minimum", setting->minimum()); - mRangeWidget->setProperty ("maximum", setting->maximum()); - mRangeWidget->setProperty ("tracking", false); - mRangeWidget->setProperty ("singleStep", setting->singleStep()); - - connect (mRangeWidget, SIGNAL (valueChanged (int)), - this, SLOT (slotUpdateView (int))); - } -} - -void CSVSettings::RangeView::buildSpinBox (CSMSettings::Setting *setting) -{ - SpinBox *sb = 0; - - switch (setting->type()) - { - case CSMSettings::Type_SpinBox: - - sb = new SpinBox (this); - - if (!setting->declaredValues().isEmpty()) - sb->setValueList (setting->declaredValues()); - - mRangeWidget = sb; - - connect (mRangeWidget, SIGNAL (valueChanged (int)), - this, SLOT (slotUpdateView (int))); - break; - - case CSMSettings::Type_DoubleSpinBox: - mRangeWidget = new QDoubleSpinBox (this); - - connect (mRangeWidget, SIGNAL (valueChanged (double)), - this, SLOT (slotUpdateView (double))); - break; - - default: - return; - } - - //min / max values are set automatically in AlphaSpinBox - if (setting->declaredValues().isEmpty()) - { - mRangeWidget->setProperty ("minimum", setting->minimum()); - mRangeWidget->setProperty ("maximum", setting->maximum()); - mRangeWidget->setProperty ("singleStep", setting->singleStep()); - } - - mRangeWidget->setProperty ("prefix", setting->prefix()); - mRangeWidget->setProperty ("suffix", setting->suffix()); - mRangeWidget->setProperty ("wrapping", setting->wrapping()); - dynamic_cast (mRangeWidget)->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - - if(setting->type() == CSMSettings::Type_SpinBox && setting->declaredValues().isEmpty()) - dynamic_cast (mRangeWidget)->setValue (setting->defaultValues().at(0).toInt()); -} - -void CSVSettings::RangeView::slotUpdateView (int value) -{ - QString textValue = ""; - QStringList list; - - switch (mRangeType) - { - case CSMSettings::Type_SpinBox: - list = static_cast (mRangeWidget)->valueList(); - if (!list.isEmpty()) - textValue = list.at(value); - break; - - default: - break; - } - - if (textValue.isEmpty()) - textValue = QVariant (value).toString(); - - setSelectedValue (textValue, false); - - View::updateView(); -} - -void CSVSettings::RangeView::slotUpdateView (double value) -{ - setSelectedValue (QVariant(value).toString(), false); - - View::updateView(); -} - -void CSVSettings::RangeView::updateView (bool signalUpdate) const -{ - QString value; - - if (!selectedValues().isEmpty()) - value = selectedValues().at(0); - - switch (mRangeType) - { - case CSMSettings::Type_SpinBox: - static_cast (mRangeWidget)->setValue (value); - break; - - case CSMSettings::Type_DoubleSpinBox: - static_cast (mRangeWidget)->setValue (value.toDouble()); - break; - - case CSMSettings::Type_Slider: - case CSMSettings::Type_Dial: - mRangeWidget->setProperty ("value", value.toInt()); - mRangeWidget->setProperty ("sliderPosition", value.toInt()); - break; - - default: - break; - - } - - View::updateView (signalUpdate); -} - -CSVSettings::RangeView *CSVSettings::RangeViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new RangeView (setting, parent); -} diff --git a/apps/opencs/view/settings/rangeview.hpp b/apps/opencs/view/settings/rangeview.hpp deleted file mode 100644 index 2ab343f1f9..0000000000 --- a/apps/opencs/view/settings/rangeview.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef CSVSETTINGS_RANGEVIEW_HPP -#define CSVSETTINGS_RANGEVIEW_HPP - -#include "view.hpp" -#include "../../model/settings/support.hpp" - -class QStringListModel; -class QAbstractSpinBox; - -namespace CSVSettings -{ - class RangeView : public View - { - Q_OBJECT - - QWidget *mRangeWidget; - CSMSettings::SettingType mRangeType; - - public: - explicit RangeView (CSMSettings::Setting *setting, - Page *parent); - - protected: - - ///virtual function called through View - void updateView (bool signalUpdate = true) const; - - ///construct a slider-based view - void buildSlider (CSMSettings::Setting *setting); - - ///construct a spinbox-based view - void buildSpinBox (CSMSettings::Setting *setting); - - private slots: - - ///responds to valueChanged signals - void slotUpdateView (int value); - void slotUpdateView (double value); - - }; - - class RangeViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit RangeViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - RangeView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_RANGEVIEW_HPP diff --git a/apps/opencs/view/settings/resizeablestackedwidget.cpp b/apps/opencs/view/settings/resizeablestackedwidget.cpp deleted file mode 100644 index 0e87a25062..0000000000 --- a/apps/opencs/view/settings/resizeablestackedwidget.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "resizeablestackedwidget.hpp" -#include "page.hpp" - -#include - -CSVSettings::ResizeableStackedWidget::ResizeableStackedWidget(QWidget *parent) : - QStackedWidget(parent) -{} - -void CSVSettings::ResizeableStackedWidget::addWidget(QWidget* pWidget) -{ - QStackedWidget::addWidget(pWidget); -} - -void CSVSettings::ResizeableStackedWidget::changePage - (int current, int previous) -{ - if (current == previous) - return; - - Page *prevPage = 0; - Page *curPage = 0; - - if (previous > -1) - prevPage = static_cast (widget (previous)); - - if (current > -1) - curPage = static_cast (widget (current)); - - if (prevPage) - prevPage->hideWidgets(); - - if (curPage) - curPage->showWidgets(); - - layout()->activate(); - - setCurrentIndex (current); -} diff --git a/apps/opencs/view/settings/resizeablestackedwidget.hpp b/apps/opencs/view/settings/resizeablestackedwidget.hpp deleted file mode 100644 index 2d0c71a23a..0000000000 --- a/apps/opencs/view/settings/resizeablestackedwidget.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP -#define CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP - -#include - -class QListWidgetItem; - -namespace CSVSettings -{ - class ResizeableStackedWidget : public QStackedWidget - { - Q_OBJECT - - public: - explicit ResizeableStackedWidget(QWidget *parent = 0); - - ///add a widget to the stacked widget - void addWidget(QWidget* pWidget); - - ///called whenever the stacked widget page is changed - void changePage (int, int); - }; -} - -#endif // CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP diff --git a/apps/opencs/view/settings/settingwindow.cpp b/apps/opencs/view/settings/settingwindow.cpp deleted file mode 100644 index 76ea9dc4fe..0000000000 --- a/apps/opencs/view/settings/settingwindow.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include -#include - -#include "../../model/settings/setting.hpp" -#include "../../model/settings/connector.hpp" -#include "../../model/settings/usersettings.hpp" -#include "settingwindow.hpp" -#include "page.hpp" -#include "view.hpp" - -CSVSettings::SettingWindow::SettingWindow(QWidget *parent) - : QMainWindow(parent), mModel(NULL) -{} - -void CSVSettings::SettingWindow::createPages() -{ - CSMSettings::SettingPageMap pageMap = mModel->settingPageMap(); - - QList connectedSettings; - - foreach (const QString &pageName, pageMap.keys()) - { - QList pageSettings = pageMap.value (pageName).second; - - mPages.append (new Page (pageName, pageSettings, this, pageMap.value (pageName).first)); - - for (int i = 0; i < pageSettings.size(); i++) - { - CSMSettings::Setting *setting = pageSettings.at(i); - - if (!setting->proxyLists().isEmpty()) - connectedSettings.append (setting); - } - } - - if (!connectedSettings.isEmpty()) - createConnections(connectedSettings); -} - -void CSVSettings::SettingWindow::createConnections - (const QList &list) -{ - foreach (const CSMSettings::Setting *setting, list) - { - View *masterView = findView (setting->page(), setting->name()); - - CSMSettings::Connector *connector = - new CSMSettings::Connector (masterView, this); - - connect (masterView, - SIGNAL (viewUpdated(const QString &, const QStringList &)), - connector, - SLOT (slotUpdateSlaves()) - ); - - const CSMSettings::ProxyValueMap &proxyMap = setting->proxyLists(); - - foreach (const QString &key, proxyMap.keys()) - { - QStringList keyPair = key.split('/'); - - if (keyPair.size() != 2) - continue; - - View *slaveView = findView (keyPair.at(0), keyPair.at(1)); - - if (!slaveView) - { - qWarning () << "Unable to create connection for view " - << key; - continue; - } - - QList proxyList = proxyMap.value (key); - connector->addSlaveView (slaveView, proxyList); - - connect (slaveView, - SIGNAL (viewUpdated(const QString &, const QStringList &)), - connector, - SLOT (slotUpdateMaster())); - } - } -} - -void CSVSettings::SettingWindow::setViewValues() -{ - //iterate each page and view, setting their definitions - //if they exist in the model - foreach (const Page *page, mPages) - { - foreach (const View *view, page->views()) - { - //testing beforehand prevents overwriting a proxy setting - if (!mModel->hasSettingDefinitions (view->viewKey())) - continue; - - QStringList defs = mModel->definitions (view->viewKey()); - - view->setSelectedValues(defs); - } - } -} - -CSVSettings::View *CSVSettings::SettingWindow::findView - (const QString &pageName, const QString &setting) -{ - foreach (const Page *page, mPages) - { - if (page->objectName() == pageName) - return page->findView (pageName, setting); - } - return 0; -} - -void CSVSettings::SettingWindow::saveSettings() -{ - //setting the definition in the model automatically syncs with the file - foreach (const Page *page, mPages) - { - foreach (const View *view, page->views()) - { - if (!view->serializable()) - continue; - - mModel->setDefinitions (view->viewKey(), view->selectedValues()); - } - } - - mModel->saveDefinitions(); -} - diff --git a/apps/opencs/view/settings/settingwindow.hpp b/apps/opencs/view/settings/settingwindow.hpp deleted file mode 100644 index 11bceee96b..0000000000 --- a/apps/opencs/view/settings/settingwindow.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef CSVSETTINGS_SETTINGWINDOW_HPP -#define CSVSETTINGS_SETTINGWINDOW_HPP - -#include -#include - -#include "../../model/settings/support.hpp" - -namespace CSMSettings { - class Setting; - class UserSettings; -} - -namespace CSVSettings { - - class Page; - class View; - - typedef QList PageList; - - class SettingWindow : public QMainWindow - { - Q_OBJECT - - PageList mPages; - CSMSettings::UserSettings *mModel; - - public: - explicit SettingWindow(QWidget *parent = 0); - - ///retrieve a reference to a view based on it's page and setting name - View *findView (const QString &pageName, const QString &setting); - - ///set the model the view uses (instance of UserSettings) - void setModel (CSMSettings::UserSettings &model) { mModel = &model; } - - protected: - - ///construct the pages to be displayed in the dialog - void createPages(); - - ///return the list of constructed pages - const PageList &pages() const { return mPages; } - - ///save settings from the GUI to file - void saveSettings(); - - ///sets the defined values for the views that have been created - void setViewValues(); - - private: - - ///create connections between settings (used for proxy settings) - void createConnections (const QList &list); - }; -} - -#endif // CSVSETTINGS_SETTINGWINDOW_HPP diff --git a/apps/opencs/view/settings/spinbox.cpp b/apps/opencs/view/settings/spinbox.cpp deleted file mode 100644 index 043107bb76..0000000000 --- a/apps/opencs/view/settings/spinbox.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "spinbox.hpp" - -#include - -CSVSettings::SpinBox::SpinBox(QWidget *parent) - : QSpinBox(parent), mValueList(QStringList()) -{ - setRange (0, 0); -} - -QString CSVSettings::SpinBox::textFromValue(int val) const -{ - if (mValueList.isEmpty()) - return QVariant (val).toString(); - - QString value; - - if (val < mValueList.size()) - value = mValueList.at (val); - - return value; -} - -int CSVSettings::SpinBox::valueFromText(const QString &text) const -{ - if (mValueList.isEmpty()) - return text.toInt(); // TODO: assumed integer, untested error handling for alpha types - - if (mValueList.contains (text)) - return mValueList.indexOf(text); - - return -1; -} - -void CSVSettings::SpinBox::setValue (const QString &value) -{ - if (!mValueList.isEmpty()) - { - lineEdit()->setText (value); - QSpinBox::setValue(valueFromText(value)); - } - else - QSpinBox::setValue (value.toInt()); -} - -void CSVSettings::SpinBox::setValueList (const QStringList &list) -{ - mValueList = list; - setMaximum (list.size() - 1); -} diff --git a/apps/opencs/view/settings/spinbox.hpp b/apps/opencs/view/settings/spinbox.hpp deleted file mode 100644 index e887e8c937..0000000000 --- a/apps/opencs/view/settings/spinbox.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CSVSETTINGS_SPINBOX_HPP -#define CSVSETTINGS_SPINBOX_HPP - -#include -#include -#include - -namespace CSVSettings -{ - class SpinBox : public QSpinBox - { - Q_OBJECT - - QStringList mValueList; - - public: - explicit SpinBox(QWidget *parent = 0); - - ///set the value displayed in the spin box - void setValue (const QString &value); - - ///set the stringlist that's used as a list of pre-defined values - ///to be displayed as the user scrolls - void setValueList (const QStringList &list); - - ///returns the pre-defined value list. - const QStringList &valueList() const { return mValueList; } - - protected: - - ///converts an index value to corresponding text to be displayed - QString textFromValue (int val) const; - - ///converts a text value to a corresponding index - int valueFromText (const QString &text) const; - }; -} -#endif // CSVSETTINGS_SPINBOX_HPP diff --git a/apps/opencs/view/settings/textview.cpp b/apps/opencs/view/settings/textview.cpp deleted file mode 100644 index a6ab657fe2..0000000000 --- a/apps/opencs/view/settings/textview.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include - -#include "textview.hpp" -#include "../../model/settings/setting.hpp" - -CSVSettings::TextView::TextView(CSMSettings::Setting *setting, Page *parent) - : View (setting, parent), mDelimiter (setting->delimiter()) - -{ - if (setting->isMultiLine()) - mTextWidget = new QTextEdit ("", this); - else - mTextWidget = new QLineEdit ("", this); - - if (setting->widgetWidth() > 0) - mTextWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); - - connect (mTextWidget, SIGNAL (textEdited (QString)), - this, SLOT (slotTextEdited (QString))); - - addWidget (mTextWidget, setting->viewRow(), setting->viewColumn()); -} - -bool CSVSettings::TextView::isEquivalent - (const QString &lhs, const QString &rhs) const -{ - return (lhs.trimmed() == rhs.trimmed()); -} - -void CSVSettings::TextView::slotTextEdited (QString value) -{ - QStringList values = value.split (mDelimiter, QString::SkipEmptyParts); - - QStringList returnValues; - - foreach (const QString &splitValue, values) - returnValues.append (splitValue.trimmed()); - - setSelectedValues (returnValues, false); - - View::updateView(); -} - -void CSVSettings::TextView::updateView(bool signalUpdate) const -{ - QString values = selectedValues().join (mDelimiter); - - if (isEquivalent (mTextWidget->property("text").toString(), values)) - return; - - mTextWidget->setProperty("text", values); - - View::updateView (signalUpdate); -} - -CSVSettings::TextView *CSVSettings::TextViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new TextView (setting, parent); -} - diff --git a/apps/opencs/view/settings/textview.hpp b/apps/opencs/view/settings/textview.hpp deleted file mode 100644 index f4cd03d2f6..0000000000 --- a/apps/opencs/view/settings/textview.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef CSVSETTINGS_TEXTVIEW_HPP -#define CSVSETTINGS_TEXTVIEW_HPP - -#include "view.hpp" -#include "../../model/settings/setting.hpp" - -namespace CSVSettings -{ - class TextView : public View - { - Q_OBJECT - - QWidget *mTextWidget; - - QString mDelimiter; - - public: - explicit TextView (CSMSettings::Setting *setting, - Page *parent = 0); - - protected: - - /// virtual function called through View - void updateView (bool signalUpdate = true) const; - - protected slots: - - ///Receives updates to the widget for signalling - void slotTextEdited (QString value); - - private: - - ///Comparison function that returns true if the trimmed() strings - ///are equal - bool isEquivalent (const QString &lhs, const QString &rhs) const; - }; - - class TextViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit TextViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - TextView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_TEXTVIEW_HPP diff --git a/apps/opencs/view/settings/view.cpp b/apps/opencs/view/settings/view.cpp deleted file mode 100644 index 21cf55fddd..0000000000 --- a/apps/opencs/view/settings/view.cpp +++ /dev/null @@ -1,222 +0,0 @@ -#include -#include -#include -#include -#include - -#include "view.hpp" -#include "../../model/settings/support.hpp" -#include "../../model/settings/setting.hpp" -#include "page.hpp" - -CSVSettings::View::View(CSMSettings::Setting *setting, - Page *parent) - - : Frame(true, setting->getLabel(), parent), - mParentPage (parent), mDataModel(0), - mHasFixedValues (!setting->declaredValues().isEmpty()), - mIsMultiValue (setting->isMultiValue()), - mViewKey (setting->page() + '/' + setting->name()), - mSerializable (setting->serializable()) -{ - if (!setting->getToolTip().isEmpty()) - setToolTip (setting->getToolTip()); - - setObjectName (setting->name()); - buildView(); - buildModel (setting); - // apply stylesheet to view's frame if exists - if(setting->styleSheet() != "") - Frame::setStyleSheet (setting->styleSheet()); -} - -void CSVSettings::View::buildModel (const CSMSettings::Setting *setting) -{ - QStringList values = setting->defaultValues(); - - if (mHasFixedValues) - buildFixedValueModel (setting->declaredValues()); - else - buildUpdatableValueModel (values); - - mSelectionModel = new QItemSelectionModel (mDataModel, this); - - setSelectedValues (values, false); -} - -void CSVSettings::View::buildFixedValueModel (const QStringList &values) -{ - //fixed value models are simple string list models, since they are read-only - mDataModel = new QStringListModel (values, this); -} - -void CSVSettings::View::buildUpdatableValueModel (const QStringList &values) -{ - //updateable models are standard item models because they support - //replacing entire columns - QList itemList; - - foreach (const QString &value, values) - itemList.append (new QStandardItem(value)); - - QStandardItemModel *model = new QStandardItemModel (this); - model->appendColumn (itemList); - - mDataModel = model; -} - -void CSVSettings::View::buildView() -{ - setFlat (true); - setHLayout(); -} - -int CSVSettings::View::currentIndex () const -{ - if (selectedValues().isEmpty()) - return -1; - - QString currentValue = selectedValues().at(0); - - for (int i = 0; i < mDataModel->rowCount(); i++) - if (value(i) == currentValue) - return i; - - return -1; -} - -void CSVSettings::View::refresh() const -{ - select (mSelectionModel->selection()); - updateView(); -} - -int CSVSettings::View::rowCount() const -{ - return mDataModel->rowCount(); -} - -void CSVSettings::View::select (const QItemSelection &selection) const -{ - mSelectionModel->clear(); - mSelectionModel->select(selection, QItemSelectionModel::Select); -} - -QStringList CSVSettings::View::selectedValues() const -{ - QStringList selValues; - - foreach (const QModelIndex &idx, mSelectionModel->selectedIndexes()) - selValues.append (value(idx.row())); - - return selValues; -} - -void CSVSettings::View::setSelectedValue (const QString &value, - bool doViewUpdate, bool signalUpdate) -{ - setSelectedValues (QStringList() << value, doViewUpdate, signalUpdate); -} - -void CSVSettings::View::setSelectedValues (const QStringList &list, - bool doViewUpdate, bool signalUpdate) const -{ - QItemSelection selection; - - if (stringListsMatch (list, selectedValues())) - return; - - if (!mHasFixedValues) - { - QStandardItemModel *model = - static_cast (mDataModel); - - model->clear(); - model->appendColumn (toStandardItemList (list)); - - for (int i = 0; i < model->rowCount(); i++) - { - QModelIndex idx = model->index(i, 0); - selection.append (QItemSelectionRange (idx, idx)); - } - } - else - { - for (int i = 0; i < mDataModel->rowCount(); i++) - { - if (list.contains(value(i))) - { - QModelIndex idx = mDataModel->index(i, 0); - selection.append(QItemSelectionRange (idx, idx)); - } - } - } - select (selection); - - //update the view if the selection was set from the model side, not by the - //user - if (doViewUpdate) - updateView (signalUpdate); -} - -void CSVSettings::View::showEvent ( QShowEvent * event ) -{ - refresh(); -} - -bool CSVSettings::View::stringListsMatch ( - const QStringList &list1, - const QStringList &list2) const -{ - //returns a "sloppy" match, verifying that each list contains all the same - //items, though not necessarily in the same order. - - if (list1.size() != list2.size()) - return false; - - QStringList tempList(list2); - - //iterate each value in the list, removing one occurrence of the value in - //the other list. If no corresponding value is found, test fails - foreach (const QString &value, list1) - { - if (!tempList.contains(value)) - return false; - - tempList.removeOne(value); - } - return true; -} - -QList CSVSettings::View::toStandardItemList - (const QStringList &list) const -{ - QList itemList; - - foreach (const QString &value, list) - itemList.append (new QStandardItem (value)); - - return itemList; -} - -void CSVSettings::View::updateView (bool signalUpdate) const -{ - if (signalUpdate) - emit viewUpdated(viewKey(), selectedValues()); -} - -QString CSVSettings::View::value (int row) const -{ - if (row > -1 && row < mDataModel->rowCount()) - return mDataModel->data (mDataModel->index(row, 0)).toString(); - - return QString(); -} - -int CSVSettings::View::widgetWidth(int characterCount) const -{ - QString widthToken = QString().fill ('m', characterCount); - QFontMetrics fm (QApplication::font()); - - return (fm.width (widthToken)); -} diff --git a/apps/opencs/view/settings/view.hpp b/apps/opencs/view/settings/view.hpp deleted file mode 100644 index 84ad62759e..0000000000 --- a/apps/opencs/view/settings/view.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef CSVSETTINGS_VIEW_HPP -#define CSVSETTINGS_VIEW_HPP - -#include -#include - -#include "frame.hpp" -#include "../../model/settings/support.hpp" - -class QGroupBox; -class QStringList; -class QStandardItem; -class QItemSelection; -class QAbstractItemModel; -class QItemSelectionModel; - -namespace CSMSettings { class Setting; } - -namespace CSVSettings -{ - class Page; - - class View : public Frame - { - Q_OBJECT - - ///Pointer to the owning Page instance - Page *mParentPage; - - ///Pointer to the selection model for the view - QItemSelectionModel *mSelectionModel; - - ///Pointer to the data model for the view's selection model - QAbstractItemModel *mDataModel; - - ///State indicating whether or not the setting has a pre-defined list - ///of values, limiting possible definitions - bool mHasFixedValues; - - ///State indicating whether the view will allow multiple values - bool mIsMultiValue; - - ///'pagename.settingname' form of the view's id - QString mViewKey; - - ///indicates whether or not the setting is written to file - bool mSerializable; - - public: - - explicit View (CSMSettings::Setting *setting, Page *parent); - - ///Returns the index / row of the passed value, -1 if not found. - int currentIndex () const; - - ///Returns the number of rows in the view's data model - int rowCount() const; - - ///Returns bool indicating the data in this view should / should not - ///be serialized to a config file - bool serializable() const { return mSerializable; } - - ///Returns a pointer to the view's owning parent page - const Page *parentPage() const { return mParentPage; } - - ///Returns the selected items in the selection model as a QStringList - QStringList selectedValues() const; - - ///Sets the selected items in the selection model based on passed list. - ///Bools allow opt-out of updating the view - ///or signaling the view was updatedto avoid viscious cylcing. - void setSelectedValues (const QStringList &values, - bool updateView = true, - bool signalUpdate = true) const; - - void setSelectedValue (const QString &value, - bool updateView = true, - bool signalUpdate = true); - - - ///Returns the value of the data model at the specified row - QString value (int row) const; - - QString viewKey() const { return mViewKey; } - - protected: - - /// Returns the model which provides data for the selection model - QAbstractItemModel *dataModel() { return mDataModel; } - - ///Accessor function for subclasses - bool isMultiValue() { return mIsMultiValue; } - - ///Returns the view selection model - QItemSelectionModel *selectionModel() { return mSelectionModel;} - - ///Global callback for basic view initialization - void showEvent ( QShowEvent * event ); - - ///Virtual for updating a specific View subclass - ///bool indicates whether viewUpdated() signal is emitted - virtual void updateView (bool signalUpdate = true) const; - - ///Returns the pixel width corresponding to the specified number of - ///characters. - int widgetWidth(int characterCount) const; - - private: - - ///Constructs the view layout - void buildView(); - - ///Constructs the data and selection models - void buildModel (const CSMSettings::Setting *setting); - - ///In cases where the view has a pre-defined list of possible values, - ///a QStringListModel is created using those values. - ///View changes operate on the selection model only. - void buildFixedValueModel (const QStringList &definitions); - - ///In cases where the view does not have a pre-defined list of possible - ///values, a QStandardItemModel is created, containing the actual - ///setting definitions. View changes first update the data in the - ///model to match the data in the view. The selection model always - ///selects all values. - void buildUpdatableValueModel (const QStringList &definitions); - - ///Refreshes the view - void refresh() const; - - ///Convenince function for selection model's select() method. Also - ///clears out the model beforehand to ensure complete selection. - void select (const QItemSelection &selection) const; - - ///Compares two string lists "loosely", ensuring that all values in - ///one list are contained entirely in the other, and that neither list - ///has more values than the other. List order is not considered. - bool stringListsMatch (const QStringList &list1, - const QStringList &list2) const; - - ///Converts a string list to a list of QStandardItem pointers. - QList toStandardItemList(const QStringList &) const; - - signals: - - ///Signals that the view has been changed. - void viewUpdated(const QString &, const QStringList &) const; - - }; - - class IViewFactory - { - public: - - ///Creation interface for view factories - virtual View *createView (CSMSettings::Setting *setting, - Page *parent) = 0; - }; -} -#endif // CSVSETTINGS_VIEW_HPP diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index a7316359e5..c7712f29cf 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -27,11 +27,6 @@ void CSVTools::ReportSubView::setEditLock (bool locked) // ignored. We don't change document state anyway. } -void CSVTools::ReportSubView::updateUserSetting (const QString &name, const QStringList &list) -{ - mTable->updateUserSetting (name, list); -} - void CSVTools::ReportSubView::refreshRequest() { if (!(mDocument.getState() & mRefreshState)) @@ -39,7 +34,7 @@ void CSVTools::ReportSubView::refreshRequest() if (mRefreshState==CSMDoc::State_Verifying) { mTable->clear(); - mDocument.verify (getUniversalId()); + mDocument.verify (getUniversalId()); } } } diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index b8eb2690a7..9f43efdac8 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -29,8 +29,6 @@ namespace CSVTools virtual void setEditLock (bool locked); - virtual void updateUserSetting (const QString &, const QStringList &); - private slots: void refreshRequest(); diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index d4cecc9719..bfc002933d 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -14,6 +14,8 @@ #include "../../model/tools/reportmodel.hpp" +#include "../../model/prefs/state.hpp" + #include "../../view/world/idtypedelegate.hpp" namespace CSVTools @@ -189,6 +191,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["Reports"].update(); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -206,40 +212,6 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co return ids; } -void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStringList& list) -{ - mIdTypeDelegate->updateUserSetting (name, list); - - QString base ("report-input/double"); - if (name.startsWith (base)) - { - QString modifierString = name.mid (base.size()); - Qt::KeyboardModifiers modifiers = 0; - - if (modifierString=="-s") - modifiers = Qt::ShiftModifier; - else if (modifierString=="-c") - modifiers = Qt::ControlModifier; - else if (modifierString=="-sc") - modifiers = Qt::ShiftModifier | Qt::ControlModifier; - - DoubleClickAction action = Action_None; - - QString value = list.at (0); - - if (value=="Edit") - action = Action_Edit; - else if (value=="Remove") - action = Action_Remove; - else if (value=="Edit And Remove") - action = Action_EditAndRemove; - - mDoubleClickActions[modifiers] = action; - - return; - } -} - std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { std::vector indices; @@ -285,6 +257,44 @@ void CSVTools::ReportTable::flagAsReplaced (int index) mModel->flagAsReplaced (index); } +void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting) +{ + if (setting->getParent()->getKey()=="Reports") + { + QString base ("double"); + QString key = setting->getKey().c_str(); + if (key.startsWith (base)) + { + QString modifierString = key.mid (base.size()); + Qt::KeyboardModifiers modifiers = 0; + + if (modifierString=="-s") + modifiers = Qt::ShiftModifier; + else if (modifierString=="-c") + modifiers = Qt::ControlModifier; + else if (modifierString=="-sc") + modifiers = Qt::ShiftModifier | Qt::ControlModifier; + + DoubleClickAction action = Action_None; + + std::string value = setting->toString(); + + if (value=="Edit") + action = Action_Edit; + else if (value=="Remove") + action = Action_Remove; + else if (value=="Edit And Remove") + action = Action_EditAndRemove; + + mDoubleClickActions[modifiers] = action; + + return; + } + } + else if (*setting=="Records/type-format") + mIdTypeDelegate->settingChanged (setting); +} + void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index c847b2d478..88936d3c3b 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -13,6 +13,11 @@ namespace CSMTools class ReportModel; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class CommandDelegate; @@ -61,8 +66,6 @@ namespace CSVTools virtual std::vector getDraggedRecords() const; - void updateUserSetting (const QString& name, const QStringList& list); - void clear(); /// Return indices of rows that are suitable for replacement. @@ -77,6 +80,8 @@ namespace CSVTools private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void showSelection(); void removeSelection(); diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index d3fdbbf5da..493defa5ad 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -6,7 +6,7 @@ #include "../../model/tools/search.hpp" #include "../../model/tools/reportmodel.hpp" #include "../../model/world/idtablebase.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "reporttable.hpp" #include "searchbox.hpp" @@ -23,8 +23,7 @@ void CSVTools::SearchSubView::replace (bool selection) const CSMTools::ReportModel& model = dynamic_cast (*mTable->model()); - bool autoDelete = CSMSettings::UserSettings::instance().setting ( - "search/auto-delete", QString ("true"))=="true"; + bool autoDelete = CSMPrefs::get()["Search & Replace"]["auto-delete"].isTrue(); CSMTools::Search search (mSearch); CSMWorld::IdTableBase *currentTable = 0; @@ -102,11 +101,6 @@ void CSVTools::SearchSubView::setEditLock (bool locked) mSearchBox.setEditLock (locked); } -void CSVTools::SearchSubView::updateUserSetting (const QString &name, const QStringList &list) -{ - mTable->updateUserSetting (name, list); -} - void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) { mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); @@ -114,13 +108,10 @@ void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *documen void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - - int paddingBefore = userSettings.setting ("search/char-before", QString ("5")).toInt(); - int paddingAfter = userSettings.setting ("search/char-after", QString ("5")).toInt(); + CSMPrefs::Category& settings = CSMPrefs::get()["Search & Replace"]; mSearch = search; - mSearch.setPadding (paddingBefore, paddingAfter); + mSearch.setPadding (settings["char-before"].toInt(), settings["char-after"].toInt()); mTable->clear(); mDocument.runSearch (getUniversalId(), mSearch); diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index 2e96b98b50..ac0a5a762d 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -36,15 +36,13 @@ namespace CSVTools protected: void showEvent (QShowEvent *event); - + public: SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); virtual void setEditLock (bool locked); - virtual void updateUserSetting (const QString &, const QStringList &); - private slots: void stateChanged (int state, CSMDoc::Document *document); diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 72f45a18c1..51d7137ec5 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -1,5 +1,6 @@ #include "datadisplaydelegate.hpp" -#include "../../model/settings/usersettings.hpp" + +#include "../../model/prefs/state.hpp" #include #include @@ -8,8 +9,8 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - const QString &pageName, - const QString &settingName, + const std::string &pageName, + const std::string &settingName, QObject *parent) : EnumDelegate (values, dispatcher, document, parent), mDisplayMode (Mode_TextOnly), mIcons (icons), mIconSize (QSize(16, 16)), @@ -18,10 +19,8 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, { buildPixmaps(); - QString value = - CSMSettings::UserSettings::instance().settingValue (mSettingKey); - - updateDisplayMode(value); + if (!pageName.empty()) + updateDisplayMode (CSMPrefs::get()[pageName][settingName].toString()); } void CSVWorld::DataDisplayDelegate::buildPixmaps () @@ -52,7 +51,7 @@ void CSVWorld::DataDisplayDelegate::setTextLeftOffset(int offset) QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = EnumDelegate::sizeHint(option, index); - + int valueIndex = getValueIndex(index); if (valueIndex != -1) { @@ -105,7 +104,7 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp textRect.setLeft(iconRect.right() + mTextLeftOffset); textRect.setRight(option.rect.right() - mHorizontalMargin); - QString text = option.fontMetrics.elidedText(mValues.at(index).second, + QString text = option.fontMetrics.elidedText(mValues.at(index).second, option.textElideMode, textRect.width()); QApplication::style()->drawItemText(painter, @@ -118,19 +117,7 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp QApplication::style()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, mPixmaps.at(index).second); } -void CSVWorld::DataDisplayDelegate::updateUserSetting (const QString &name, - const QStringList &list) -{ - if (list.isEmpty()) - return; - - QString value = list.at(0); - - if (name == mSettingKey) - updateDisplayMode (value); -} - -void CSVWorld::DataDisplayDelegate::updateDisplayMode (const QString &mode) +void CSVWorld::DataDisplayDelegate::updateDisplayMode (const std::string &mode) { if (mode == "Icon and Text") mDisplayMode = Mode_IconAndText; @@ -146,6 +133,13 @@ CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() { } +void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting==mSettingKey) + updateDisplayMode (setting->toString()); +} + + void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, QString iconFilename) { mIcons.push_back (std::make_pair(enumValue, QIcon(iconFilename))); @@ -158,5 +152,3 @@ CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate ( { return new DataDisplayDelegate (mValues, mIcons, dispatcher, document, "", "", parent); } - - diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index e565a3469e..cde109fd4c 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -4,10 +4,13 @@ #include #include "enumdelegate.hpp" -namespace CSVWorld +namespace CSMPrefs { + class Setting; +} - +namespace CSVWorld +{ class DataDisplayDelegate : public EnumDelegate { public: @@ -34,12 +37,12 @@ namespace CSVWorld int mHorizontalMargin; int mTextLeftOffset; - QString mSettingKey; + std::string mSettingKey; public: DataDisplayDelegate (const ValueList & values, const IconList & icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - const QString &pageName, const QString &settingName, QObject *parent); + const std::string& pageName, const std::string& settingName, QObject *parent); ~DataDisplayDelegate(); @@ -53,13 +56,10 @@ namespace CSVWorld /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. void setTextLeftOffset (int offset); - ///update the display mode for the delegate - void updateUserSetting (const QString &name, const QStringList &list); - private: /// update the display mode based on a passed string - void updateDisplayMode (const QString &); + void updateDisplayMode (const std::string &); /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only. void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const; @@ -67,6 +67,7 @@ namespace CSVWorld /// rebuild the list of pixmaps from the provided icons (called when icon size is changed) void buildPixmaps(); + virtual void settingChanged (const CSMPrefs::Setting *setting); }; class DataDisplayDelegateFactory : public EnumDelegateFactory diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 7402f62eec..25bd8e8ee4 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -31,7 +31,8 @@ #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" -#include "../../model/settings/usersettings.hpp" + +#include "../../model/prefs/state.hpp" #include "../widget/coloreditor.hpp" #include "../widget/droplineedit.hpp" @@ -564,10 +565,23 @@ void CSVWorld::EditWidget::remake(int row) static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this); - table->resizeColumnsToContents(); + bool editable = true; + bool fixedRows = false; + QVariant v = mTable->index(row, i).data(); + if (v.canConvert()) + { + assert (QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if (v.value() == CSMWorld::ColumnBase::TableEdit_None) + editable = false; + else if (v.value() == CSMWorld::ColumnBase::TableEdit_FixedRows) + fixedRows = true; + } + + NestedTable* table = + new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + table->resizeColumnsToContents(); + if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setEnabled(false); @@ -583,7 +597,7 @@ void CSVWorld::EditWidget::remake(int row) new QLabel (mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if(!editable) label->setEnabled(false); tablesLayout->addWidget(label); @@ -870,12 +884,12 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, connect (mBottom, SIGNAL (requestFocus (const std::string&)), this, SLOT (requestFocus (const std::string&))); - // button bar - if (CSMSettings::UserSettings::instance().setting ("dialogues/toolbar", QString("true")) == "true") - addButtonBar(); - // layout getMainLayout().addWidget (mBottom); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["ID Dialogues"].update(); } void CSVWorld::DialogueSubView::setEditLock (bool locked) @@ -886,29 +900,21 @@ void CSVWorld::DialogueSubView::setEditLock (bool locked) mButtons->setEditLock (locked); } -void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value) +void CSVWorld::DialogueSubView::settingChanged (const CSMPrefs::Setting *setting) { - SimpleDialogueSubView::updateUserSetting (name, value); - - if (name=="dialogues/toolbar") + if (*setting=="ID Dialogues/toolbar") { - if (value.at(0)==QString ("true")) + if (setting->isTrue()) { addButtonBar(); } - else + else if (mButtons) { - if (mButtons) - { - getMainLayout().removeWidget (mButtons); - delete mButtons; - mButtons = 0; - } + getMainLayout().removeWidget (mButtons); + delete mButtons; + mButtons = 0; } } - - if (mButtons) - mButtons->updateUserSetting (name, value); } void CSVWorld::DialogueSubView::showPreview () diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 2ae0f97203..bd7116ba2e 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -27,6 +27,11 @@ namespace CSMWorld class NestedTableProxyModel; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSMDoc { class Document; @@ -271,10 +276,10 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateUserSetting (const QString& name, const QStringList& value); - private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void showPreview(); void viewRecord(); diff --git a/apps/opencs/view/world/idtypedelegate.cpp b/apps/opencs/view/world/idtypedelegate.cpp index 34c8d12cd7..ee35e07d41 100755 --- a/apps/opencs/view/world/idtypedelegate.cpp +++ b/apps/opencs/view/world/idtypedelegate.cpp @@ -5,7 +5,7 @@ CSVWorld::IdTypeDelegate::IdTypeDelegate (const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : DataDisplayDelegate (values, icons, dispatcher, document, - "records", "type-format", + "Records", "type-format", parent) {} diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 0876b2ce78..23d5664396 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -16,8 +16,13 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent) + QWidget* parent, + bool editable, + bool fixedRows) : DragRecordTable(document, parent), + mAddNewRowAction(NULL), + mRemoveRowAction(NULL), + mEditIdAction(NULL), mModel(model) { mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); @@ -49,18 +54,24 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setModel(model); - mAddNewRowAction = new QAction (tr ("Add new row"), this); + if (editable) + { + if (!fixedRows) + { + mAddNewRowAction = new QAction (tr ("Add new row"), this); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + connect(mAddNewRowAction, SIGNAL(triggered()), + this, SLOT(addNewRowActionTriggered())); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + mRemoveRowAction = new QAction (tr ("Remove row"), this); - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + connect(mRemoveRowAction, SIGNAL(triggered()), + this, SLOT(removeRowActionTriggered())); + } - mEditIdAction = new TableEditIdAction(*this, this); - connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); + mEditIdAction = new TableEditIdAction(*this, this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); + } } std::vector CSVWorld::NestedTable::getDraggedRecords() const @@ -71,6 +82,9 @@ std::vector CSVWorld::NestedTable::getDraggedRecords() co void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { + if (!mEditIdAction) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); @@ -84,10 +98,13 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) menu.addSeparator(); } - if (selectionModel()->selectedRows().size() == 1) - menu.addAction(mRemoveRowAction); + if (mAddNewRowAction && mRemoveRowAction) + { + if (selectionModel()->selectedRows().size() == 1) + menu.addAction(mRemoveRowAction); - menu.addAction(mAddNewRowAction); + menu.addAction(mAddNewRowAction); + } menu.exec (event->globalPos()); } diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index ba8b6c0e32..765060ea5b 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -38,7 +38,9 @@ namespace CSVWorld NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent = NULL); + QWidget* parent = NULL, + bool editable = true, + bool fixedRows = false); virtual std::vector getDraggedRecords() const; diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 1a838a3b31..40a24bf658 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -6,7 +6,7 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/commanddispatcher.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "../world/tablebottombox.hpp" @@ -32,7 +32,7 @@ void CSVWorld::RecordButtonBar::updatePrevNextButtons() mPrevButton->setDisabled (true); mNextButton->setDisabled (true); } - else if (CSMSettings::UserSettings::instance().settingValue ("general-input/cycle")=="true") + else if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) { mPrevButton->setDisabled (false); mNextButton->setDisabled (false); @@ -131,6 +131,9 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + updateModificationButtons(); updatePrevNextButtons(); } @@ -141,18 +144,18 @@ void CSVWorld::RecordButtonBar::setEditLock (bool locked) updateModificationButtons(); } -void CSVWorld::RecordButtonBar::updateUserSetting (const QString& name, const QStringList& value) -{ - if (name=="general-input/cycle") - updatePrevNextButtons(); -} - void CSVWorld::RecordButtonBar::universalIdChanged (const CSMWorld::UniversalId& id) { mId = id; updatePrevNextButtons(); } +void CSVWorld::RecordButtonBar::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="General Input/cycle") + updatePrevNextButtons(); +} + void CSVWorld::RecordButtonBar::cloneRequest() { if (mBottom) @@ -173,8 +176,7 @@ void CSVWorld::RecordButtonBar::nextId() if (newRow >= mTable.rowCount()) { - if (CSMSettings::UserSettings::instance().settingValue ("general-input/cycle") - =="true") + if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) newRow = 0; else return; @@ -189,8 +191,7 @@ void CSVWorld::RecordButtonBar::prevId() if (newRow < 0) { - if (CSMSettings::UserSettings::instance().settingValue ("general-input/cycle") - =="true") + if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) newRow = mTable.rowCount()-1; else return; diff --git a/apps/opencs/view/world/recordbuttonbar.hpp b/apps/opencs/view/world/recordbuttonbar.hpp index 93ca45518e..fbee066cea 100644 --- a/apps/opencs/view/world/recordbuttonbar.hpp +++ b/apps/opencs/view/world/recordbuttonbar.hpp @@ -14,6 +14,11 @@ namespace CSMWorld class CommandDispatcher; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class TableBottomBox; @@ -49,7 +54,7 @@ namespace CSVWorld void updateModificationButtons(); void updatePrevNextButtons(); - + public: RecordButtonBar (const CSMWorld::UniversalId& id, @@ -58,14 +63,14 @@ namespace CSVWorld void setEditLock (bool locked); - void updateUserSetting (const QString& name, const QStringList& value); - public slots: void universalIdChanged (const CSMWorld::UniversalId& id); private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void cloneRequest(); void nextId(); @@ -73,7 +78,7 @@ namespace CSVWorld void prevId(); void rowNumberChanged (const QModelIndex& parent, int start, int end); - + signals: void showPreview(); diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp index 58a5c01774..12d5453391 100644 --- a/apps/opencs/view/world/recordstatusdelegate.cpp +++ b/apps/opencs/view/world/recordstatusdelegate.cpp @@ -4,14 +4,13 @@ #include #include -#include "../../model/settings/usersettings.hpp" #include "../../model/world/columns.hpp" CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, const IconList & icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : DataDisplayDelegate (values, icons, dispatcher, document, - "records", "status-format", + "Records", "status-format", parent) {} diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 753d791c0f..44fe94d840 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -147,12 +147,6 @@ std::string CSVWorld::SceneSubView::getTitle() const return mTitle; } -void CSVWorld::SceneSubView::updateUserSetting (const QString& name, const QStringList& value) -{ - mScene->updateUserSetting (name, value); - CSVDoc::SubView::updateUserSetting (name, value); -} - void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) { setUniversalId(id); diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index 2458d58f42..0f18e8c303 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -59,8 +59,6 @@ namespace CSVWorld virtual std::string getTitle() const; - virtual void updateUserSetting (const QString& name, const QStringList& value); - private: void makeConnections(CSVRender::PagedWorldspaceWidget* widget); diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 25f4fd0777..9f1abcf970 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -12,8 +12,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../../model/settings/usersettings.hpp" - +#include "../../model/prefs/state.hpp" CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) { @@ -92,31 +91,24 @@ CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighli connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - connect (&userSettings, SIGNAL (userSettingUpdated(const QString &, const QStringList &)), - this, SLOT (updateUserSetting (const QString &, const QStringList &))); + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + { + ChangeLock lock (*this); + CSMPrefs::get()["Scripts"].update(); + } mUpdateTimer.setSingleShot (true); // TODO: provide a font selector dialogue mMonoFont.setStyleHint(QFont::TypeWriter); - if (userSettings.setting("script-editor/mono-font", "true") == "true") - setFont(mMonoFont); - mLineNumberArea = new LineNumberArea(this); updateLineNumberAreaWidth(0); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); - - showLineNum(userSettings.settingValue("script-editor/show-linenum") == "true"); -} - -void CSVWorld::ScriptEdit::updateUserSetting (const QString &name, const QStringList &list) -{ - if (mHighlighter->updateUserSetting (name, list)) - updateHighlighting(); + updateHighlighting(); } void CSVWorld::ScriptEdit::showLineNum(bool show) @@ -202,6 +194,16 @@ bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const return !(string.contains(mWhiteListQoutes)); } +void CSVWorld::ScriptEdit::settingChanged (const CSMPrefs::Setting *setting) +{ + if (mHighlighter->settingChanged (setting)) + updateHighlighting(); + else if (*setting=="Scripts/mono-font") + setFont (setting->isTrue() ? mMonoFont : mDefaultFont); + else if (*setting=="Scripts/show-linenum") + showLineNum (setting->isTrue()); +} + void CSVWorld::ScriptEdit::idListChanged() { mHighlighter->invalidateIds(); diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index d17abf24ed..941a6295d8 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -91,6 +91,8 @@ namespace CSVWorld private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void idListChanged(); void updateHighlighting(); @@ -98,10 +100,6 @@ namespace CSVWorld void updateLineNumberAreaWidth(int newBlockCount); void updateLineNumberArea(const QRect &, int); - - public slots: - - void updateUserSetting (const QString &name, const QStringList &list); }; class LineNumberArea : public QWidget diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index a9e315c73c..c22bcf1991 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -9,7 +9,8 @@ #include #include "../../model/doc/document.hpp" -#include "../../model/settings/usersettings.hpp" + +#include "../../model/prefs/state.hpp" void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { @@ -57,7 +58,7 @@ void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, setItem (row, 2, messageItem); } -void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value) +void CSVWorld::ScriptErrorTable::setWarningsMode (const std::string& value) { if (value=="Ignore") Compiler::ErrorHandler::setWarningsMode (0); @@ -91,17 +92,13 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); - setWarningsMode (CSMSettings::UserSettings::instance().settingValue ("script-editor/warnings")); + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["Scripts"].update(); connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); } -void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value) -{ - if (name=="script-editor/warnings" && !value.isEmpty()) - setWarningsMode (value.at (0)); -} - void CSVWorld::ScriptErrorTable::update (const std::string& source) { clear(); @@ -136,6 +133,12 @@ bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) return mContext.clearLocals (script); } +void CSVWorld::ScriptErrorTable::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="Scripts/warnings") + setWarningsMode (setting->toString()); +} + void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { if (item (row, 1)) diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 33af7c8643..4841aac5b3 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -14,6 +14,11 @@ namespace CSMDoc class Document; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler @@ -32,14 +37,12 @@ namespace CSVWorld void addMessage (const std::string& message, CSMDoc::Message::Severity severity, int line = -1, int column = -1); - void setWarningsMode (const QString& value); + void setWarningsMode (const std::string& value); public: ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); - void updateUserSetting (const QString& name, const QStringList& value); - void update (const std::string& source); void clear(); @@ -51,6 +54,8 @@ namespace CSVWorld private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void cellClicked (int row, int column); signals: diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index 487b5b1395..846a61b471 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -5,7 +5,8 @@ #include #include -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/setting.hpp" +#include "../../model/prefs/category.hpp" bool CSVWorld::ScriptHighlighter::parseInt (int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) @@ -79,79 +80,12 @@ CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, Mode : QSyntaxHighlighter (parent), Compiler::Parser (mErrorHandler, mContext), mContext (data), mMode (mode) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + QColor color ("black"); + QTextCharFormat format; + format.setForeground (color); - QColor color = QColor(); - - { - color.setNamedColor(userSettings.setting("script-editor/colour-int", "Dark magenta")); - if (!color.isValid()) - color = QColor(Qt::darkMagenta); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Int, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-float", "Magenta")); - if (!color.isValid()) - color = QColor(Qt::magenta); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Float, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-name", "Gray")); - if (!color.isValid()) - color = QColor(Qt::gray); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Name, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-keyword", "Red")); - if (!color.isValid()) - color = QColor(Qt::red); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Keyword, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-special", "Dark yellow")); - if (!color.isValid()) - color = QColor(Qt::darkYellow); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Special, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-comment", "Green")); - if (!color.isValid()) - color = QColor(Qt::green); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Comment, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-id", "Blue")); - if (!color.isValid()) - color = QColor(Qt::blue); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Id, format)); - } + for (int i=0; i<=Type_Id; ++i) + mScheme.insert (std::make_pair (static_cast (i), format)); // configure compiler Compiler::registerExtensions (mExtensions); @@ -176,85 +110,26 @@ void CSVWorld::ScriptHighlighter::invalidateIds() mContext.invalidateIds(); } -bool CSVWorld::ScriptHighlighter::updateUserSetting (const QString &name, const QStringList &list) +bool CSVWorld::ScriptHighlighter::settingChanged (const CSMPrefs::Setting *setting) { - if (list.empty()) - return false; - - QColor color = QColor(); - - if (name == "script-editor/colour-int") + if (setting->getParent()->getKey()=="Scripts") { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Int] = format; + static const char *const colours[Type_Id+2] = + { + "colour-int", "colour-float", "colour-name", "colour-keyword", + "colour-special", "colour-comment", "colour-id", + 0 + }; + + for (int i=0; colours[i]; ++i) + if (setting->getKey()==colours[i]) + { + QTextCharFormat format; + format.setForeground (setting->toColor()); + mScheme[static_cast (i)] = format; + return true; + } } - else if (name == "script-editor/colour-float") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Float] = format; - } - else if (name == "script-editor/colour-name") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Name] = format; - } - else if (name == "script-editor/colour-keyword") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Keyword] = format; - } - else if (name == "script-editor/colour-special") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Special] = format; - } - else if (name == "script-editor/colour-comment") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Comment] = format; - } - else if (name == "script-editor/colour-id") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Id] = format; - } - else - return false; - - return true; + return false; } diff --git a/apps/opencs/view/world/scripthighlighter.hpp b/apps/opencs/view/world/scripthighlighter.hpp index 6f1f58e823..33824da0dc 100644 --- a/apps/opencs/view/world/scripthighlighter.hpp +++ b/apps/opencs/view/world/scripthighlighter.hpp @@ -11,6 +11,11 @@ #include "../../model/world/scriptcontext.hpp" +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class ScriptHighlighter : public QSyntaxHighlighter, private Compiler::Parser @@ -19,13 +24,13 @@ namespace CSVWorld enum Type { - Type_Int, - Type_Float, - Type_Name, - Type_Keyword, - Type_Special, - Type_Comment, - Type_Id + Type_Int = 0, + Type_Float = 1, + Type_Name = 2, + Type_Keyword = 3, + Type_Special = 4, + Type_Comment = 5, + Type_Id = 6 }; enum Mode @@ -88,7 +93,7 @@ namespace CSVWorld void invalidateIds(); - bool updateUserSetting (const QString &name, const QStringList &list); + bool settingChanged (const CSMPrefs::Setting *setting); }; } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index eb0c706564..ee0acb560a 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -13,7 +13,7 @@ #include "../../model/world/columnbase.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "scriptedit.hpp" #include "recordbuttonbar.hpp" @@ -39,8 +39,7 @@ void CSVWorld::ScriptSubView::addButtonBar() void CSVWorld::ScriptSubView::recompile() { if (!mCompileDelay->isActive() && !isDeleted()) - mCompileDelay->start ( - CSMSettings::UserSettings::instance().setting ("script-editor/compile-delay").toInt()); + mCompileDelay->start (CSMPrefs::get()["Scripts"]["compile-delay"].toInt()); } bool CSVWorld::ScriptSubView::isDeleted() const @@ -54,6 +53,7 @@ void CSVWorld::ScriptSubView::updateDeletedState() if (isDeleted()) { mErrors->clear(); + adjustSplitter(); mEditor->setEnabled (false); } else @@ -63,9 +63,32 @@ void CSVWorld::ScriptSubView::updateDeletedState() } } +void CSVWorld::ScriptSubView::adjustSplitter() +{ + QList sizes; + + if (mErrors->rowCount()) + { + if (mErrors->height()) + return; // keep old height if the error panel was already open + + sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight; + } + else + { + if (mErrors->height()) + mErrorHeight = mErrors->height(); + + sizes << 1 << 0; + } + + mMain->setSizes (sizes); +} + CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), - mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), + mErrorHeight (CSMPrefs::get()["Scripts"]["error-height"].toInt()) { std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); @@ -81,6 +104,10 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mErrors = new ScriptErrorTable (document, this); mMain->addWidget (mErrors); + QList sizes; + sizes << 1 << 0; + mMain->setSizes (sizes); + QWidget *widget = new QWidget (this);; widget->setLayout (&mLayout); setWidget (widget); @@ -98,9 +125,6 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: // bottom box and buttons mBottom = new TableBottomBox (CreatorFactory(), document, id, this); - if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true") - addButtonBar(); - connect (mBottom, SIGNAL (requestFocus (const std::string&)), this, SLOT (switchToId (const std::string&))); @@ -128,55 +152,40 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); updateDeletedState(); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["Scripts"].update(); } -void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) +void CSVWorld::ScriptSubView::setStatusBar (bool show) { - if (name == "script-editor/show-linenum") - { - std::string showLinenum = value.at(0).toUtf8().constData(); - mEditor->showLineNum(showLinenum == "true"); - mBottom->setVisible(showLinenum == "true"); - } - else if (name == "script-editor/mono-font") - { - mEditor->setMonoFont (value.at(0)==QString ("true")); - } - else if (name=="script-editor/toolbar") + mBottom->setStatusBar (show); +} + +void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="Scripts/toolbar") { - if (value.at(0)==QString ("true")) + if (setting->isTrue()) { addButtonBar(); } - else + else if (mButtons) { - if (mButtons) - { - mLayout.removeWidget (mButtons); - delete mButtons; - mButtons = 0; - } + mLayout.removeWidget (mButtons); + delete mButtons; + mButtons = 0; } } - else if (name=="script-editor/compile-delay") + else if (*setting=="Scripts/compile-delay") { - mCompileDelay->setInterval (value.at (0).toInt()); + mCompileDelay->setInterval (setting->toInt()); } - - if (mButtons) - mButtons->updateUserSetting (name, value); - - mErrors->updateUserSetting (name, value); - - if (name=="script-editor/warnings") + else if (*setting=="Scripts/warnings") recompile(); } -void CSVWorld::ScriptSubView::setStatusBar (bool show) -{ - mBottom->setStatusBar (show); -} - void CSVWorld::ScriptSubView::updateStatusBar () { mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, @@ -347,4 +356,6 @@ void CSVWorld::ScriptSubView::updateRequest() QString source = mModel->data (index).toString(); mErrors->update (source.toUtf8().constData()); + + adjustSplitter(); } diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 907dc7958b..c1016babf9 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -23,6 +23,11 @@ namespace CSMWorld class IdTable; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class ScriptEdit; @@ -47,6 +52,7 @@ namespace CSVWorld QSplitter *mMain; ScriptErrorTable *mErrors; QTimer *mCompileDelay; + int mErrorHeight; private: @@ -58,6 +64,8 @@ namespace CSVWorld void updateDeletedState(); + void adjustSplitter(); + public: ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); @@ -66,8 +74,6 @@ namespace CSVWorld virtual void useHint (const std::string& hint); - virtual void updateUserSetting (const QString& name, const QStringList& value); - virtual void setStatusBar (bool show); public slots: @@ -80,6 +86,8 @@ namespace CSVWorld private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void updateStatusBar(); void switchToRow (int row); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 73bef7b26b..95dfa10340 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -23,7 +23,8 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" -#include "../../model/settings/usersettings.hpp" + +#include "../../model/prefs/state.hpp" #include "recordstatusdelegate.hpp" #include "tableeditidaction.hpp" @@ -232,24 +233,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, : DragRecordTable(document), mCreateAction (0), mCloneAction(0),mRecordStatusDisplay (0) { - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - QString jumpSetting = settings.settingValue ("table-input/jump-to-added"); - if (jumpSetting.isEmpty() || jumpSetting == "Jump and Select") // default - { - mJumpToAddedRecord = true; - mUnselectAfterJump = false; - } - else if(jumpSetting == "Jump Only") - { - mJumpToAddedRecord = true; - mUnselectAfterJump = true; - } - else - { - mJumpToAddedRecord = false; - mUnselectAfterJump = false; - } - mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || @@ -358,7 +341,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); - connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), + connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), this, SLOT (rowAdded (const std::string &))); /// \note This signal could instead be connected to a slot that filters out changes not affecting @@ -375,6 +358,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["ID Tables"].update(); } void CSVWorld::Table::setEditLock (bool locked) @@ -404,7 +391,7 @@ std::vector CSVWorld::Table::getSelectedIds() const QModelIndexList selectedRows = selectionModel()->selectedRows(); int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - for (QModelIndexList::const_iterator iter (selectedRows.begin()); + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter != selectedRows.end(); ++iter) { @@ -548,9 +535,7 @@ void CSVWorld::Table::previewRecord() void CSVWorld::Table::executeExtendedDelete() { - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - QString configSetting = settings.settingValue ("table-input/extended-config"); - if (configSetting == "true") + if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { emit extendedDeleteConfigRequest(getSelectedIds()); } @@ -562,9 +547,7 @@ void CSVWorld::Table::executeExtendedDelete() void CSVWorld::Table::executeExtendedRevert() { - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - QString configSetting = settings.settingValue ("table-input/extended-config"); - if (configSetting == "true") + if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { emit extendedRevertConfigRequest(getSelectedIds()); } @@ -574,16 +557,16 @@ void CSVWorld::Table::executeExtendedRevert() } } -void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList &list) +void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting) { - if (name=="table-input/jump-to-added") + if (*setting=="ID Tables/jump-to-added") { - if(list.isEmpty() || list.at(0) == "Jump and Select") // default + if (setting->toString()=="Jump and Select") { mJumpToAddedRecord = true; mUnselectAfterJump = false; } - else if(list.at(0) == "Jump Only") + else if (setting->toString()=="Jump Only") { mJumpToAddedRecord = true; mUnselectAfterJump = true; @@ -594,28 +577,23 @@ void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList mUnselectAfterJump = false; } } - - if (name=="records/type-format" || name=="records/status-format") + else if (*setting=="Records/type-format" || *setting=="Records/status-format") { int columns = mModel->columnCount(); for (int i=0; i - (*delegate).updateUserSetting (name, list); - { - emit dataChanged (mModel->index (0, i), - mModel->index (mModel->rowCount()-1, i)); - } + dynamic_cast (*delegate).settingChanged (setting); + emit dataChanged (mModel->index (0, i), + mModel->index (mModel->rowCount()-1, i)); } - return; } - - QString base ("table-input/double"); - if (name.startsWith (base)) + else if (setting->getParent()->getKey()=="ID Tables" && + setting->getKey().substr (0, 6)=="double") { - QString modifierString = name.mid (base.size()); + std::string modifierString = setting->getKey().substr (6); + Qt::KeyboardModifiers modifiers = 0; if (modifierString=="-s") @@ -627,7 +605,7 @@ void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList DoubleClickAction action = Action_None; - QString value = list.at (0); + std::string value = setting->toString(); if (value=="Edit in Place") action = Action_InPlaceEdit; @@ -645,8 +623,6 @@ void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList action = Action_ViewAndClose; mDoubleClickActions[modifiers] = action; - - return; } } diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 95a25075d3..53249f66fe 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -27,6 +27,11 @@ namespace CSMWorld class CommandDispatcher; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class CommandDelegate; @@ -140,6 +145,8 @@ namespace CSVWorld public slots: + void settingChanged (const CSMPrefs::Setting *setting); + void tableSizeUpdate(); void selectionSizeUpdate(); @@ -148,8 +155,6 @@ namespace CSVWorld void recordFilterChanged (boost::shared_ptr filter); - void updateUserSetting (const QString &name, const QStringList &list); - void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 81b2699934..1b25e1c086 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -98,12 +98,6 @@ void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const focusId (id, hint); } -void CSVWorld::TableSubView::updateUserSetting - (const QString &name, const QStringList &list) -{ - mTable->updateUserSetting(name, list); -} - void CSVWorld::TableSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 9d86c32e40..7d143d927c 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -43,9 +43,6 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateUserSetting - (const QString& name, const QStringList &list); - virtual void setStatusBar (bool show); virtual void useHint (const std::string& hint); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index b87d6b4f48..b0431f71a0 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -166,7 +166,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); - + // This createEditor() method is called implicitly from tables. // For boolean values in tables use the default editor (combobox). // Checkboxes is looking ugly in the table view. @@ -239,7 +239,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO edit->setUndoRedoEnabled (false); return edit; } - + case CSMWorld::ColumnBase::Display_Boolean: return new QCheckBox(parent); @@ -260,7 +260,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO widget->setMaxLength (32); return widget; } - + default: return QStyledItemDelegate::createEditor (parent, option, index); @@ -329,3 +329,5 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde } } + +void CSVWorld::CommandDelegate::settingChanged (const CSMPrefs::Setting *setting) {} diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index d695be0d7e..766d729588 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -18,6 +18,11 @@ namespace CSMWorld class CommandDispatcher; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { ///< \brief Getting the data out of an editor widget @@ -138,10 +143,9 @@ namespace CSVWorld virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const; - public slots: - - virtual void updateUserSetting - (const QString &name, const QStringList &list) {} + /// \attention This is not a slot. For ordering reasons this function needs to be + /// called manually from the parent object's settingChanged function. + virtual void settingChanged (const CSMPrefs::Setting *setting); }; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index fa05576f5d..0113fed022 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,12 +57,12 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper movieaudiofactory + soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory ) add_openmw_dir (mwworld refdata worldimp scene globals class action nullaction actionteleport - containerstore actiontalk actiontake manualref player cellfunctors failedaction + containerstore actiontalk actiontake manualref player cellvisitors failedaction cells localscripts customdata inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor @@ -113,16 +113,24 @@ endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. -include_directories(${SOUND_INPUT_INCLUDES}) +include_directories( + ${FFMPEG_INCLUDE_DIRS} +) target_link_libraries(openmw - ${OPENSCENEGRAPH_LIBRARIES} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + ${OSGPARTICLE_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGDB_LIBRARIES} + ${OSGVIEWER_LIBRARIES} + ${OSGGA_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPENAL_LIBRARY} - ${SOUND_INPUT_LIBRARY} + ${FFMPEG_LIBRARIES} ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} @@ -132,15 +140,27 @@ target_link_libraries(openmw ) if (ANDROID) + set (OSG_PLUGINS + -Wl,--whole-archive + ${OSG_PLUGINS_DIR}/libosgdb_dds.a + ${OSG_PLUGINS_DIR}/libosgdb_bmp.a + ${OSG_PLUGINS_DIR}/libosgdb_tga.a + ${OSG_PLUGINS_DIR}/libosgdb_gif.a + ${OSG_PLUGINS_DIR}/libosgdb_jpeg.a + ${OSG_PLUGINS_DIR}/libosgdb_png.a + -Wl,--no-whole-archive + ) target_link_libraries(openmw EGL android log dl - MyGUIEngineStatic - cpufeatures - BulletCollision - LinearMath + z + ${OPENSCENEGRAPH_LIBRARIES} + ${OSG_PLUGINS} + jpeg + gif + png ) endif (ANDROID) diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 47b77a8b38..8cd69e8f01 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -1,4 +1,3 @@ -#include "../../SDL_internal.h" #ifdef __ANDROID__ #include "SDL_main.h" diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 373d78746c..0b4ff6304f 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -387,11 +387,17 @@ static void crash_handler(const char *logfile) kill(crash_info.pid, SIGKILL); } + // delay between killing of the crashed process and showing the message box to + // work around occasional X server lock-up. this can only be a bug in X11 since + // even faulty applications shouldn't be able to freeze the X server. + usleep(100000); + if(logfile) { std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); } + exit(0); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 79c8c4cc98..b43fd2f530 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -168,9 +168,6 @@ void OMW::Engine::frame(float frametime) if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { -#if 0 - mEnvironment.getWindowManager()->wmUpdateFps(fps); -#endif mEnvironment.getWindowManager()->update(); } @@ -301,8 +298,8 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) std::string OMW::Engine::loadSettings (Settings::Manager & settings) { // Create the settings manager and load default settings file - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg"; + const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); + const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); // prefer local if (boost::filesystem::exists(localdefault)) @@ -313,7 +310,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); // load user settings if they exist - const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; + const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); @@ -454,12 +451,13 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); mResourceSystem->getTextureManager()->setUnRefImageDataAfterApply(true); - osg::Texture::FilterMode min = osg::Texture::LINEAR_MIPMAP_NEAREST; - osg::Texture::FilterMode mag = osg::Texture::LINEAR; - if (Settings::Manager::getString("texture filtering", "General") == "trilinear") - min = osg::Texture::LINEAR_MIPMAP_LINEAR; - int maxAnisotropy = Settings::Manager::getInt("anisotropy", "General"); - mResourceSystem->getTextureManager()->setFilterSettings(min, mag, maxAnisotropy); + mResourceSystem->getTextureManager()->setFilterSettings( + Settings::Manager::getString("texture mag filter", "General"), + Settings::Manager::getString("texture min filter", "General"), + Settings::Manager::getString("texture mipmap", "General"), + Settings::Manager::getInt("anisotropy", "General"), + NULL + ); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -486,8 +484,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - // FIXME: shouldn't depend on Engine - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, *this, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); mEnvironment.setInputManager (input); std::string myguiResources = (mResDir / "mygui").string(); @@ -513,10 +510,11 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create the world mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mFileCollections, mContentFiles, mEncoder, mFallbackMap, - mActivationDistanceOverride, mCellName, mStartupScript)); + mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string())); mEnvironment.getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); + window->setStore(mEnvironment.getWorld()->getStore()); window->initUI(); window->renderWorldMap(); @@ -720,41 +718,6 @@ void OMW::Engine::go() std::cout << "Quitting peacefully." << std::endl; } -void OMW::Engine::activate() -{ - if (mEnvironment.getWindowManager()->isGuiMode()) - return; - - MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); - if (playerStats.isParalyzed() || playerStats.getKnockedDown()) - return; - - MWWorld::Ptr ptr = mEnvironment.getWorld()->getFacedObject(); - - if (ptr.isEmpty()) - return; - - if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated - return; - - if (ptr.getClass().isActor()) - { - MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - - if (stats.getAiSequence().isInCombat() && !stats.isDead()) - return; - } - - mEnvironment.getWorld()->activate(ptr, mEnvironment.getWorld()->getPlayerPtr()); -} - -void OMW::Engine::screenshot() -{ - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); -} - void OMW::Engine::setCompileAll (bool all) { mCompileAll = all; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 73de57dc4b..a03752b86f 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -169,12 +169,6 @@ namespace OMW /// Initialise and enter main loop. void go(); - /// Activate the focussed object. - void activate(); - - /// Write screenshot to file. - void screenshot(); - /// Compile all scripts (excludign dialogue scripts) at startup? void setCompileAll (bool all); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3d631c7439..c3f0f8688e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -23,7 +23,7 @@ #endif -#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) +#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix)) #define USE_CRASH_CATCHER 1 #else #define USE_CRASH_CATCHER 0 @@ -210,6 +210,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as()); + std::cout << v.describe() << std::endl; + engine.setGrabMouse(!variables.count("no-grab")); // Font encoding settings @@ -321,6 +324,10 @@ private: int main(int argc, char**argv) { +#if defined(__APPLE__) + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); +#endif + // Some objects used to redirect cout and cerr // Scope must be here, so this still works inside the catch block for logging exceptions std::streambuf* cout_rdbuf = std::cout.rdbuf (); diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 63ad1d32fc..31afc126a1 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -184,8 +184,9 @@ namespace MWBase virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) = 0; virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) = 0; - ///return the list of actors which are following the given actor - /**ie AiFollow is active and the target is the actor**/ + ///Returns the list of actors which are siding with the given actor in fights + /**ie AiFollow or AiEscort is active and the target is the actor **/ + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 4fccec40bb..22582c2b1f 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -15,6 +15,7 @@ namespace MWWorld namespace MWSound { class Sound; + class Stream; struct Sound_Decoder; typedef boost::shared_ptr DecoderPtr; } @@ -22,6 +23,7 @@ namespace MWSound namespace MWBase { typedef boost::shared_ptr SoundPtr; + typedef boost::shared_ptr SoundStreamPtr; /// \brief Interface for sound manager (implemented in MWSound) class SoundManager @@ -29,18 +31,17 @@ namespace MWBase public: /* These must all fit together */ enum PlayMode { - Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ + Play_Normal = 0, /* non-looping, affected by environment */ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ - Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position - * but do not keep it updated (the sound will not move with - * the object and will not stop when the object is deleted. */ - Play_RemoveAtDistance = 1<<3, /* (3D only) If the listener gets further than 2000 units away + Play_RemoveAtDistance = 1<<2, /* (3D only) If the listener gets further than 2000 units away from the sound source, the sound is removed. This is weird stuff but apparently how vanilla works for sounds played by the PlayLoopSound family of script functions. Perhaps we can make this cut off a more subtle fade later, but have to be careful to not change the overall volume of areas by too much. */ + Play_NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the + player is making it. */ Play_LoopNoEnv = Play_Loop | Play_NoEnv, Play_LoopRemoveAtDistance = Play_Loop | Play_RemoveAtDistance }; @@ -86,7 +87,7 @@ namespace MWBase ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - virtual void say(const MWWorld::Ptr &reference, const std::string& filename) = 0; + virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -94,58 +95,66 @@ namespace MWBase ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const = 0; + virtual bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const = 0; ///< Is actor not speaking? - virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0; + virtual void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) = 0; ///< Stop an actor speaking - virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const = 0; + virtual float getSaySoundLoudness(const MWWorld::ConstPtr& 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; + virtual SoundStreamPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; ///< Play a 2D audio track, using a custom decoder + virtual void stopTrack(SoundStreamPtr stream) = 0; + ///< Stop the given audio track from playing + + virtual double getTrackTimeDelay(SoundStreamPtr stream) = 0; + ///< Retives the time delay, in seconds, of the audio track (must be a sound + /// returned by \ref playTrack). Only intended to be called by the track + /// decoder's read method. + virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0; ///< Play a sound, independently of 3D-position - ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + virtual MWBase::SoundPtr playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0; ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. - ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playManualSound3D(const osg::Vec3f& initialPos, const std::string& soundId, - float volume, float pitch, PlayType type, PlayMode mode, float offset=0) = 0; - ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition. + virtual MWBase::SoundPtr playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, + float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0; + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. - virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0; + virtual void stopSound(SoundPtr sound) = 0; + ///< Stop the given sound from playing + + virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId) = 0; ///< Stop the given object from playing the given sound, - virtual void stopSound3D(const MWWorld::Ptr &reference) = 0; + virtual void stopSound3D(const MWWorld::ConstPtr &reference) = 0; ///< Stop the given object from playing all sounds. - virtual void stopSound(MWBase::SoundPtr sound) = 0; - ///< Stop the given sound handle - virtual void stopSound(const MWWorld::CellStore *cell) = 0; ///< Stop all sounds for the given cell. virtual void stopSound(const std::string& soundId) = 0; ///< Stop a non-3d looping sound - virtual void fadeOutSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float duration) = 0; + virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration) = 0; ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. - virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const = 0; + virtual bool getSoundPlaying(const MWWorld::ConstPtr &reference, const std::string& soundId) const = 0; ///< Is the given sound currently playing on the given object? /// If you want to check if sound played with playSound is playing, use empty Ptr @@ -157,9 +166,9 @@ namespace MWBase virtual void update(float duration) = 0; - virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up) = 0; + virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) = 0; - virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0; + virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; virtual void clear() = 0; }; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index fb7eca4a35..f364ada7ac 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -155,8 +155,6 @@ namespace MWBase virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; - virtual void wmUpdateFps(float fps) = 0; - /// Set value for the given ID. virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value) = 0; virtual void setValue (int parSkill, const MWMechanics::SkillValue& value) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1622e15376..5ae21022ca 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -14,6 +14,7 @@ namespace osg { class Vec3f; + class Matrixf; class Quat; class Image; } @@ -177,7 +178,7 @@ namespace MWBase virtual MWWorld::Ptr searchPtrViaActorId (int actorId) = 0; ///< Search is limited to the active cells. - virtual MWWorld::Ptr findContainer (const MWWorld::Ptr& ptr) = 0; + virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr) = 0; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. @@ -247,7 +248,7 @@ namespace MWBase /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance) = 0; + virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance) = 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. @@ -270,9 +271,7 @@ namespace MWBase virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0; - virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; + virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -348,14 +347,14 @@ namespace MWBase virtual void update (float duration, bool paused) = 0; - virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; + virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0; + virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) = 0; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object @@ -368,13 +367,15 @@ namespace MWBase virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const = 0; - virtual bool isSwimming(const MWWorld::Ptr &object) const = 0; - virtual bool isWading(const MWWorld::Ptr &object) const = 0; + virtual bool isSwimming(const MWWorld::ConstPtr &object) const = 0; + virtual bool isWading(const MWWorld::ConstPtr &object) const = 0; ///Is the head of the creature underwater? - virtual bool isSubmerged(const MWWorld::Ptr &object) const = 0; + virtual bool isSubmerged(const MWWorld::ConstPtr &object) const = 0; virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; + virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; + virtual void togglePOV() = 0; virtual bool isFirstPerson() const = 0; virtual void togglePreviewMode(bool enable) = 0; @@ -395,25 +396,25 @@ namespace MWBase /// @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; + virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is standing on \a object + virtual bool getActorStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::ConstPtr& 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; + virtual void hurtCollidingActors (const MWWorld::ConstPtr& 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; + virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; ///< get all containers in active cells owned by this Npc - virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; + virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) = 0; + virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) = 0; @@ -429,6 +430,7 @@ namespace MWBase /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; + virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const = 0; virtual void reattachPlayerCamera() = 0; /// \todo this does not belong here @@ -480,7 +482,7 @@ namespace MWBase virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; - virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; virtual const std::vector& getContentFiles() const = 0; @@ -540,11 +542,16 @@ namespace MWBase /// Resets all actors in the current active cells to their original location within that cell. virtual void resetActors() = 0; - virtual bool isWalkingOnWater (const MWWorld::Ptr& actor) = 0; + virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor) = 0; /// Return a vector aiming the actor's weapon towards a target. /// @note The length of the vector is the distance between actor and target. - virtual osg::Vec3f aimToTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) = 0; + virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; + + /// Return the distance between actor's weapon and target's collision box. + virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; + + virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 3a0f1b951b..9785eef1e6 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -26,10 +26,6 @@ namespace MWClass { - std::string Activator::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -45,11 +41,9 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Activator::getModel(const MWWorld::Ptr &ptr) const + std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -58,17 +52,16 @@ namespace MWClass return ""; } - std::string Activator::getName (const MWWorld::Ptr& ptr) const + std::string Activator::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } - std::string Activator::getScript (const MWWorld::Ptr& ptr) const + std::string Activator::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; @@ -81,21 +74,19 @@ namespace MWClass registerClass (typeid (ESM::Activator).name(), instance); } - bool Activator::hasToolTip (const MWWorld::Ptr& ptr) const + bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) @@ -124,12 +115,10 @@ namespace MWClass } - MWWorld::Ptr - Activator::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Activator::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 646bb79bb2..15dbf57677 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -8,30 +8,26 @@ namespace MWClass class Activator : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; @@ -39,7 +35,7 @@ namespace MWClass static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index d58047d06f..4e89c7282e 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -61,11 +61,6 @@ namespace MWClass } } - bool Actor::hasToolTip(const MWWorld::Ptr& ptr) const - { - return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); - } - osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const { MWMechanics::Movement &movement = getMovementSettings(ptr); diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 3f795dff9d..88a3f1a328 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -28,9 +28,6 @@ namespace MWClass virtual void block(const MWWorld::Ptr &ptr) const; - virtual bool hasToolTip(const MWWorld::Ptr& ptr) const; - ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const; ///< Return desired rotations, as euler angles. diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index f93556ef90..8907a5f8da 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -20,10 +20,6 @@ namespace MWClass { - std::string Apparatus::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -37,11 +33,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const + std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -50,10 +44,9 @@ namespace MWClass return ""; } - std::string Apparatus::getName (const MWWorld::Ptr& ptr) const + std::string Apparatus::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -64,18 +57,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Apparatus::getScript (const MWWorld::Ptr& ptr) const + std::string Apparatus::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Apparatus::getValue (const MWWorld::Ptr& ptr) const + int Apparatus::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -87,39 +78,36 @@ namespace MWClass registerClass (typeid (ESM::Apparatus).name(), instance); } - std::string Apparatus::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Apparatus::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Up"); } - std::string Apparatus::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Apparatus::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Down"); } - std::string Apparatus::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Apparatus::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Apparatus::hasToolTip (const MWWorld::Ptr& ptr) const + bool Apparatus::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -142,24 +130,21 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionAlchemy()); } - MWWorld::Ptr - Apparatus::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Apparatus::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Apparatus::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Apparatus) != 0; } - float Apparatus::getWeight(const MWWorld::Ptr &ptr) const + float Apparatus::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 94e998e488..d669b9c2da 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -8,22 +8,18 @@ namespace MWClass class Apparatus : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -31,36 +27,36 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 324dd32eef..2e4667016f 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -26,10 +26,6 @@ namespace MWClass { - std::string Armor::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -43,11 +39,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Armor::getModel(const MWWorld::Ptr &ptr) const + std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -56,10 +50,9 @@ namespace MWClass return ""; } - std::string Armor::getName (const MWWorld::Ptr& ptr) const + std::string Armor::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -70,31 +63,28 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - bool Armor::hasItemHealth (const MWWorld::Ptr& ptr) const + bool Armor::hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } - int Armor::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Armor::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mHealth; } - std::string Armor::getScript (const MWWorld::Ptr& ptr) const + std::string Armor::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Armor::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Armor::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; @@ -125,10 +115,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Armor::getEquipmentSkill (const MWWorld::Ptr& ptr) const + int Armor::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::string typeGmst; @@ -170,10 +159,9 @@ namespace MWClass return ESM::Skill::HeavyArmor; } - int Armor::getValue (const MWWorld::Ptr& ptr) const + int Armor::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -185,7 +173,7 @@ namespace MWClass registerClass (typeid (ESM::Armor).name(), instance); } - std::string Armor::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Armor::getUpSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) @@ -196,7 +184,7 @@ namespace MWClass return std::string("Item Armor Heavy Up"); } - std::string Armor::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Armor::getDownSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) @@ -207,29 +195,26 @@ namespace MWClass return std::string("Item Armor Heavy Down"); } - std::string Armor::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Armor::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Armor::hasToolTip (const MWWorld::Ptr& ptr) const + bool Armor::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -268,18 +253,16 @@ namespace MWClass return info; } - std::string Armor::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Armor::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Armor::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Armor::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Armor newItem = *ref->mBase; newItem.mId=""; @@ -290,9 +273,9 @@ namespace MWClass return record->mId; } - int Armor::getEffectiveArmorRating(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const + int Armor::getEffectiveArmorRating(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &actor) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int armorSkillType = getEquipmentSkill(ptr); int armorSkill = actor.getClass().getSkill(actor, armorSkillType); @@ -306,7 +289,7 @@ namespace MWClass return ref->mBase->mData.mArmor * armorSkill / iBaseArmorSkill; } - std::pair Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Armor::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); @@ -376,33 +359,29 @@ namespace MWClass return action; } - MWWorld::Ptr - Armor::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Armor::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Armor::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Armor::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Armor) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Armor::getWeight(const MWWorld::Ptr &ptr) const + float Armor::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index ec32908783..9746d6d209 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -7,22 +7,18 @@ namespace MWClass { class Armor : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -30,50 +26,50 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const; ///< \return Item health data available? - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + virtual int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n /// Second item in the pair specifies the error message @@ -81,14 +77,14 @@ namespace MWClass const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; /// Get the effective armor rating, factoring in the actor's skills, for the given armor. - virtual int getEffectiveArmorRating(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; + virtual int getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; }; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 2c20435b2f..a7d8ed8f4d 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -23,10 +23,6 @@ namespace MWClass { - std::string Book::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -40,11 +36,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Book::getModel(const MWWorld::Ptr &ptr) const + std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -53,10 +47,9 @@ namespace MWClass return ""; } - std::string Book::getName (const MWWorld::Ptr& ptr) const + std::string Book::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -78,18 +71,16 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRead(ptr)); } - std::string Book::getScript (const MWWorld::Ptr& ptr) const + std::string Book::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Book::getValue (const MWWorld::Ptr& ptr) const + int Book::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -101,39 +92,36 @@ namespace MWClass registerClass (typeid (ESM::Book).name(), instance); } - std::string Book::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Book::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Up"); } - std::string Book::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Book::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Down"); } - std::string Book::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Book::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Book::hasToolTip (const MWWorld::Ptr& ptr) const + bool Book::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -153,18 +141,16 @@ namespace MWClass return info; } - std::string Book::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Book::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Book::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Book::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Book newItem = *ref->mBase; newItem.mId=""; @@ -181,33 +167,29 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRead(ptr)); } - MWWorld::Ptr - Book::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Book::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Book::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Book::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Books) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Book::getWeight(const MWWorld::Ptr &ptr) const + float Book::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 8dcae731a2..73bfecacd9 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Book : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,45 +24,45 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index cea30d561f..20ebab3145 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -22,10 +22,6 @@ namespace MWClass { - std::string Clothing::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -39,11 +35,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Clothing::getModel(const MWWorld::Ptr &ptr) const + std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -52,10 +46,9 @@ namespace MWClass return ""; } - std::string Clothing::getName (const MWWorld::Ptr& ptr) const + std::string Clothing::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -66,18 +59,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Clothing::getScript (const MWWorld::Ptr& ptr) const + std::string Clothing::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; @@ -114,10 +105,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Clothing::getEquipmentSkill (const MWWorld::Ptr& ptr) const + int Clothing::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType==ESM::Clothing::Shoes) return ESM::Skill::Unarmored; @@ -125,10 +115,9 @@ namespace MWClass return -1; } - int Clothing::getValue (const MWWorld::Ptr& ptr) const + int Clothing::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -140,10 +129,9 @@ namespace MWClass registerClass (typeid (ESM::Clothing).name(), instance); } - std::string Clothing::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Clothing::getUpSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType == 8) { @@ -152,10 +140,9 @@ namespace MWClass return std::string("Item Clothes Up"); } - std::string Clothing::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Clothing::getDownSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType == 8) { @@ -164,29 +151,26 @@ namespace MWClass return std::string("Item Clothes Down"); } - std::string Clothing::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Clothing::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Clothing::hasToolTip (const MWWorld::Ptr& ptr) const + bool Clothing::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -208,18 +192,16 @@ namespace MWClass return info; } - std::string Clothing::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Clothing::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Clothing::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Clothing::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Clothing newItem = *ref->mBase; newItem.mId=""; @@ -230,7 +212,7 @@ namespace MWClass return record->mId; } - std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); @@ -270,33 +252,29 @@ namespace MWClass return action; } - MWWorld::Ptr - Clothing::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Clothing::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Clothing::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Clothing::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Clothing) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Clothing::getWeight(const MWWorld::Ptr &ptr) const + float Clothing::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index adb3491580..e1c0f63f78 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Clothing : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,44 +24,44 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + virtual int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message @@ -73,13 +69,13 @@ namespace MWClass const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index f785797c10..675287af96 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -27,27 +27,25 @@ #include "../mwmechanics/npcstats.hpp" -namespace +namespace MWClass { - struct ContainerCustomData : public MWWorld::CustomData + class ContainerCustomData : public MWWorld::CustomData { + public: MWWorld::ContainerStore mContainerStore; virtual MWWorld::CustomData *clone() const; + + virtual ContainerCustomData& asContainerCustomData() + { + return *this; + } }; MWWorld::CustomData *ContainerCustomData::clone() const { return new ContainerCustomData (*this); } -} - -namespace MWClass -{ - std::string Container::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { @@ -74,6 +72,7 @@ namespace MWClass ptr.get(); if (ref->mBase->mFlags & ESM::Container::Respawn) { + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -103,11 +102,9 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Container::getModel(const MWWorld::Ptr &ptr) const + std::string Container::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -145,11 +142,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; @@ -189,10 +186,9 @@ namespace MWClass } } - std::string Container::getName (const MWWorld::Ptr& ptr) const + std::string Container::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -202,13 +198,12 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return ptr.getRefData().getCustomData()->asContainerCustomData().mContainerStore; } - std::string Container::getScript (const MWWorld::Ptr& ptr) const + std::string Container::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -220,18 +215,16 @@ namespace MWClass registerClass (typeid (ESM::Container).name(), instance); } - bool Container::hasToolTip (const MWWorld::Ptr& ptr) const + bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName; @@ -280,21 +273,22 @@ namespace MWClass ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } - bool Container::canLock(const MWWorld::Ptr &ptr) const + bool Container::canLock(const MWWorld::ConstPtr &ptr) const { return true; } - MWWorld::Ptr Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; const ESM::ContainerState& state2 = dynamic_cast (state); if (!ptr.getRefData().getCustomData()) @@ -308,13 +302,17 @@ namespace MWClass readState (state2.mInventory); } - void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const + void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::ContainerState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } - dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. writeState (state2.mInventory); } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 3541937d12..3add65a7e4 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -10,20 +10,16 @@ namespace MWClass void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -31,16 +27,16 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual float getCapacity (const MWWorld::Ptr& ptr) const; @@ -57,13 +53,13 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object - virtual bool canLock(const MWWorld::Ptr &ptr) const; + virtual bool canLock(const MWWorld::ConstPtr &ptr) const; virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. @@ -73,7 +69,7 @@ namespace MWClass virtual void restock (const MWWorld::Ptr &ptr) const; - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 450889eb2a..e9502d86b8 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -40,14 +40,33 @@ namespace { - struct CreatureCustomData : public MWWorld::CustomData + bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask) { + return (ptr.get()->mBase->mFlags & bitMask) != 0; + } +} + +namespace MWClass +{ + + class CreatureCustomData : public MWWorld::CustomData + { + public: MWMechanics::CreatureStats mCreatureStats; MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures MWMechanics::Movement mMovement; virtual MWWorld::CustomData *clone() const; + virtual CreatureCustomData& asCreatureCustomData() + { + return *this; + } + virtual const CreatureCustomData& asCreatureCustomData() const + { + return *this; + } + CreatureCustomData() : mContainerStore(0) {} virtual ~CreatureCustomData() { delete mContainerStore; } }; @@ -59,14 +78,6 @@ namespace return cloned; } - bool isFlagBitSet(const MWWorld::Ptr &ptr, ESM::Creature::Flags bitMask) - { - return (ptr.get()->mBase->mFlags & bitMask) != 0; - } -} - -namespace MWClass -{ const Creature::GMST& Creature::getGmst() { static GMST gmst; @@ -147,32 +158,22 @@ namespace MWClass // store ptr.getRefData().setCustomData(data.release()); - getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr)); + getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); if (hasInventory) getInventoryStore(ptr).autoEquip(ptr); } } - std::string Creature::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mId; - } - void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } - std::string Creature::getModel(const MWWorld::Ptr &ptr) const + std::string Creature::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert (ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -181,10 +182,9 @@ namespace MWClass return ""; } - std::string Creature::getName (const MWWorld::Ptr& ptr) const + std::string Creature::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -193,7 +193,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mCreatureStats; + return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats; } @@ -331,7 +331,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId()); if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { @@ -349,7 +349,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId()); if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); @@ -421,7 +421,7 @@ namespace MWClass { ensureCustomData (ptr); - return *dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const @@ -437,14 +437,14 @@ namespace MWClass return isFlagBitSet(ptr, ESM::Creature::Weapon); } - std::string Creature::getScript (const MWWorld::Ptr& ptr) const + std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - bool Creature::isEssential (const MWWorld::Ptr& ptr) const + bool Creature::isEssential (const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Essential); } @@ -511,13 +511,21 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; + return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement; } - MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::Ptr& ptr) const + bool Creature::hasToolTip(const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + if (!ptr.getRefData().getCustomData()) + return true; + + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); + return !customData.mCreatureStats.getAiSequence().isInCombat() || customData.mCreatureStats.isDead(); + } + + MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName; @@ -542,18 +550,18 @@ namespace MWClass return static_cast(stats.getAttribute(0).getModified() * 5); } - int Creature::getServices(const MWWorld::Ptr &actor) const + int Creature::getServices(const MWWorld::ConstPtr &actor) const { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); if (ref->mBase->mHasAI) return ref->mBase->mAiData.mServices; else return 0; } - bool Creature::isPersistent(const MWWorld::Ptr &actor) const + bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); return ref->mBase->mPersistent; } @@ -568,7 +576,7 @@ namespace MWClass MWWorld::LiveCellRef* ref = ptr.get(); - const std::string& ourId = (ref->mBase->mOriginal.empty()) ? getId(ptr) : ref->mBase->mOriginal; + const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; MWWorld::Store::iterator sound = store.begin(); while(sound != store.end()) @@ -587,31 +595,29 @@ namespace MWClass return ""; } - MWWorld::Ptr - Creature::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Creature::isBipedal(const MWWorld::Ptr &ptr) const + bool Creature::isBipedal(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, ESM::Creature::Bipedal); } - bool Creature::canFly(const MWWorld::Ptr &ptr) const + bool Creature::canFly(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, ESM::Creature::Flies); } - bool Creature::canSwim(const MWWorld::Ptr &ptr) const + bool Creature::canSwim(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Swims | ESM::Creature::Bipedal)); } - bool Creature::canWalk(const MWWorld::Ptr &ptr) const + bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Walks | ESM::Creature::Bipedal)); } @@ -674,7 +680,7 @@ namespace MWClass } } - int Creature::getBloodTexture(const MWWorld::Ptr &ptr) const + int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const { int flags = ptr.get()->mBase->mFlags; @@ -711,13 +717,13 @@ namespace MWClass else ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. - CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); customData.mContainerStore->readState (state2.mInventory); customData.mCreatureStats.readState (state2.mCreatureStats); } - void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::CreatureState& state2 = dynamic_cast (state); @@ -728,15 +734,13 @@ namespace MWClass return; } - ensureCustomData (ptr); - - CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); customData.mContainerStore->writeState (state2.mInventory); customData.mCreatureStats.writeState (state2.mCreatureStats); } - int Creature::getBaseGold(const MWWorld::Ptr& ptr) const + int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mData.mGold; } @@ -755,6 +759,7 @@ namespace MWClass // Reset to original position ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -768,15 +773,15 @@ namespace MWClass store.restock(list, ptr, ptr.getCellRef().getRefId()); } - int Creature::getBaseFightRating(const MWWorld::Ptr &ptr) const + int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } - void Creature::adjustScale(const MWWorld::Ptr &ptr, osg::Vec3f &scale) const + void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); scale *= ref->mBase->mScale; } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index c4ea09255e..cb89a53d62 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -14,8 +14,7 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name); @@ -41,17 +40,17 @@ namespace MWClass public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip(const MWWorld::ConstPtr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const; @@ -74,7 +73,7 @@ namespace MWClass virtual bool hasInventoryStore (const MWWorld::Ptr &ptr) const; - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual float getCapacity (const MWWorld::Ptr& ptr) const; @@ -84,12 +83,12 @@ namespace MWClass virtual float getArmorRating (const MWWorld::Ptr& ptr) const; ///< @return combined armor rating of this actor - virtual bool isEssential (const MWWorld::Ptr& ptr) const; + virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - virtual int getServices (const MWWorld::Ptr& actor) const; + virtual int getServices (const MWWorld::ConstPtr& actor) const; - virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; @@ -100,40 +99,39 @@ namespace MWClass static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual bool isActor() const { return true; } - virtual bool isBipedal (const MWWorld::Ptr &ptr) const; - virtual bool canFly (const MWWorld::Ptr &ptr) const; - virtual bool canSwim (const MWWorld::Ptr &ptr) const; - virtual bool canWalk (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::ConstPtr &ptr) const; + virtual bool canFly (const MWWorld::ConstPtr &ptr) const; + virtual bool canSwim (const MWWorld::ConstPtr &ptr) const; + virtual bool canWalk (const MWWorld::ConstPtr &ptr) const; virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const; + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const; + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. - virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr &ptr) const; - virtual int getBaseFightRating(const MWWorld::Ptr &ptr) const; + virtual int getBaseFightRating(const MWWorld::ConstPtr &ptr) const; - virtual void adjustScale(const MWWorld::Ptr& ptr, osg::Vec3f& scale) const; + virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 433e5fcea6..db2ba45bc7 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -7,31 +7,33 @@ #include "../mwworld/customdata.hpp" -namespace +namespace MWClass { - struct CreatureLevListCustomData : public MWWorld::CustomData + class CreatureLevListCustomData : public MWWorld::CustomData { + public: // actorId of the creature we spawned int mSpawnActorId; bool mSpawn; // Should a new creature be spawned? virtual MWWorld::CustomData *clone() const; + + virtual CreatureLevListCustomData& asCreatureLevListCustomData() + { + return *this; + } + virtual const CreatureLevListCustomData& asCreatureLevListCustomData() const + { + return *this; + } }; MWWorld::CustomData *CreatureLevListCustomData::clone() const { return new CreatureLevListCustomData (*this); } -} - -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 + std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } @@ -40,7 +42,7 @@ namespace MWClass { ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); customData.mSpawn = true; } @@ -55,7 +57,7 @@ namespace MWClass { ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); if (!customData.mSpawn) return; @@ -101,21 +103,29 @@ namespace MWClass void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; + const ESM::CreatureLevListState& state2 = dynamic_cast (state); ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); customData.mSpawnActorId = state2.mSpawnActorId; customData.mSpawn = state2.mSpawn; } - void CreatureLevList::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::CreatureLevListState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + + const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); state2.mSpawnActorId = customData.mSpawnActorId; state2.mSpawn = customData.mSpawn; } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 177aa72359..67a7858d87 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -11,10 +11,7 @@ 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; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -23,12 +20,10 @@ namespace MWClass virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const; + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const; + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. virtual void respawn (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 18c381e133..8d54dff5d0 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -28,27 +28,29 @@ #include "../mwmechanics/actorutil.hpp" -namespace +namespace MWClass { - struct DoorCustomData : public MWWorld::CustomData + class DoorCustomData : public MWWorld::CustomData { + public: int mDoorState; // 0 = nothing, 1 = opening, 2 = closing virtual MWWorld::CustomData *clone() const; + + virtual DoorCustomData& asDoorCustomData() + { + return *this; + } + virtual const DoorCustomData& asDoorCustomData() const + { + return *this; + } }; MWWorld::CustomData *DoorCustomData::clone() const { return new DoorCustomData (*this); } -} - -namespace MWClass -{ - std::string Door::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -60,12 +62,12 @@ namespace MWClass void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) { - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); if (customData.mDoorState > 0) { MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState); @@ -75,11 +77,9 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Door::getModel(const MWWorld::Ptr &ptr) const + std::string Door::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -88,10 +88,9 @@ namespace MWClass return ""; } - std::string Door::getName (const MWWorld::Ptr& ptr) const + std::string Door::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -114,11 +113,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; @@ -159,16 +158,19 @@ namespace MWClass boost::shared_ptr action(new MWWorld::ActionDoor(ptr)); int doorstate = getDoorState(ptr); bool opening = true; + float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2]; if (doorstate == 1) opening = false; - if (doorstate == 0 && ptr.getRefData().getLocalRotation().rot[2] != 0) + if (doorstate == 0 && doorRot != 0) opening = false; if (opening) { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, closeSound, 0.5f); - float offset = ptr.getRefData().getLocalRotation().rot[2]/ 3.14159265f * 2.0f; + // Doors rotate at 90 degrees per second, so start the sound at + // where it would be at the current rotation. + float offset = doorRot/(3.14159265f * 0.5f); action->setSoundOffset(offset); action->setSound(openSound); } @@ -176,10 +178,8 @@ namespace MWClass { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, openSound, 0.5f); - float offset = 1.0f - ptr.getRefData().getLocalRotation().rot[2]/ 3.14159265f * 2.0f; - //most if not all door have closing bang somewhere in the middle of the sound, - //so we divide offset by two - action->setSoundOffset(offset * 0.5f); + float offset = 1.0f - doorRot/(3.14159265f * 0.5f); + action->setSoundOffset(std::max(offset, 0.0f)); action->setSound(closeSound); } @@ -208,15 +208,14 @@ namespace MWClass ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } - bool Door::canLock(const MWWorld::Ptr &ptr) const + bool Door::canLock(const MWWorld::ConstPtr &ptr) const { return true; } - std::string Door::getScript (const MWWorld::Ptr& ptr) const + std::string Door::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -228,18 +227,16 @@ namespace MWClass registerClass (typeid (ESM::Door).name(), instance); } - bool Door::hasToolTip (const MWWorld::Ptr& ptr) const + bool Door::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName; @@ -300,13 +297,11 @@ namespace MWClass return "#{sCell=" + dest + "}"; } - MWWorld::Ptr - Door::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } void Door::ensureCustomData(const MWWorld::Ptr &ptr) const @@ -320,10 +315,11 @@ namespace MWClass } } - int Door::getDoorState (const MWWorld::Ptr &ptr) const + int Door::getDoorState (const MWWorld::ConstPtr &ptr) const { - ensureCustomData(ptr); - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (!ptr.getRefData().getCustomData()) + return 0; + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); return customData.mDoorState; } @@ -333,23 +329,29 @@ namespace MWClass throw std::runtime_error("load doors can't be moved"); ensureCustomData(ptr); - DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); customData.mDoorState = state; } void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; ensureCustomData(ptr); - DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); const ESM::DoorState& state2 = dynamic_cast(state); customData.mDoorState = state2.mDoorState; } - void Door::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const + void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - ensureCustomData(ptr); - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); ESM::DoorState& state2 = dynamic_cast(state); state2.mDoorState = customData.mDoorState; diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index db5a7f32a3..42aa6d64d3 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -11,20 +11,16 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -32,10 +28,10 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. static std::string getDestination (const MWWorld::LiveCellRef& door); @@ -47,17 +43,17 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object - virtual bool canLock(const MWWorld::Ptr &ptr) const; + virtual bool canLock(const MWWorld::ConstPtr &ptr) const; - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; /// 0 = nothing, 1 = opening, 2 = closing - virtual int getDoorState (const MWWorld::Ptr &ptr) const; + virtual int getDoorState (const MWWorld::ConstPtr &ptr) const; /// This does not actually cause the door to move. Use World::activateDoor instead. virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; @@ -66,7 +62,7 @@ namespace MWClass const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. }; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index c9e6e70f21..99a50a67a0 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -23,13 +23,6 @@ namespace MWClass { - std::string Ingredient::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mId; - } void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -43,11 +36,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const + std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -56,10 +47,9 @@ namespace MWClass return ""; } - std::string Ingredient::getName (const MWWorld::Ptr& ptr) const + std::string Ingredient::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -70,18 +60,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Ingredient::getScript (const MWWorld::Ptr& ptr) const + std::string Ingredient::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Ingredient::getValue (const MWWorld::Ptr& ptr) const + int Ingredient::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -103,39 +91,36 @@ namespace MWClass registerClass (typeid (ESM::Ingredient).name(), instance); } - std::string Ingredient::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Ingredient::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Up"); } - std::string Ingredient::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Ingredient::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Down"); } - std::string Ingredient::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Ingredient::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Ingredient::hasToolTip (const MWWorld::Ptr& ptr) const + bool Ingredient::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -179,25 +164,22 @@ namespace MWClass return info; } - MWWorld::Ptr - Ingredient::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Ingredient::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Ingredient::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Ingredient::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Ingredients) != 0; } - float Ingredient::getWeight(const MWWorld::Ptr &ptr) const + float Ingredient::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 69dd70743f..9a8bdacfb8 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Ingredient : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,16 +24,16 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) @@ -46,20 +42,20 @@ namespace MWClass static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index a70f311159..290ac4c26d 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -4,12 +4,8 @@ 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 + std::string ItemLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp index 2b507135fd..a8b313185c 100644 --- a/apps/openmw/mwclass/itemlevlist.hpp +++ b/apps/openmw/mwclass/itemlevlist.hpp @@ -9,10 +9,7 @@ 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; + virtual std::string getName (const MWWorld::ConstPtr& 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 34d93da678..c7ebc184fd 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -27,10 +27,6 @@ namespace MWClass { - std::string Light::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Light::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -59,11 +55,9 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Light::getModel(const MWWorld::Ptr &ptr) const + std::string Light::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert (ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -72,10 +66,9 @@ namespace MWClass return ""; } - std::string Light::getName (const MWWorld::Ptr& ptr) const + std::string Light::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mModel.empty()) return ""; @@ -96,18 +89,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Light::getScript (const MWWorld::Ptr& ptr) const + std::string Light::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Light::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Light::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; @@ -117,10 +108,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Light::getValue (const MWWorld::Ptr& ptr) const + int Light::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -132,40 +122,37 @@ namespace MWClass registerClass (typeid (ESM::Light).name(), instance); } - std::string Light::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Light::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Up"); } - std::string Light::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Light::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Down"); } - std::string Light::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Light::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Light::hasToolTip (const MWWorld::Ptr& ptr) const + bool Light::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -202,40 +189,36 @@ namespace MWClass ptr.getCellRef().setChargeFloat(duration); } - float Light::getRemainingUsageTime (const MWWorld::Ptr& ptr) const + float Light::getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ptr.getCellRef().getCharge() == -1) return static_cast(ref->mBase->mData.mTime); else return ptr.getCellRef().getChargeFloat(); } - MWWorld::Ptr - Light::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Light::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Light::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Lights) != 0; } - float Light::getWeight(const MWWorld::Ptr &ptr) const + float Light::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } - std::pair Light::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Light::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) return std::make_pair(0,""); @@ -260,7 +243,7 @@ namespace MWClass return std::make_pair(1,""); } - std::string Light::getSound(const MWWorld::Ptr& ptr) const + std::string Light::getSound(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mSound; } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 6161f18997..5ec21f41fd 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -7,52 +7,48 @@ namespace MWClass { class Light : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) @@ -62,18 +58,18 @@ namespace MWClass virtual void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const; ///< Sets the remaining duration of the object. - virtual float getRemainingUsageTime (const MWWorld::Ptr& ptr) const; + virtual float getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const; ///< Returns the remaining duration of the object. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; - virtual std::string getSound(const MWWorld::Ptr& ptr) const; + virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 5cffdf13a7..d3889d2fcd 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -21,10 +21,6 @@ namespace MWClass { - std::string Lockpick::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -38,11 +34,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const + std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -51,10 +45,9 @@ namespace MWClass return ""; } - std::string Lockpick::getName (const MWWorld::Ptr& ptr) const + std::string Lockpick::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -65,15 +58,14 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Lockpick::getScript (const MWWorld::Ptr& ptr) const + std::string Lockpick::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { std::vector slots_; @@ -82,10 +74,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Lockpick::getValue (const MWWorld::Ptr& ptr) const + int Lockpick::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -97,39 +88,36 @@ namespace MWClass registerClass (typeid (ESM::Lockpick).name(), instance); } - std::string Lockpick::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Lockpick::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Up"); } - std::string Lockpick::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Lockpick::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Down"); } - std::string Lockpick::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Lockpick::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Lockpick::hasToolTip (const MWWorld::Ptr& ptr) const + bool Lockpick::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -160,32 +148,28 @@ namespace MWClass return action; } - MWWorld::Ptr - Lockpick::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Lockpick::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Lockpick::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Picks) != 0; } - int Lockpick::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Lockpick::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } - float Lockpick::getWeight(const MWWorld::Ptr &ptr) const + float Lockpick::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 3f2c004f8f..f04674f5c0 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Lockpick : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,47 +24,47 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const { return true; } + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } ///< \return Item health data available? (default implementation: false) }; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 98b4faab9b..2f41fca303 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -26,7 +26,7 @@ namespace MWClass { - bool Miscellaneous::isGold (const MWWorld::Ptr& ptr) const + bool Miscellaneous::isGold (const MWWorld::ConstPtr& ptr) const { return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") @@ -34,10 +34,6 @@ namespace MWClass || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); } - std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -51,11 +47,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const + std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -64,10 +58,9 @@ namespace MWClass return ""; } - std::string Miscellaneous::getName (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -78,18 +71,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Miscellaneous::getScript (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Miscellaneous::getValue (const MWWorld::Ptr& ptr) const + int Miscellaneous::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int value = ref->mBase->mData.mValue; if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) @@ -111,47 +102,42 @@ namespace MWClass registerClass (typeid (ESM::Miscellaneous).name(), instance); } - std::string Miscellaneous::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getUpSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) return std::string("Item Gold Up"); return std::string("Item Misc Up"); } - std::string Miscellaneous::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getDownSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) return std::string("Item Gold Down"); return std::string("Item Misc Down"); } - std::string Miscellaneous::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Miscellaneous::hasToolTip (const MWWorld::Ptr& ptr) const + bool Miscellaneous::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - int count = ptr.getRefData().getCount(); - bool gold = isGold(ptr); if (gold) count *= getValue(ptr); @@ -189,8 +175,7 @@ namespace MWClass return info; } - MWWorld::Ptr - Miscellaneous::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Miscellaneous::copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const { MWWorld::Ptr newPtr; @@ -198,7 +183,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore(); if (isGold(ptr)) { - int goldAmount = getValue(ptr) * ptr.getRefData().getCount(); + int goldAmount = getValue(ptr) * count; std::string base = "Gold_001"; if (goldAmount >= 100) @@ -213,16 +198,20 @@ namespace MWClass // Really, I have no idea why moving ref out of conditional // scope causes list::push_back throwing std::bad_alloc MWWorld::ManualRef newRef(store, base); - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = newRef.getPtr().get(); - newPtr = MWWorld::Ptr(&cell.get().insert(*ref), &cell); + + newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getCellRef().setGoldValue(goldAmount); newPtr.getRefData().setCount(1); } else { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); - newPtr = MWWorld::Ptr(&cell.get().insert(*ref), &cell); + newPtr = MWWorld::Ptr(cell.insert(ref), &cell); + newPtr.getRefData().setCount(count); } + newPtr.getCellRef().unsetRefNum(); + return newPtr; } @@ -234,25 +223,22 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionSoulgem(ptr)); } - bool Miscellaneous::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Miscellaneous::canSell (const MWWorld::ConstPtr& item, int npcServices) const { - MWWorld::LiveCellRef *ref = - item.get(); + const MWWorld::LiveCellRef *ref = item.get(); return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) && !isGold(item); } - float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const + float Miscellaneous::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } - bool Miscellaneous::isKey(const MWWorld::Ptr &ptr) const + bool Miscellaneous::isKey(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mIsKey != 0; } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 394c9ffc01..2a534f058b 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Miscellaneous : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; - public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual MWWorld::Ptr copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,42 +24,42 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual bool isKey (const MWWorld::Ptr &ptr) const; + virtual bool isKey (const MWWorld::ConstPtr &ptr) const; - virtual bool isGold (const MWWorld::Ptr& ptr) const; + virtual bool isGold (const MWWorld::ConstPtr& ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 9fa2cc6037..57a6d088ab 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -42,19 +42,6 @@ namespace { - struct NpcCustomData : public MWWorld::CustomData - { - MWMechanics::NpcStats mNpcStats; - MWMechanics::Movement mMovement; - MWWorld::InventoryStore mInventoryStore; - - virtual MWWorld::CustomData *clone() const; - }; - - MWWorld::CustomData *NpcCustomData::clone() const - { - return new NpcCustomData (*this); - } int is_even(double d) { double int_part; @@ -251,6 +238,31 @@ namespace namespace MWClass { + + class NpcCustomData : public MWWorld::CustomData + { + public: + MWMechanics::NpcStats mNpcStats; + MWMechanics::Movement mMovement; + MWWorld::InventoryStore mInventoryStore; + + virtual MWWorld::CustomData *clone() const; + + virtual NpcCustomData& asNpcCustomData() + { + return *this; + } + virtual const NpcCustomData& asNpcCustomData() const + { + return *this; + } + }; + + MWWorld::CustomData *NpcCustomData::clone() const + { + return new NpcCustomData (*this); + } + const Npc::GMST& Npc::getGmst() { static GMST gmst; @@ -383,7 +395,7 @@ namespace MWClass // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items - data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr)); + data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); data->mNpcStats.setGoldPool(gold); @@ -394,30 +406,20 @@ namespace MWClass } } - std::string Npc::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mId; - } - void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { renderingInterface.getObjects().insertNPC(ptr); } - bool Npc::isPersistent(const MWWorld::Ptr &actor) const + bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); return ref->mBase->mPersistent; } - std::string Npc::getModel(const MWWorld::Ptr &ptr) const + std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); std::string model = "meshes\\base_anim.nif"; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -425,12 +427,11 @@ namespace MWClass model = "meshes\\base_animkna.nif"; return model; - } - std::string Npc::getName (const MWWorld::Ptr& ptr) const + std::string Npc::getName (const MWWorld::ConstPtr& ptr) const { - if(getNpcStats(ptr).isWerewolf()) + if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); @@ -438,7 +439,7 @@ namespace MWClass return store.find("sWerewolfPopup")->getString(); } - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -446,14 +447,14 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; + return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; + return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } @@ -594,7 +595,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId()); if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { @@ -612,7 +613,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId()); if (damage > 0.0f && !object.isEmpty()) @@ -780,7 +781,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) @@ -788,13 +789,12 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } - std::string Npc::getScript (const MWWorld::Ptr& ptr) const + std::string Npc::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -897,13 +897,12 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; + return ptr.getRefData().getCustomData()->asNpcCustomData().mMovement; } - bool Npc::isEssential (const MWWorld::Ptr& ptr) const + bool Npc::isEssential (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mFlags & ESM::NPC::Essential) != 0; } @@ -914,15 +913,24 @@ namespace MWClass registerClass (typeid (ESM::NPC).name(), instance); } - MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const + bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + if (!ptr.getRefData().getCustomData()) + return true; + + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); + return !customData.mNpcStats.getAiSequence().isInCombat() || customData.mNpcStats.isDead(); + } + + MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp(); MWGui::ToolTipInfo info; info.caption = getName(ptr); - if(fullHelp && getNpcStats(ptr).isWerewolf()) + if(fullHelp && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { info.caption += " ("; info.caption += ref->mBase->mName; @@ -1012,10 +1020,13 @@ namespace MWClass + shield; } - void Npc::adjustScale(const MWWorld::Ptr &ptr, osg::Vec3f&scale) const + void Npc::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f&scale, bool rendering) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + if (!rendering) + return; // collision meshes are not scaled based on race height + // having the same collision extents for all races makes the environments easier to test + + const MWWorld::LiveCellRef *ref = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -1035,9 +1046,9 @@ namespace MWClass } - int Npc::getServices(const MWWorld::Ptr &actor) const + int Npc::getServices(const MWWorld::ConstPtr &actor) const { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); if (ref->mBase->mHasAI) return ref->mBase->mAiData.mServices; else @@ -1091,7 +1102,7 @@ namespace MWClass if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return "DefaultLandWater"; if(world->isOnGround(ptr)) - return "Body Fall Medium"; + return "DefaultLand"; return ""; } if(name == "swimleft") @@ -1112,13 +1123,11 @@ namespace MWClass throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } - MWWorld::Ptr - Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const @@ -1126,9 +1135,9 @@ namespace MWClass return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified(); } - int Npc::getBloodTexture(const MWWorld::Ptr &ptr) const + int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mFlags & ESM::NPC::Skeleton) return 1; @@ -1157,14 +1166,14 @@ namespace MWClass else ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. - NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); customData.mInventoryStore.readState (state2.mInventory); customData.mNpcStats.readState (state2.mNpcStats); static_cast (customData.mNpcStats).readState (state2.mCreatureStats); } - void Npc::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::NpcState& state2 = dynamic_cast (state); @@ -1175,29 +1184,37 @@ namespace MWClass return; } - ensureCustomData (ptr); - - NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); customData.mInventoryStore.writeState (state2.mInventory); customData.mNpcStats.writeState (state2.mNpcStats); static_cast (customData.mNpcStats).writeState (state2.mCreatureStats); } - int Npc::getBaseGold(const MWWorld::Ptr& ptr) const + int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) return ref->mBase->mNpdt52.mGold; else return ref->mBase->mNpdt12.mGold; } - bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const + bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const { return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass, className); } + bool Npc::canSwim(const MWWorld::ConstPtr &ptr) const + { + return true; + } + + bool Npc::canWalk(const MWWorld::ConstPtr &ptr) const + { + return true; + } + void Npc::respawn(const MWWorld::Ptr &ptr) const { if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) @@ -1212,6 +1229,7 @@ namespace MWClass // Reset to original position ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -1225,26 +1243,26 @@ namespace MWClass store.restock(list, ptr, ptr.getCellRef().getRefId()); } - int Npc::getBaseFightRating (const MWWorld::Ptr& ptr) const + int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } - bool Npc::isBipedal(const MWWorld::Ptr &ptr) const + bool Npc::isBipedal(const MWWorld::ConstPtr &ptr) const { return true; } - std::string Npc::getPrimaryFaction (const MWWorld::Ptr& ptr) const + std::string Npc::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mFaction; } - int Npc::getPrimaryFactionRank (const MWWorld::Ptr& ptr) const + int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->getFactionRank(); } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d919131db9..5df34380a5 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -14,8 +14,7 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; struct GMST { @@ -45,13 +44,10 @@ namespace MWClass public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -64,7 +60,10 @@ namespace MWClass virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip(const MWWorld::ConstPtr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; @@ -80,7 +79,7 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual float getSpeed (const MWWorld::Ptr& ptr) const; @@ -109,28 +108,29 @@ namespace MWClass /// \param actor Actor that is resposible for the ID being applied to \a ptr. /// \return Any effect? - virtual void adjustScale (const MWWorld::Ptr &ptr, osg::Vec3f &scale) const; + virtual void adjustScale (const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh 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 bool isEssential (const MWWorld::Ptr& ptr) const; + virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - virtual int getServices (const MWWorld::Ptr& actor) const; + virtual int getServices (const MWWorld::ConstPtr& actor) const; - virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; virtual bool isActor() const { return true; @@ -144,32 +144,28 @@ namespace MWClass const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. - virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; - virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; - virtual bool canSwim (const MWWorld::Ptr &ptr) const { - return true; - } + virtual bool canSwim (const MWWorld::ConstPtr &ptr) const; - virtual bool canWalk (const MWWorld::Ptr &ptr) const { - return true; - } + virtual bool canWalk (const MWWorld::ConstPtr &ptr) const; - virtual bool isBipedal (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::ConstPtr &ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr& ptr) const; - virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; + virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; - virtual std::string getPrimaryFaction(const MWWorld::Ptr &ptr) const; - virtual int getPrimaryFactionRank(const MWWorld::Ptr &ptr) const; + virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const; + virtual int getPrimaryFactionRank(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index cf6b0919b3..40b4d62342 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -24,10 +24,6 @@ namespace MWClass { - std::string Potion::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -41,11 +37,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Potion::getModel(const MWWorld::Ptr &ptr) const + std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -54,10 +48,9 @@ namespace MWClass return ""; } - std::string Potion::getName (const MWWorld::Ptr& ptr) const + std::string Potion::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -68,18 +61,17 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Potion::getScript (const MWWorld::Ptr& ptr) const + std::string Potion::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Potion::getValue (const MWWorld::Ptr& ptr) const + int Potion::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -91,39 +83,36 @@ namespace MWClass registerClass (typeid (ESM::Potion).name(), instance); } - std::string Potion::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Potion::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Up"); } - std::string Potion::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Potion::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Down"); } - std::string Potion::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Potion::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Potion::hasToolTip (const MWWorld::Ptr& ptr) const + bool Potion::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -172,24 +161,21 @@ namespace MWClass return action; } - MWWorld::Ptr - Potion::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Potion::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Potion::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Potion::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Potions) != 0; } - float Potion::getWeight(const MWWorld::Ptr &ptr) const + float Potion::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 091d291953..9cdd58058d 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Potion : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,16 +24,16 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; @@ -45,20 +41,20 @@ namespace MWClass static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index ff717c5062..79d33ba60e 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -21,10 +21,6 @@ namespace MWClass { - std::string Probe::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -38,11 +34,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Probe::getModel(const MWWorld::Ptr &ptr) const + std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -51,10 +45,9 @@ namespace MWClass return ""; } - std::string Probe::getName (const MWWorld::Ptr& ptr) const + std::string Probe::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -64,15 +57,15 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Probe::getScript (const MWWorld::Ptr& ptr) const + std::string Probe::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Probe::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { std::vector slots_; @@ -81,10 +74,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Probe::getValue (const MWWorld::Ptr& ptr) const + int Probe::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -96,39 +88,36 @@ namespace MWClass registerClass (typeid (ESM::Probe).name(), instance); } - std::string Probe::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Probe::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Up"); } - std::string Probe::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Probe::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Down"); } - std::string Probe::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Probe::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Probe::hasToolTip (const MWWorld::Ptr& ptr) const + bool Probe::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -159,32 +148,28 @@ namespace MWClass return action; } - MWWorld::Ptr - Probe::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Probe::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Probe::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Probes) != 0; } - int Probe::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Probe::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } - float Probe::getWeight(const MWWorld::Ptr &ptr) const + float Probe::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index e39e43c270..4fc991a5ff 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Probe : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,47 +24,47 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const { return true; } + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } ///< \return Item health data available? (default implementation: false) }; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index e6baea2e0d..271f52bde9 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -20,10 +20,6 @@ namespace MWClass { - std::string Repair::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -37,11 +33,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Repair::getModel(const MWWorld::Ptr &ptr) const + std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -50,10 +44,9 @@ namespace MWClass return ""; } - std::string Repair::getName (const MWWorld::Ptr& ptr) const + std::string Repair::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -64,18 +57,17 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Repair::getScript (const MWWorld::Ptr& ptr) const + std::string Repair::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Repair::getValue (const MWWorld::Ptr& ptr) const + int Repair::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -87,52 +79,48 @@ namespace MWClass registerClass (typeid (ESM::Repair).name(), instance); } - std::string Repair::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Repair::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Up"); } - std::string Repair::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Repair::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Down"); } - std::string Repair::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Repair::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Repair::hasToolTip (const MWWorld::Ptr& ptr) const + bool Repair::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - bool Repair::hasItemHealth (const MWWorld::Ptr& ptr) const + bool Repair::hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } - int Repair::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Repair::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } - MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -154,13 +142,11 @@ namespace MWClass return info; } - MWWorld::Ptr - Repair::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Repair::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } boost::shared_ptr Repair::use (const MWWorld::Ptr& ptr) const @@ -168,15 +154,14 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRepair(ptr)); } - bool Repair::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Repair::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::RepairItem) != 0; } - float Repair::getWeight(const MWWorld::Ptr &ptr) const + float Repair::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 295b9d4f15..f0d1292f4d 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Repair : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,46 +24,46 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu (default implementation: return a /// null action). - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const; ///< \return Item health data available? (default implementation: false) - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 9755df28e5..65e69ea90a 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -11,10 +11,6 @@ namespace MWClass { - std::string Static::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -29,11 +25,9 @@ namespace MWClass physics.addObject(ptr, model); } - std::string Static::getModel(const MWWorld::Ptr &ptr) const + std::string Static::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -42,7 +36,7 @@ namespace MWClass return ""; } - std::string Static::getName (const MWWorld::Ptr& ptr) const + std::string Static::getName (const MWWorld::ConstPtr& ptr) const { return ""; } @@ -54,12 +48,10 @@ namespace MWClass registerClass (typeid (ESM::Static).name(), instance); } - MWWorld::Ptr - Static::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 3d78f949bc..076c39cf10 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -7,26 +7,22 @@ namespace MWClass { class Static : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index da4c7deb22..f3f8409215 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -22,12 +22,6 @@ namespace MWClass { - std::string Weapon::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - - return ref->mBase->mId; - } void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -41,11 +35,9 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects } - std::string Weapon::getModel(const MWWorld::Ptr &ptr) const + std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -54,10 +46,9 @@ namespace MWClass return ""; } - std::string Weapon::getName (const MWWorld::Ptr& ptr) const + std::string Weapon::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -68,34 +59,31 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - bool Weapon::hasItemHealth (const MWWorld::Ptr& ptr) const + bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mData.mType < 11); // thrown weapons and arrows/bolts don't have health, only quantity } - int Weapon::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mHealth; } - std::string Weapon::getScript (const MWWorld::Ptr& ptr) const + std::string Weapon::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; bool stack = false; @@ -116,10 +104,9 @@ namespace MWClass return std::make_pair (slots_, stack); } - int Weapon::getEquipmentSkill (const MWWorld::Ptr& ptr) const + int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); const int size = 12; @@ -146,10 +133,9 @@ namespace MWClass return -1; } - int Weapon::getValue (const MWWorld::Ptr& ptr) const + int Weapon::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -161,10 +147,9 @@ namespace MWClass registerClass (typeid (ESM::Weapon).name(), instance); } - std::string Weapon::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; // Ammo @@ -206,10 +191,9 @@ namespace MWClass return std::string("Item Misc Up"); } - std::string Weapon::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; // Ammo @@ -251,29 +235,26 @@ namespace MWClass return std::string("Item Misc Down"); } - std::string Weapon::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Weapon::hasToolTip (const MWWorld::Ptr& ptr) const + bool Weapon::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -355,18 +336,16 @@ namespace MWClass return info; } - std::string Weapon::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Weapon::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Weapon::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Weapon newItem = *ref->mBase; newItem.mId=""; @@ -378,7 +357,7 @@ namespace MWClass return record->mId; } - std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); @@ -411,33 +390,29 @@ namespace MWClass return action; } - MWWorld::Ptr - Weapon::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Weapon::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Weapon::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Weapon::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Weapon) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Weapon::getWeight(const MWWorld::Ptr &ptr) const + float Weapon::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 47c1157a02..5fc3983f25 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -8,19 +8,16 @@ namespace MWClass class Weapon : public MWWorld::Class { virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,50 +25,50 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const; ///< \return Item health data available? - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + virtual int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message @@ -79,13 +76,13 @@ namespace MWClass const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index f8c9bb38e4..c99e86c2d0 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -114,6 +114,8 @@ namespace MWDialogue void DialogueManager::startDialogue (const MWWorld::Ptr& actor) { + updateGlobals(); + // Dialogue with dead actor (e.g. through script) should not be allowed. if (actor.getClass().getCreatureStats(actor).isDead()) return; @@ -298,15 +300,18 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); - // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, - // in which case it should not be added to the journal. - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); - iter!=dialogue.mInfo.end(); ++iter) + if (dialogue.mType == ESM::Dialogue::Topic) { - if (iter->mId == info->mId) + // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, + // in which case it should not be added to the journal. + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); + iter!=dialogue.mInfo.end(); ++iter) { - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); - break; + if (iter->mId == info->mId) + { + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); + break; + } } } @@ -328,6 +333,8 @@ namespace MWDialogue void DialogueManager::updateTopics() { + updateGlobals(); + std::list keywordList; int choice = mChoice; mChoice = -1; @@ -414,8 +421,6 @@ namespace MWDialogue win->setKeywords(keywordList); mChoice = choice; - - updateGlobals(); } void DialogueManager::keywordSelected (const std::string& keyword) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index e3a773b052..43d979ea28 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -28,7 +28,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const // actor id if (!info.mActor.empty()) { - if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getClass().getId (mActor))) + if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getCellRef().getRefId())) return false; } else if (isCreature) @@ -41,7 +41,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!info.mRace.empty()) { if (isCreature) - return false; + return true; MWWorld::LiveCellRef *cellRef = mActor.get(); @@ -53,7 +53,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!info.mClass.empty()) { if (isCreature) - return false; + return true; MWWorld::LiveCellRef *cellRef = mActor.get(); @@ -65,7 +65,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!info.mFaction.empty()) { if (isCreature) - return false; + return true; if (!Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), info.mFaction)) return false; @@ -77,7 +77,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const else if (info.mData.mRank != -1) { if (isCreature) - return false; + return true; // Rank requirement, but no faction given. Use the actor's faction, if there is one. // check rank @@ -156,10 +156,8 @@ bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name())) // If the actor is a creature, we do not test the conditions applicable - // only to NPCs. Such conditions can never be satisfied, apart - // inverted ones (NotClass, NotRace, NotFaction return true - // because creatures are not of any race, class or faction). - return select.getType() == SelectWrapper::Type_Inverted; + // only to NPCs. + return true; switch (select.getType()) { @@ -313,7 +311,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con int value = 0; - for (int i=0; i<=15; ++i) // everything except thigns held in hands and amunition + for (int i=0; i<=15; ++i) // everything except things held in hands and ammunition { MWWorld::ContainerStoreIterator slot = store.getSlot (i); @@ -440,7 +438,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotId: - return !Misc::StringUtils::ciEqual(mActor.getClass().getId (mActor), select.getName()); + return !Misc::StringUtils::ciEqual(mActor.getCellRef().getRefId(), select.getName()); case SelectWrapper::Function_NotFaction: diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index c4e1d75538..3532dc22b8 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -2,7 +2,7 @@ #define GAME_MWDIALOGUE_KEYWORDSEARCH_H #include -#include +#include #include #include #include // std::reverse @@ -44,7 +44,7 @@ public: typename Entry::childen_t::iterator current; typename Entry::childen_t::iterator next; - current = mRoot.mChildren.find (std::tolower (*keyword.begin(), mLocale)); + current = mRoot.mChildren.find (Misc::StringUtils::toLower (*keyword.begin())); if (current == mRoot.mChildren.end()) return false; else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword)) @@ -55,7 +55,7 @@ public: for (Point i = ++keyword.begin(); i != keyword.end(); ++i) { - next = current->second.mChildren.find(std::tolower (*i, mLocale)); + next = current->second.mChildren.find(Misc::StringUtils::toLower (*i)); if (next == current->second.mChildren.end()) return false; if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword)) @@ -89,7 +89,7 @@ public: // check first character - typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale)); + typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); // no match, on to next character if (candidate == mRoot.mChildren.end ()) @@ -104,7 +104,7 @@ public: while ((j + 1) != end) { - typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale)); + typename Entry::childen_t::iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); if (next == candidate->second.mChildren.end ()) { @@ -136,7 +136,7 @@ public: while (k != end && t != candidate->second.mKeyword.end ()) { - if (std::tolower (*k, mLocale) != std::tolower (*t, mLocale)) + if (Misc::StringUtils::toLower (*k) != Misc::StringUtils::toLower (*t)) break; ++k, ++t; @@ -212,7 +212,7 @@ private: void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry) { - int ch = tolower (keyword.at (depth), mLocale); + int ch = Misc::StringUtils::toLower (keyword.at (depth)); typename Entry::childen_t::iterator j = entry.mChildren.find (ch); @@ -249,7 +249,6 @@ private: } Entry mRoot; - std::locale mLocale; }; } diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index a4eba30ae4..68c88e9437 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -263,10 +263,6 @@ bool MWDialogue::SelectWrapper::isNpcOnly() const { Function_NotFaction, Function_NotClass, Function_NotRace, Function_SameGender, Function_SameRace, Function_SameFaction, - Function_PcSkill, - Function_PcExpelled, - Function_PcVampire, - Function_PcCrimeLevel, Function_RankRequirement, Function_Reputation, Function_FactionRankDiff, Function_Werewolf, Function_WerewolfKills, diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index cfb49ebffb..b728b748f7 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -153,6 +153,34 @@ struct TypesetBookImpl : TypesetBook visitRuns (top, bottom, NULL, visitor); } + /// hit test with a margin for error. only hits on interactive text fragments are reported. + StyleImpl * hitTestWithMargin (int left, int top) + { + StyleImpl * hit = hitTest(left, top); + if (hit && hit->mInteractiveId > 0) + return hit; + + const int maxMargin = 10; + for (int margin=1; margin < maxMargin; ++margin) + { + for (int i=0; i<4; ++i) + { + if (i==0) + hit = hitTest(left, top-margin); + else if (i==1) + hit = hitTest(left, top+margin); + else if (i==2) + hit = hitTest(left-margin, top); + else + hit = hitTest(left+margin, top); + + if (hit && hit->mInteractiveId > 0) + return hit; + } + } + return NULL; + } + StyleImpl * hitTest (int left, int top) const { for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) @@ -916,15 +944,15 @@ public: left -= mCroppedParent->getAbsoluteLeft (); top -= mCroppedParent->getAbsoluteTop (); - Style * Hit = mBook->hitTest (left, mViewTop + top); + Style * hit = mBook->hitTestWithMargin (left, mViewTop + top); if (mLastDown == MyGUI::MouseButton::None) { - if (Hit != mFocusItem) + if (hit != mFocusItem) { dirtyFocusItem (); - mFocusItem = Hit; + mFocusItem = hit; mItemActive = false; dirtyFocusItem (); @@ -933,7 +961,7 @@ public: else if (mFocusItem != 0) { - bool newItemActive = Hit == mFocusItem; + bool newItemActive = hit == mFocusItem; if (newItemActive != mItemActive) { @@ -949,12 +977,18 @@ public: if (!mBook) return; - left -= mCroppedParent->getAbsoluteLeft (); - top -= mCroppedParent->getAbsoluteTop (); + // work around inconsistency in MyGUI where the mouse press coordinates aren't + // transformed by the current Layer (even though mouse *move* events are). + MyGUI::IntPoint pos (left, top); +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) + pos = mNode->getLayer()->getPosition(left, top); +#endif + pos.left -= mCroppedParent->getAbsoluteLeft (); + pos.top -= mCroppedParent->getAbsoluteTop (); if (mLastDown == MyGUI::MouseButton::None) { - mFocusItem = mBook->hitTest (left, mViewTop + top); + mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); mItemActive = true; dirtyFocusItem (); @@ -968,14 +1002,21 @@ public: if (!mBook) return; - left -= mCroppedParent->getAbsoluteLeft (); - top -= mCroppedParent->getAbsoluteTop (); + // work around inconsistency in MyGUI where the mouse release coordinates aren't + // transformed by the current Layer (even though mouse *move* events are). + MyGUI::IntPoint pos (left, top); +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) + pos = mNode->getLayer()->getPosition(left, top); +#endif + + pos.left -= mCroppedParent->getAbsoluteLeft (); + pos.top -= mCroppedParent->getAbsoluteTop (); if (mLastDown == id) { - Style * mItem = mBook->hitTest (left, mViewTop + top); + Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); - bool clicked = mFocusItem == mItem; + bool clicked = mFocusItem == item; mItemActive = false; @@ -983,8 +1024,8 @@ public: mLastDown = MyGUI::MouseButton::None; - if (clicked && mLinkClicked && mItem && mItem->mInteractiveId != 0) - mLinkClicked (mItem->mInteractiveId); + if (clicked && mLinkClicked && item && item->mInteractiveId != 0) + mLinkClicked (item->mInteractiveId); } } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 761f7d1642..1777d86ad4 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -365,7 +365,7 @@ namespace MWGui /* Is the beginning of the string different from the input string? If yes skip it. */ for( std::string::iterator iter=tmp.begin(), iter2=(*it).begin(); iter < tmp.end();++iter, ++iter2) { - if( tolower(*iter) != tolower(*iter2) ) { + if( Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2) ) { string_different=true; break; } @@ -405,7 +405,7 @@ namespace MWGui for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) { for(std::vector::iterator it=matches.begin(); it < matches.end();++it) { - if( tolower((*it)[i]) != tolower(*iter) ) { + if( Misc::StringUtils::toLower((*it)[i]) != Misc::StringUtils::toLower(*iter) ) { /* Append the longest match to the end of the output string*/ output.append(matches.front().substr( 0, i)); return output; diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 6adef5eeb4..6a8145e970 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -129,7 +129,7 @@ namespace MWGui size_t tagNameEndPos = tag.find(' '); mAttributes.clear(); mTag = tag.substr(0, tagNameEndPos); - Misc::StringUtils::toLower(mTag); + Misc::StringUtils::lowerCaseInPlace(mTag); if (mTag.empty()) return; @@ -151,7 +151,7 @@ namespace MWGui return; std::string key = tag.substr(0, sepPos); - Misc::StringUtils::toLower(key); + Misc::StringUtils::lowerCaseInPlace(key); tag.erase(0, sepPos+1); std::string value; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a93bb47e9d..cf0fa3414a 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -67,7 +67,7 @@ namespace MWGui }; - HUD::HUD(CustomMarkerCollection &customMarkers, bool showFps, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) + HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : Layout("openmw_hud.layout") , LocalMapBase(customMarkers, localMapRender) , mHealth(NULL) @@ -85,8 +85,6 @@ namespace MWGui , mCellNameBox(NULL) , mDrowningFrame(NULL) , mDrowningFlash(NULL) - , mFpsBox(NULL) - , mFpsCounter(NULL) , mHealthManaStaminaBaseLeft(0) , mWeapBoxBaseLeft(0) , mSpellBoxBaseLeft(0) @@ -161,8 +159,6 @@ namespace MWGui getWidget(mCrosshair, "Crosshair"); - setFpsVisible(showFps); - LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map")); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); @@ -181,28 +177,6 @@ namespace MWGui delete mSpellIcons; } - void HUD::setFpsVisible(const bool visible) - { - mFpsCounter = 0; - - MyGUI::Widget* fps; - getWidget(fps, "FPSBox"); - fps->setVisible(false); - - if (visible) - { - getWidget(mFpsBox, "FPSBox"); - //mFpsBox->setVisible(true); - getWidget(mFpsCounter, "FPSCounter"); - } - } - - void HUD::setFPS(float fps) - { - if (mFpsCounter) - mFpsCounter->setCaption(MyGUI::utility::toString((int)fps)); - } - void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { int current = std::max(0, static_cast(value.getCurrent())); @@ -222,7 +196,7 @@ namespace MWGui mMagicka->setProgressRange (modified); mMagicka->setProgressPosition (current); getWidget(w, "MagickaFrame"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 629613c982..c5ae457891 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -19,10 +19,9 @@ namespace MWGui class HUD : public Layout, public LocalMapBase { public: - HUD(CustomMarkerCollection& customMarkers, bool fpsVisible, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender); + HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender); virtual ~HUD(); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); - void setFPS(float fps); /// Set time left for the player to start drowning /// @param time time left to start drowning @@ -38,8 +37,6 @@ namespace MWGui void setEffectVisible(bool visible); void setMinimapVisible(bool visible); - void setFpsVisible(const bool visible); - void setSelectedSpell(const std::string& spellId, int successChancePercent); void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); @@ -77,9 +74,6 @@ namespace MWGui MyGUI::TextBox* mWeaponSpellBox; MyGUI::Widget *mDrowningFrame, *mDrowningFlash; - MyGUI::Widget* mFpsBox; - MyGUI::TextBox* mFpsCounter; - // bottom left elements int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; // bottom right elements diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index c1e202bc02..7678cb006c 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -167,13 +167,15 @@ namespace MWGui MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height)); + bool needUpdate = (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()); + mMainWidget->setPosition(pos); mMainWidget->setSize(size); - if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) - updatePreviewSize(); - adjustPanes(); + + if (needUpdate) + updatePreviewSize(); } SortFilterItemModel* InventoryWindow::getSortFilterModel() @@ -471,16 +473,18 @@ namespace MWGui MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); } - if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) + mSkippedToEquip = MWWorld::Ptr(); + if (ptr.getRefData().getCount()) // make sure the item is still there, the script might have removed it { - boost::shared_ptr action = ptr.getClass().use(ptr); - - action->execute (player); + if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) + { + boost::shared_ptr action = ptr.getClass().use(ptr); - mSkippedToEquip = MWWorld::Ptr(); + action->execute (player); + } + else + mSkippedToEquip = ptr; } - else - mSkippedToEquip = ptr; if (isVisible()) { diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index a1e36bce67..2c382f3cf4 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -35,7 +35,7 @@ namespace MWGui { const ESM::GameSetting ¤tSetting = *currentIteration; std::string currentGMSTID = currentSetting.mId; - Misc::StringUtils::toLower(currentGMSTID); + Misc::StringUtils::lowerCaseInPlace(currentGMSTID); // Don't bother checking this GMST if it's not a sMagicBound* one. const std::string& toFind = "smagicbound"; @@ -44,7 +44,7 @@ namespace MWGui // All sMagicBound* GMST's should be of type string std::string currentGMSTValue = currentSetting.getString(); - Misc::StringUtils::toLower(currentGMSTValue); + Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); boundItemIDCache.insert(currentGMSTValue); } @@ -52,7 +52,7 @@ namespace MWGui // Perform bound item check and assign the Flag_Bound bit if it passes std::string tempItemID = base.getCellRef().getRefId(); - Misc::StringUtils::toLower(tempItemID); + Misc::StringUtils::lowerCaseInPlace(tempItemID); if (boundItemIDCache.count(tempItemID) != 0) mFlags |= Flag_Bound; diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index d05257e46c..70f1305e0a 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -29,8 +29,6 @@ struct JournalViewModelImpl : JournalViewModel mutable bool mKeywordSearchLoaded; mutable KeywordSearchT mKeywordSearch; - std::locale mLocale; - JournalViewModelImpl () { mKeywordSearchLoaded = false; @@ -74,8 +72,6 @@ struct JournalViewModelImpl : JournalViewModel } } - wchar_t tolower (wchar_t ch) const { return std::tolower (ch, mLocale); } - bool isEmpty () const { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); @@ -319,7 +315,7 @@ struct JournalViewModelImpl : JournalViewModel for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) { - if (i->first [0] != std::tolower (character, mLocale)) + if (i->first [0] != Misc::StringUtils::toLower(character)) continue; visitor (i->second.getName()); diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index b6f72a04cb..9af87c7ae3 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -96,7 +96,7 @@ namespace return getWidget (name); } - JournalWindowImpl (MWGui::JournalViewModel::Ptr Model) + JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList) : WindowBase("openmw_journal.layout"), JournalBooks (Model) { mMainWidget->setVisible(false); @@ -142,17 +142,17 @@ namespace getPage (RightTopicIndex)->adviseLinkClicked (callback); } - adjustButton(OptionsBTN, true); adjustButton(PrevPageBTN); adjustButton(NextPageBTN); adjustButton(CloseBTN); adjustButton(CancelBTN); - adjustButton(ShowAllBTN, true); - adjustButton(ShowActiveBTN, true); adjustButton(JournalBTN); Gui::ImageButton* optionsButton = getWidget(OptionsBTN); - if (optionsButton->getWidth() == 0) + Gui::ImageButton* showActiveButton = getWidget(ShowActiveBTN); + Gui::ImageButton* showAllButton = getWidget(ShowAllBTN); + Gui::ImageButton* questsButton = getWidget(QuestsBTN); + if (!questList) { // 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 @@ -162,6 +162,23 @@ namespace topicsButton->setPosition(optionsButton->getPosition()); topicsButton->eventMouseButtonClick.clear(); topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); + + optionsButton->setVisible(false); + showActiveButton->setVisible(false); + showAllButton->setVisible(false); + questsButton->setVisible(false); + } + else + { + optionsButton->setImage("textures/tx_menubook_options.dds"); + showActiveButton->setImage("textures/tx_menubook_quests_active.dds"); + showAllButton->setImage("textures/tx_menubook_quests_all.dds"); + questsButton->setImage("textures/tx_menubook_quests.dds"); + + adjustButton(ShowAllBTN); + adjustButton(ShowActiveBTN); + adjustButton(OptionsBTN); + adjustButton(QuestsBTN); } Gui::ImageButton* nextButton = getWidget(NextPageBTN); @@ -173,7 +190,6 @@ namespace } adjustButton(TopicsBTN); - adjustButton(QuestsBTN, true); int width = getWidget(TopicsBTN)->getSize().width + getWidget(QuestsBTN)->getSize().width; int topicsWidth = getWidget(TopicsBTN)->getSize().width; int pageWidth = getWidget(RightBookPage)->getSize().width; @@ -186,12 +202,12 @@ namespace mOptionsMode = false; } - void adjustButton (char const * name, bool optional = false) + void adjustButton (char const * name) { Gui::ImageButton* button = getWidget(name); - MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(!optional); - button->setSize(button->getRequestedSize(!optional)); + MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(); + button->setSize(button->getRequestedSize()); if (button->getAlign().isRight()) button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); @@ -551,7 +567,7 @@ namespace } // glue the implementation to the interface -MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model) +MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList) { - return new JournalWindowImpl (Model); + return new JournalWindowImpl (Model, questList); } diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index 5d2a5318ac..740284e203 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -12,7 +12,7 @@ namespace MWGui struct JournalWindow { /// construct a new instance of the one JournalWindow implementation - static JournalWindow * create (boost::shared_ptr Model); + static JournalWindow * create (boost::shared_ptr Model, bool questList); /// destroy this instance of the JournalWindow implementation virtual ~JournalWindow () {}; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 9afce68735..e32140cb35 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -36,6 +36,7 @@ namespace MWGui , mLastWallpaperChangeTime(0.0) , mLastRenderTime(0.0) , mLoadingOnTime(0.0) + , mImportantLabel(false) , mProgress(0) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); @@ -82,8 +83,10 @@ namespace MWGui std::cerr << "No splash screens found!" << std::endl; } - void LoadingScreen::setLabel(const std::string &label) + void LoadingScreen::setLabel(const std::string &label, bool important) { + mImportantLabel = important; + mLoadingText->setCaptionWithReplacing(label); int padding = mLoadingBox->getWidth() - mLoadingText->getWidth(); MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); @@ -176,6 +179,19 @@ namespace MWGui void LoadingScreen::loadingOff() { + if (mLastRenderTime < mLoadingOnTime) + { + // the loading was so fast that we didn't show loading screen at all + // we may still want to show the label if the caller requested it + if (mImportantLabel) + { + MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption()); + mImportantLabel = false; + } + } + else + mImportantLabel = false; // label was already shown on loading screen + //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; setVisible(false); @@ -209,6 +225,7 @@ namespace MWGui // skip expensive update if there isn't enough visible progress if (value - mProgress < mProgressBar->getScrollRange()/200.f) return; + value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); @@ -219,22 +236,12 @@ namespace MWGui { mProgressBar->setScrollPosition(0); size_t value = mProgress + increase; + value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); draw(); } - void LoadingScreen::indicateProgress() - { - float time = (static_cast(mTimer.time_m()) % 2001) / 1000.f; - if (time > 1) - time = (time-2)*-1; - - mProgressBar->setTrackSize(50); - mProgressBar->setScrollPosition(static_cast(time * (mProgressBar->getScrollRange() - 1))); - draw(); - } - bool LoadingScreen::needToDrawLoadingScreen() { if ( mTimer.time_m() <= mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0) @@ -282,7 +289,13 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true, true); //osg::Timer timer; - mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); //std::cout << "frame took " << timer.time_m() << std::endl; //if (mViewer->getIncrementalCompileOperation()) diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index f0f3542233..a7f7d3619c 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -33,23 +33,16 @@ namespace MWGui LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer); virtual ~LoadingScreen(); - virtual void setLabel (const std::string& label); - - /// Indicate that some progress has been made, without specifying how much - virtual void indicateProgress (); - + /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details + virtual void setLabel (const std::string& label, bool important); virtual void loadingOn(); virtual void loadingOff(); - virtual void setProgressRange (size_t range); virtual void setProgress (size_t value); virtual void increaseProgress (size_t increase=1); virtual void setVisible(bool visible); - void setLoadingProgress (const std::string& stage, int depth, int current, int total); - void loadingDone(); - private: void findSplashScreens(); bool needToDrawLoadingScreen(); @@ -64,6 +57,8 @@ namespace MWGui osg::Timer mTimer; double mLoadingOnTime; + bool mImportantLabel; + size_t mProgress; MyGUI::Widget* mLoadingBox; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 2df1c8f3c2..0ebb595ddf 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -746,7 +746,7 @@ namespace MWGui mGlobalMapTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); - mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index c647ecaf52..f8ddeba3ee 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -117,8 +117,11 @@ namespace MWGui bool MessageBoxManager::createInteractiveMessageBox (const std::string& message, const std::vector& buttons) { - if(mInterMessageBoxe != NULL) { - throw std::runtime_error("There is a message box already"); + if (mInterMessageBoxe != NULL) + { + std::cerr << "Warning: replacing an interactive message box that was not answered yet" << std::endl; + delete mInterMessageBoxe; + mInterMessageBoxe = NULL; } mInterMessageBoxe = new InteractiveMessageBox(*this, message, buttons); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index fa07f10207..d7d6c3cdb1 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -154,7 +154,7 @@ namespace MWGui { mMagicka->setValue(static_cast(value.getCurrent()), static_cast(value.getModified())); std::string valStr = MyGUI::utility::toString(value.getCurrent()) + "/" + MyGUI::utility::toString(value.getModified()); - mMagicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + mMagicka->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 816fc0fa81..53c2807374 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -360,12 +360,17 @@ namespace MWGui timeinfo = localtime(&time); // Use system/environment locale settings for datetime formatting + char* oldLctime = setlocale(LC_TIME, NULL); setlocale(LC_TIME, ""); const int size=1024; char buffer[size]; if (std::strftime(buffer, size, "%x %X", timeinfo) > 0) text << buffer << "\n"; + + // reset + setlocale(LC_TIME, oldLctime); + text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n"; diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 473776a82a..be0346e882 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -1,6 +1,7 @@ #include "screenfader.hpp" #include +#include namespace MWGui { @@ -66,20 +67,25 @@ namespace MWGui mFader->notifyOperationFinished(); } - ScreenFader::ScreenFader(const std::string & texturePath) - : WindowBase("openmw_screen_fader.layout") + ScreenFader::ScreenFader(const std::string & texturePath, const std::string& layout, const MyGUI::FloatCoord& texCoordOverride) + : WindowBase(layout) , mCurrentAlpha(0.f) , mFactor(1.f) , mRepeat(false) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - setTexture(texturePath); setVisible(false); - } - void ScreenFader::setTexture(const std::string & texturePath) - { - mMainWidget->setProperty("ImageTexture", texturePath); + MyGUI::ImageBox* imageBox = mMainWidget->castType(false); + if (imageBox) + { + imageBox->setImageTexture(texturePath); + const MyGUI::IntSize imageSize = imageBox->getImageSize(); + imageBox->setImageCoord(MyGUI::IntCoord(texCoordOverride.left * imageSize.width, + texCoordOverride.top * imageSize.height, + texCoordOverride.width * imageSize.width, + texCoordOverride.height * imageSize.height)); + } } void ScreenFader::update(float dt) diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp index 402554555e..f87fd84432 100644 --- a/apps/openmw/mwgui/screenfader.hpp +++ b/apps/openmw/mwgui/screenfader.hpp @@ -36,9 +36,7 @@ namespace MWGui class ScreenFader : public WindowBase { public: - ScreenFader(const std::string & texturePath); - - void setTexture(const std::string & texturePath); + ScreenFader(const std::string & texturePath, const std::string& layout = "openmw_screen_fader.layout", const MyGUI::FloatCoord& texCoordOverride = MyGUI::FloatCoord(0,0,1,1)); void update(float dt); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3ab2a6ce3f..fbfbd0e702 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -35,12 +35,13 @@ namespace return "#{sOn}"; } - std::string textureFilteringToStr(const std::string& val) + std::string textureMipmappingToStr(const std::string& val) { - if (val == "trilinear") - return "Trilinear"; - else - return "Bilinear"; + if (val == "linear") return "Trilinear"; + if (val == "nearest") return "Bilinear"; + if (val != "none") + std::cerr<< "Invalid texture mipmap option: "<eventTabChangeSelect += MyGUI::newDelegate(this, &SettingsWindow::onTabChanged); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); - mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); + mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); + mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); @@ -235,23 +236,28 @@ namespace MWGui } highlightCurrentResolution(); - std::string tf = Settings::Manager::getString("texture filtering", "General"); - mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); + std::string tmip = Settings::Manager::getString("texture mipmap", "General"); + mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip)); mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(Settings::Manager::getInt("anisotropy", "General")) + ")"); + int waterTextureSize = Settings::Manager::getInt ("rtt size", "Water"); + if (waterTextureSize >= 512) + mWaterTextureSize->setIndexSelected(0); + if (waterTextureSize >= 1024) + mWaterTextureSize->setIndexSelected(1); + if (waterTextureSize >= 2048) + mWaterTextureSize->setIndexSelected(2); + mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); if (!Settings::Manager::getBool("shaders", "Objects")) { - mRefractionButton->setEnabled(false); mShadowsEnabledButton->setEnabled(false); } - mFPSButton->setCaptionWithReplacing(fpsLevelToStr(Settings::Manager::getInt("fps", "HUD"))); - MyGUI::TextBox* fovText; getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "General"))) + ")"); + fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "Camera"))) + ")"); MyGUI::TextBox* diffText; getWidget(diffText, "DifficultyText"); @@ -324,6 +330,19 @@ namespace MWGui } } + void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos) + { + int size = 0; + if (pos == 0) + size = 512; + else if (pos == 1) + size = 1024; + else if (pos == 2) + size = 2048; + Settings::Manager::setInt("rtt size", "Water", size); + apply(); + } + void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) { Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos)); @@ -350,12 +369,6 @@ namespace MWGui { if (newState == false) { - // refraction needs shaders to display underwater fog - mRefractionButton->setCaptionWithReplacing("#{sOff}"); - mRefractionButton->setEnabled(false); - - Settings::Manager::setBool("refraction", "Water", false); - // shadows not supported mShadowsEnabledButton->setEnabled(false); mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); @@ -363,9 +376,6 @@ namespace MWGui } else { - // re-enable - mRefractionButton->setEnabled(true); - mShadowsEnabledButton->setEnabled(true); } } @@ -414,17 +424,14 @@ namespace MWGui } } - void SettingsWindow::onFpsToggled(MyGUI::Widget* _sender) - { - int newLevel = (Settings::Manager::getInt("fps", "HUD") + 1) % 2; - Settings::Manager::setInt("fps", "HUD", newLevel); - mFPSButton->setCaptionWithReplacing(fpsLevelToStr(newLevel)); - apply(); - } - void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos) { - Settings::Manager::setString("texture filtering", "General", Misc::StringUtils::lowerCase(_sender->getItemNameAt(pos))); + if(pos == 0) + Settings::Manager::setString("texture mipmap", "General", "nearest"); + else if(pos == 1) + Settings::Manager::setString("texture mipmap", "General", "linear"); + else + std::cerr<< "Unexpected option pos "<getStore().get().find (iter->first); + const ESM::Spell* spell = iter->first; if (spell->mData.mType!=ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers @@ -110,10 +109,10 @@ namespace MWGui continue; } - if (playerHasSpell(iter->first)) + if (playerHasSpell(iter->first->mId)) continue; - addSpell (iter->first); + addSpell (iter->first->mId); } updateLabels(); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index ff5a27abe2..64d4d86c68 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -516,8 +516,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find (it->first); + const ESM::Spell* spell = it->first; // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 58ec057948..c775330084 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -52,7 +52,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = esmStore.get().find(it->first); + const ESM::Spell* spell = it->first; if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; @@ -67,9 +67,9 @@ namespace MWGui } else newSpell.mType = Spell::Type_Power; - newSpell.mId = it->first; + newSpell.mId = spell->mId; - newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == it->first); + newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == spell->mId); newSpell.mActive = true; mSpells.push_back(newSpell); } @@ -93,7 +93,7 @@ namespace MWGui Spell newSpell; newSpell.mItem = item; - newSpell.mId = item.getClass().getId(item); + newSpell.mId = item.getCellRef().getRefId(); newSpell.mName = item.getClass().getName(item); newSpell.mType = Spell::Type_EnchantedItem; newSpell.mSelected = invStore.getSelectedEnchantItem() == it; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index efbbeb29ae..e66e012386 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -159,7 +159,7 @@ namespace MWGui else if (id == "MBar") { getWidget(w, "Magicka"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 7c7f951af1..cb3d0d0a1f 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -87,9 +87,9 @@ namespace MWGui return; } - bool gameMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); + bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - if (gameMode) + if (guiMode) { const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); @@ -112,10 +112,10 @@ namespace MWGui if (info.caption.empty()) info.caption=mFocusObject.getCellRef().getRefId(); info.icon=""; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, true); } else - tooltipSize = getToolTipViaPtr(true); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); position(tooltipPosition, tooltipSize, viewSize); @@ -178,26 +178,22 @@ namespace MWGui ToolTipInfo info; info.text = data.caption; info.notes = data.notes; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, false); } else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); - tooltipSize = getToolTipViaPtr(false); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); } else if (type == "ItemModelIndex") { std::pair pair = *focus->getUserData >(); mFocusObject = pair.second->getItem(pair.first).mBase; - // HACK: To get the correct count for multiple item stack sources - int oldCount = mFocusObject.getRefData().getCount(); - mFocusObject.getRefData().setCount(pair.second->getItem(pair.first).mCount); - tooltipSize = getToolTipViaPtr(false); - mFocusObject.getRefData().setCount(oldCount); + tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false); } else if (type == "ToolTipInfo") { - tooltipSize = createToolTip(*focus->getUserData()); + tooltipSize = createToolTip(*focus->getUserData(), false); } else if (type == "AvatarItemSelection") { @@ -207,7 +203,7 @@ namespace MWGui mFocusObject = item; if (!mFocusObject.isEmpty ()) - tooltipSize = getToolTipViaPtr(false); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); } else if (type == "Spell") { @@ -240,7 +236,7 @@ namespace MWGui info.text = "#{sSchool}: " + sSchoolNames[school]; } info.effects = effects; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, false); } else if (type == "Layout") { @@ -294,7 +290,7 @@ namespace MWGui { if (!mFocusObject.isEmpty()) { - MyGUI::IntSize tooltipSize = getToolTipViaPtr(); + MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount()); setCoord(viewSize.width/2 - tooltipSize.width/2, std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), @@ -321,12 +317,12 @@ namespace MWGui } } - void ToolTips::setFocusObject(const MWWorld::Ptr& focus) + void ToolTips::setFocusObject(const MWWorld::ConstPtr& focus) { mFocusObject = focus; } - MyGUI::IntSize ToolTips::getToolTipViaPtr (bool image) + MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image) { // this the maximum width of the tooltip before it starts word-wrapping setCoord(0, 0, 300, 300); @@ -342,10 +338,10 @@ namespace MWGui { mDynamicToolTipBox->setVisible(true); - ToolTipInfo info = object.getToolTipInfo(mFocusObject); + ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); if (!image) info.icon = ""; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, true); } return tooltipSize; @@ -370,13 +366,13 @@ namespace MWGui } } - MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) + MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isFocusObject) { mDynamicToolTipBox->setVisible(true); if(mShowOwned == 1 || mShowOwned == 3) { - if(checkOwned()) + if(isFocusObject && checkOwned()) { mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp_Owned"); } diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 983b50fe2f..1fc736bffd 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -58,7 +58,7 @@ namespace MWGui void setDelay(float delay); - void setFocusObject(const MWWorld::Ptr& focus); + void setFocusObject(const MWWorld::ConstPtr& focus); void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); ///< set the screen-space position of the tooltip for focused object @@ -93,13 +93,14 @@ namespace MWGui private: MyGUI::Widget* mDynamicToolTipBox; - MWWorld::Ptr mFocusObject; + MWWorld::ConstPtr mFocusObject; - MyGUI::IntSize getToolTipViaPtr (bool image=true); + MyGUI::IntSize getToolTipViaPtr (int count, bool image=true); ///< @return requested tooltip size - MyGUI::IntSize createToolTip(const ToolTipInfo& info); + MyGUI::IntSize createToolTip(const ToolTipInfo& info, bool isFocusObject); ///< @return requested tooltip size + /// @param isFocusObject Is the object this tooltips originates from mFocusObject? float mFocusToolTipX; float mFocusToolTipY; diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 191e8223d8..c9d1b06175 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -49,11 +49,13 @@ void WindowBase::center() { // Centre dialog - MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntSize layerSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (mMainWidget->getLayer()) + layerSize = mMainWidget->getLayer()->getSize(); MyGUI::IntCoord coord = mMainWidget->getCoord(); - coord.left = (gameWindowSize.width - coord.width)/2; - coord.top = (gameWindowSize.height - coord.height)/2; + coord.left = (layerSize.width - coord.width)/2; + coord.top = (layerSize.height - coord.height)/2; mMainWidget->setCoord(coord); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 90a5f74817..f151bee80b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -33,6 +33,9 @@ #include #include +#include +#include +#include #include @@ -114,7 +117,8 @@ namespace MWGui osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem , const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription) - : mResourceSystem(resourceSystem) + : mStore(NULL) + , mResourceSystem(resourceSystem) , mViewer(viewer) , mConsoleOnlyScripts(consoleOnlyScripts) , mCurrentModals() @@ -185,7 +189,6 @@ namespace MWGui , mForceHidden(GW_None) , mAllowed(GW_ALL) , mRestAllowed(true) - , mFPS(0.0f) , mFallbackMap(fallbackMap) , mShowOwned(0) , mVersionDescription(versionDescription) @@ -216,6 +219,8 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Layer"); + MyGUI::FactoryManager::getInstance().registerFactory("Layer"); BookPage::registerMyGUIComponents (); ItemView::registerComponents(); ItemWidget::registerComponents(); @@ -246,7 +251,7 @@ namespace MWGui MyGUI::PointerManager::getInstance().setVisible(false); mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "Overlay"); + MyGUI::Align::Default, "InputBlocker"); mVideoBackground->setImageTexture("black"); mVideoBackground->setVisible(false); mVideoBackground->setNeedMouseFocus(true); @@ -284,9 +289,10 @@ namespace MWGui trackWindow(mStatsWindow, "stats"); mConsole = new Console(w,h, mConsoleOnlyScripts); trackWindow(mConsole, "console"); - mJournal = JournalWindow::create(JournalViewModel::create ()); - mMessageBoxManager = new MessageBoxManager( - MWBase::Environment::get().getWorld()->getStore().get().find("fMessageTimePerChar")->getFloat()); + + bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); + mJournal = JournalWindow::create(JournalViewModel::create (), questList); + mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->getFloat()); mInventoryWindow = new InventoryWindow(mDragAndDrop, mViewer, mResourceSystem); mTradeWindow = new TradeWindow(); trackWindow(mTradeWindow, "barter"); @@ -296,7 +302,7 @@ namespace MWGui trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); trackWindow(mContainerWindow, "container"); - mHud = new HUD(mCustomMarkers, Settings::Manager::getInt("fps", "HUD"), mDragAndDrop, mLocalMapRender); + mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); mBookWindow = new BookWindow(); @@ -320,19 +326,27 @@ namespace MWGui trackWindow(mCompanionWindow, "companion"); mJailScreen = new JailScreen(); - mWerewolfFader = new ScreenFader("textures\\werewolfoverlay.dds"); + std::string werewolfFaderTex = "textures\\werewolfoverlay.dds"; + if (mResourceSystem->getVFS()->exists(werewolfFaderTex)) + mWerewolfFader = new ScreenFader(werewolfFaderTex); mBlindnessFader = new ScreenFader("black"); - std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; + // fall back to player_hit_01.dds if bm_player_hit_01.dds is not available - // TODO: check if non-BM versions actually use player_hit_01.dds + std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; + const std::string hitFaderLayout = "openmw_screen_fader_hit.layout"; + MyGUI::FloatCoord hitFaderCoord (0,0,1,1); if(!mResourceSystem->getVFS()->exists(hitFaderTexture)) + { hitFaderTexture = "textures\\player_hit_01.dds"; - mHitFader = new ScreenFader(hitFaderTexture); + hitFaderCoord = MyGUI::FloatCoord(0.2, 0.25, 0.6, 0.5); + } + mHitFader = new ScreenFader(hitFaderTexture, hitFaderLayout, hitFaderCoord); + mScreenFader = new ScreenFader("black"); mDebugWindow = new DebugWindow(); - mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"Overlay"); + mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); mHud->setVisible(mHudEnabled); @@ -447,6 +461,11 @@ namespace MWGui delete mGuiPlatform; } + void WindowManager::setStore(const MWWorld::ESMStore &store) + { + mStore = &store; + } + void WindowManager::cleanupGarbage() { // Delete any dialogs which are no longer in use @@ -464,8 +483,6 @@ namespace MWGui { cleanupGarbage(); - mHud->setFPS(mFPS); - mHud->update(); } @@ -859,7 +876,13 @@ namespace MWGui mMessageBoxManager->onFrame(0.f); MWBase::Environment::get().getInputManager()->update(0, true, false); - mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } } } @@ -890,8 +913,7 @@ namespace MWGui std::string WindowManager::getGameSettingString(const std::string &id, const std::string &default_) { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().search(id); + const ESM::GameSetting *setting = mStore->get().search(id); if (setting && setting->mValue.getType()==ESM::VT_String) return setting->mValue.getString(); @@ -976,7 +998,8 @@ namespace MWGui mCompanionWindow->onFrame(); mJailScreen->onFrame(frameDuration); - mWerewolfFader->update(frameDuration); + if (mWerewolfFader) + mWerewolfFader->update(frameDuration); mBlindnessFader->update(frameDuration); mHitFader->update(frameDuration); mScreenFader->update(frameDuration); @@ -1120,8 +1143,12 @@ namespace MWGui } else { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().find(tag); + if (!mStore) + { + std::cerr << "WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'" << std::endl; + return; + } + const ESM::GameSetting *setting = mStore->get().find(tag); if (setting && setting->mValue.getType()==ESM::VT_String) _result = setting->mValue.getString(); @@ -1132,7 +1159,6 @@ namespace MWGui void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) { - mHud->setFpsVisible(static_cast(Settings::Manager::getInt("fps", "HUD"))); mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); for (Settings::CategorySettingVector::const_iterator it = changed.begin(); @@ -1245,8 +1271,7 @@ namespace MWGui mSelectedSpell = spellId; mHud->setSelectedSpell(spellId, successChancePercent); - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = mStore->get().find(spellId); mSpellWindow->setTitle(spell->mName); } @@ -1254,7 +1279,7 @@ namespace MWGui void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { mSelectedSpell = ""; - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get() + const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100 @@ -1316,11 +1341,6 @@ namespace MWGui mConsole->executeFile (path); } - void WindowManager::wmUpdateFps(float fps) - { - mFPS = fps; - } - MWGui::DialogueWindow* WindowManager::getDialogueWindow() { return mDialogueWindow; } MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; } MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } @@ -1694,7 +1714,7 @@ namespace MWGui { reader.getSubNameIs("ID__"); std::string spell = reader.getHString(); - if (MWBase::Environment::get().getWorld()->getStore().get().search(spell)) + if (mStore->get().search(spell)) mSelectedSpell = spell; } else if (type == ESM::REC_MARK) @@ -1741,6 +1761,7 @@ namespace MWGui MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); sizeVideo(screenSize.width, screenSize.height); + MyGUI::Widget* oldKeyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); setKeyFocusWidget(mVideoWidget); mVideoBackground->setVisible(true); @@ -1756,12 +1777,20 @@ namespace MWGui { MWBase::Environment::get().getInputManager()->update(0, true, false); - mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } mVideoWidget->stop(); MWBase::Environment::get().getSoundManager()->resumeSounds(); + setKeyFocusWidget(oldKeyFocus); + setCursorVisible(cursorWasVisible); // Restore normal rendering @@ -1867,7 +1896,8 @@ namespace MWGui if (!mWerewolfOverlayEnabled) return; - mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); + if (mWerewolfFader) + mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); } void WindowManager::onClipboardChanged(const std::string &_type, const std::string &_data) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index a1f76683e5..6edb4660cf 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -28,6 +28,11 @@ namespace MyGUI class ImageBox; } +namespace MWWorld +{ + class ESMStore; +} + namespace Compiler { class Extensions; @@ -119,6 +124,9 @@ namespace MWGui Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription); virtual ~WindowManager(); + /// Set the ESMStore to use for retrieving of GUI-related strings. + void setStore (const MWWorld::ESMStore& store); + void initUI(); void renderWorldMap(); @@ -180,8 +188,6 @@ namespace MWGui virtual void setConsoleSelectedObject(const MWWorld::Ptr& object); - virtual void wmUpdateFps(float fps); - ///< Set value for the given ID. virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value); virtual void setValue (int parSkill, const MWMechanics::SkillValue& value); @@ -374,6 +380,7 @@ namespace MWGui void writeFog(MWWorld::CellStore* cell); private: + const MWWorld::ESMStore* mStore; Resource::ResourceSystem* mResourceSystem; osgMyGUI::Platform* mGuiPlatform; @@ -486,8 +493,6 @@ namespace MWGui void updateMap(); - float mFPS; - std::map mFallbackMap; int mShowOwned; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index ad5fe35bd9..71938dfb04 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -4,6 +4,8 @@ #include +#include + #include #include #include @@ -15,12 +17,11 @@ #include #include -#include "../engine.hpp" - #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/environment.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -30,22 +31,20 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" -using namespace ICS; - namespace MWInput { InputManager::InputManager( SDL_Window* window, osg::ref_ptr viewer, - OMW::Engine& engine, + osg::ref_ptr screenCaptureHandler, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab) : mWindow(window) , mWindowVisible(true) , mViewer(viewer) + , mScreenCaptureHandler(screenCaptureHandler) , mJoystickLastUsed(false) , mPlayer(NULL) - , mEngine(engine) , mInputManager(NULL) , mVideoWrapper(NULL) , mUserFile(userFile) @@ -54,7 +53,6 @@ namespace MWInput , mInvertY (Settings::Manager::getBool("invert y axis", "Input")) , mControlsDisabled(false) , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) - , mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input")) , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) @@ -121,10 +119,11 @@ namespace MWInput SDL_ControllerDeviceEvent evt; evt.which = i; controllerAdded(mFakeDeviceID, evt); + std::cout << "Detected game controller: " << SDL_GameControllerNameForIndex(i) << std::endl; } else { - //ICS_LOG(std::string("Unusable controller plugged in: ")+SDL_JoystickNameForIndex(i)); + std::cout << "Detected unusable controller: " << SDL_JoystickNameForIndex(i) << std::endl; } } @@ -587,9 +586,6 @@ namespace MWInput if (it->first == "Input" && it->second == "camera sensitivity") mCameraSensitivity = Settings::Manager::getFloat("camera sensitivity", "Input"); - if (it->first == "Input" && it->second == "ui sensitivity") - mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input"); - if (it->first == "Input" && it->second == "grab cursor") mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); @@ -952,7 +948,8 @@ namespace MWInput void InputManager::screenshot() { - mEngine.screenshot(); + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } @@ -1058,7 +1055,7 @@ namespace MWInput void InputManager::activate() { if (mControlSwitch["playercontrols"]) - mEngine.activate(); + mPlayer->activate(); } void InputManager::toggleAutoMove() diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 62d0f413ba..3b11e04c09 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -25,11 +25,6 @@ namespace MWBase class WindowManager; } -namespace OMW -{ - class Engine; -} - namespace ICS { class InputControlSystem; @@ -54,6 +49,7 @@ namespace SDLUtil namespace osgViewer { class Viewer; + class ScreenCaptureHandler; } struct SDL_Window; @@ -77,7 +73,7 @@ namespace MWInput InputManager( SDL_Window* window, osg::ref_ptr viewer, - OMW::Engine& engine, + osg::ref_ptr screenCaptureHandler, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab); @@ -157,10 +153,10 @@ namespace MWInput SDL_Window* mWindow; bool mWindowVisible; osg::ref_ptr mViewer; + osg::ref_ptr mScreenCaptureHandler; bool mJoystickLastUsed; MWWorld::Player* mPlayer; - OMW::Engine& mEngine; ICS::InputControlSystem* mInputBinder; @@ -178,7 +174,6 @@ namespace MWInput bool mControlsDisabled; float mCameraSensitivity; - float mUISensitivity; float mCameraYMultiplier; float mPreviewPOVDelay; float mTimeIdle; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 7068310a0d..6a247622c5 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -132,15 +132,11 @@ namespace MWMechanics return scaledDuration-usedUp; } - bool ActiveSpells::isSpellActive(std::string id) const + bool ActiveSpells::isSpellActive(const std::string& id) const { - Misc::StringUtils::toLower(id); for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) { - std::string left = iter->first; - Misc::StringUtils::toLower(left); - - if (iter->first == id) + if (Misc::StringUtils::ciEqual(iter->first, id)) return true; } return false; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2a4d75d402..3842ee61c5 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -97,7 +97,7 @@ namespace MWMechanics /// Remove all spells void clear(); - bool isSpellActive (std::string id) const; + bool isSpellActive (const std::string& id) const; ///< case insensitive const MagicEffects& getMagicEffects() const; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1c26c7dd11..7f857a7008 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -3,11 +3,10 @@ #include #include -#include - #include #include #include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -213,8 +212,9 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, "", mCreature.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getSoundManager()->playSound3D(mCreature, "conjuration hit", 1.f, 1.f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + MWBase::Environment::get().getSoundManager()->playSound3D( + mCreature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f + ); } }; @@ -303,7 +303,7 @@ namespace MWMechanics 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); + const std::list& followers = getActorsSidingWith(actor2); if (std::find(followers.begin(), followers.end(), actor1) != followers.end()) return; @@ -322,7 +322,7 @@ namespace MWMechanics } // start combat if target actor is in combat with one of our followers - const std::list& followers = getActorsFollowing(actor1); + const std::list& followers = getActorsSidingWith(actor1); const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); for (std::list::const_iterator it = followers.begin(); it != followers.end(); ++it) { @@ -336,20 +336,21 @@ namespace MWMechanics // 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 = static_cast(*it)->getTarget(); - if (followTarget.isEmpty()) - continue; + if (!(*it)->sideWithTarget()) + continue; - if (creatureStats.getAiSequence().isInCombat(followTarget)) - continue; + MWWorld::Ptr followTarget = (*it)->getTarget(); - // 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 (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) @@ -485,11 +486,25 @@ namespace MWMechanics bool wasDead = creatureStats.isDead(); - // tickable effects (i.e. effects having a lasting impact after expiry) - // these effects can be applied as "instant" (handled in spellcasting.cpp) or with a duration, handled here - for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + if (duration > 0) { - effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); + for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + // tickable effects (i.e. effects having a lasting impact after expiry) + effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); + + // instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities + if (it->second.getMagnitude() > 0) + { + CastSpell cast(ptr, ptr); + if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude())) + { + creatureStats.getActiveSpells().purgeEffect(it->first.mId); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId); + } + } + } } // attributes @@ -838,7 +853,7 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); + static const 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 @@ -846,7 +861,7 @@ namespace MWMechanics && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - static int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); + static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); else @@ -926,7 +941,7 @@ namespace MWMechanics PtrActorMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { - if(iter->first.getCell()==cellStore && iter->first != ignore) + if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore) { delete iter->second; mActors.erase(iter++); @@ -1290,6 +1305,28 @@ namespace MWMechanics } } + std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) + { + std::list list; + for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow or AiEscort is the current AiPackage, or there are only Combat packages before the AiFollow/AiEscort package + for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->sideWithTarget() && (*it)->getTarget() == actor) + list.push_back(iter->first); + else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + break; + } + } + return list; + } + std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; @@ -1303,16 +1340,8 @@ namespace MWMechanics // 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) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) - { - MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); - if (followTarget.isEmpty()) - continue; - if (followTarget == actor) - list.push_back(iter->first); - else - break; - } + if ((*it)->followTargetThroughDoors() && (*it)->getTarget() == actor) + list.push_back(iter->first); else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) break; } @@ -1340,8 +1369,7 @@ namespace MWMechanics continue; if (followTarget == actor) list.push_back(static_cast(*it)->getFollowIndex()); - else - break; + break; } else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) break; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a16b808846..0bdf611d2a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -111,8 +111,9 @@ namespace MWMechanics void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); - ///Returns the list of actors which are following the given actor - /**ie AiFollow is active and the target is the actor **/ + ///Returns the list of actors which are siding with the given actor in fights + /**ie AiFollow or AiEscort is active and the target is the actor **/ + std::list getActorsSidingWith(const MWWorld::Ptr& actor); std::list getActorsFollowing(const MWWorld::Ptr& actor); /// Get the list of AiFollow::mFollowIndex for all actors following this target diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 409f7b9c40..100b666e89 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -11,7 +11,7 @@ #include "steering.hpp" -MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::Ptr& doorPtr) +MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) : AiPackage(), mDuration(1), mDoorPtr(doorPtr), mAdjAngle(0) { diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 1ad945bca8..9d63c63e08 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -16,7 +16,7 @@ namespace MWMechanics { public: /// Avoid door until the door is fully open - AiAvoidDoor(const MWWorld::Ptr& doorPtr); + AiAvoidDoor(const MWWorld::ConstPtr& doorPtr); virtual AiAvoidDoor *clone() const; @@ -28,7 +28,7 @@ namespace MWMechanics private: float mDuration; - MWWorld::Ptr mDoorPtr; + MWWorld::ConstPtr mDoorPtr; ESM::Position mLastPos; float mAdjAngle; }; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 6270779a49..3cea00e45b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -347,7 +347,7 @@ namespace MWMechanics osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); - float distToTarget = (vTargetPos - vActorPos).length(); + float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); osg::Vec3f& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 33e3c3d679..60446e524d 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -541,7 +541,7 @@ 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); + const ESM::Spell* spell = it->first; float rating = rateSpell(spell, actor, target); if (rating > bestActionRating) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index f75fc22ad1..baf4f1be77 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,12 +1,14 @@ #include "aiescort.hpp" #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -73,6 +75,12 @@ namespace MWMechanics return true; } + if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) + return false; + + if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) + return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); @@ -114,6 +122,11 @@ namespace MWMechanics return TypeIdEscort; } + MWWorld::Ptr AiEscort::getTarget() + { + return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + } + void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr escort(new ESM::AiSequence::AiEscort()); diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 9f6335b933..cdb0f79360 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -37,6 +37,9 @@ namespace MWMechanics virtual int getTypeId() const; + MWWorld::Ptr getTarget(); + virtual bool sideWithTarget() const { return true; } + void writeState(ESM::AiSequence::AiSequence &sequence) const; private: diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 8555f9bc4d..017ea7122d 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -32,6 +32,8 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); MWWorld::Ptr getTarget(); + virtual bool sideWithTarget() const { return true; } + virtual bool followTargetThroughDoors() const { return true; } virtual AiFollow *clone() const; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index bedff8bcdc..cb2b002f6c 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -20,6 +20,21 @@ MWMechanics::AiPackage::~AiPackage() {} +MWWorld::Ptr MWMechanics::AiPackage::getTarget() +{ + return MWWorld::Ptr(); +} + +bool MWMechanics::AiPackage::sideWithTarget() const +{ + return false; +} + +bool MWMechanics::AiPackage::followTargetThroughDoors() const +{ + return false; +} + MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 3f227a49a2..76f89dff4e 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -69,6 +69,15 @@ namespace MWMechanics /// Simulates the passing of time virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} + /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) + virtual MWWorld::Ptr getTarget(); + + /// Return true if having this AiPackage makes the actor side with the target in fights (default false) + virtual bool sideWithTarget() const; + + /// Return true if the actor should follow the target through teleport doors (default false) + virtual bool followTargetThroughDoors() const; + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: @@ -88,10 +97,19 @@ namespace MWMechanics ESM::Pathgrid::Point mPrevDest; + bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) const + { + // 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. + return (pos1 - pos2).length2() <= 7168*7168; + } + private: bool isNearInactiveCell(const ESM::Position& actorPos); }; + } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index a1c5ab14f0..d55dc240e0 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -72,7 +72,7 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const targetActor = combat->getTarget(); - return true; + return !targetActor.isEmpty(); } std::list::const_iterator AiSequence::begin() const diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 1585a3007f..c29861f4ee 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -13,18 +13,6 @@ #include "movement.hpp" #include "creaturestats.hpp" -namespace -{ - -bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) -{ - // 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. - return (pos1 - pos2).length2() <= 7168*7168; -} - -} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 38d85a7cd3..d39c881506 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -421,8 +421,8 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) return -1; for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) - if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getClass().getId(ingredient), - iter->getClass().getId(*iter))) + if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getCellRef().getRefId(), + iter->getCellRef().getRefId())) return -1; mIngredients[slot] = ingredient; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 36c251053c..ba6a8fe4b0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -21,7 +21,6 @@ #include -#include #include "movement.hpp" #include "npcstats.hpp" @@ -33,6 +32,8 @@ #include +#include + #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" @@ -396,10 +397,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } else { - if (weap != sWeaponTypeListEnd) - movemask = MWRender::Animation::BlendMask_LowerBody; movementAnimName.erase(swimpos, 4); - if(!mAnimation->hasAnimation(movementAnimName)) + if (weap != sWeaponTypeListEnd) + { + std::string weapMovementAnimName = movementAnimName + weap->shortgroup; + if(mAnimation->hasAnimation(weapMovementAnimName)) + movementAnimName = weapMovementAnimName; + else + movemask = MWRender::Animation::BlendMask_LowerBody; + } + + if (!mAnimation->hasAnimation(movementAnimName)) movementAnimName.clear(); } } @@ -504,8 +512,6 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } - - updateIdleStormState(); } @@ -656,6 +662,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mAnimation(anim) , mIdleState(CharState_None) , mMovementState(CharState_None) + , mAdjustMovementAnimSpeed(false) , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) @@ -777,10 +784,15 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx; if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) - type = MWBase::SoundManager::Play_TypeFoot; - sndMgr->playSound3D(mPtr, sound, volume, pitch, type); + { + // Don't make foot sounds local for the player, it makes sense to keep them + // positioned on the ground. + sndMgr->playSound3D(mPtr, sound, volume, pitch, MWBase::SoundManager::Play_TypeFoot, + MWBase::SoundManager::Play_NoPlayerLocal); + } + else + sndMgr->playSound3D(mPtr, sound, volume, pitch); } return; } @@ -867,7 +879,7 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) mPtr = ptr; } -void CharacterController::updateIdleStormState() +void CharacterController::updateIdleStormState(bool inwater) { bool inStormDirection = false; if (MWBase::Environment::get().getWorld()->isInStorm()) @@ -877,7 +889,7 @@ void CharacterController::updateIdleStormState() inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) > osg::DegreesToRadians(120.f); } - if (inStormDirection && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) + if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) { float complete = 0; mAnimation->getInfo("idlestorm", &complete); @@ -1480,6 +1492,8 @@ bool CharacterController::updateWeaponState() } } + mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); + return forcestateupdate; } @@ -1796,6 +1810,7 @@ void CharacterController::update(float duration) forcestateupdate = updateCreatureState() || forcestateupdate; refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); + updateIdleStormState(inwater); } if (inJump) @@ -1814,7 +1829,6 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - rot *= osg::RadiansToDegrees(1.0f); if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut) { world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); @@ -2100,7 +2114,7 @@ void CharacterController::setActive(bool active) mAnimation->setActive(active); } -void CharacterController::setHeadTrackTarget(const MWWorld::Ptr &target) +void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) { mHeadTrackTarget = target; } @@ -2122,8 +2136,8 @@ void CharacterController::updateHeadTracking(float duration) osg::Matrixf mat = mats[0]; osg::Vec3f headPos = mat.getTrans(); - osg::Vec3f targetPos (mHeadTrackTarget.getRefData().getPosition().asVec3()); - if (MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + osg::Vec3f direction; + if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) { const osg::Node* node = anim->getNode("Head"); if (node == NULL) @@ -2132,11 +2146,12 @@ void CharacterController::updateHeadTracking(float duration) { osg::MatrixList mats = node->getWorldMatrices(); if (mats.size()) - targetPos = mats[0].getTrans(); + direction = mats[0].getTrans() - headPos; } + else + // no head node to look at, fall back to look at center of collision box + direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); } - - osg::Vec3f direction = targetPos - headPos; direction.normalize(); if (!mPtr.getRefData().getBaseNode()) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index fee7b959cb..5e43bc2b73 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -180,7 +180,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener float mSecondsOfSwimming; float mSecondsOfRunning; - MWWorld::Ptr mHeadTrackTarget; + MWWorld::ConstPtr mHeadTrackTarget; float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning @@ -196,7 +196,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool updateWeaponState(); bool updateCreatureState(); - void updateIdleStormState(); + void updateIdleStormState(bool inwater); void updateHeadTracking(float duration); @@ -255,7 +255,7 @@ public: void setActive(bool active); /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. - void setHeadTrackTarget(const MWWorld::Ptr& target); + void setHeadTrackTarget(const MWWorld::ConstPtr& target); }; MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index f11e6bcfda..a01dc7079c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -1,9 +1,9 @@ #include "combat.hpp" -#include - #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 0638637fa5..6933c40a3f 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -48,8 +48,10 @@ namespace MWMechanics const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - return gmst.find ("fFatigueBase")->getFloat() - - gmst.find ("fFatigueMult")->getFloat() * (1-normalised); + static const float fFatigueBase = gmst.find("fFatigueBase")->getFloat(); + static const float fFatigueMult = gmst.find("fFatigueMult")->getFloat(); + + return fFatigueBase - fFatigueMult * (1-normalised); } const AttributeValue &CreatureStats::getAttribute(int index) const @@ -265,9 +267,11 @@ namespace MWMechanics { if (mDead) { + if (mDynamic[0].getModified() < 1) + mDynamic[0].setModified(1, 0); + mDynamic[0].setCurrent(mDynamic[0].getModified()); - if (mDynamic[0].getCurrent()>=1) - mDead = false; + mDead = false; } } diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 27c19364f9..25bde56ab8 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -34,8 +34,7 @@ namespace MWMechanics Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - + const ESM::Spell* spell = it->first; if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) continue; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 761c5ece9e..9dafebffa7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -2,13 +2,13 @@ #include -#include - #include #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -604,7 +604,7 @@ namespace MWMechanics int rank = 0; std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); - Misc::StringUtils::toLower(npcFaction); + Misc::StringUtils::lowerCaseInPlace(npcFaction); if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { @@ -983,7 +983,7 @@ namespace MWMechanics MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getClass().getId(*it))); + StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); if (stolenIt == mStolenItems.end()) continue; OwnerMap& owners = stolenIt->second; @@ -1042,10 +1042,10 @@ namespace MWMechanics owner.first = ownerCellRef->getFaction(); owner.second = true; } - Misc::StringUtils::toLower(owner.first); + Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) - mStolenItems[Misc::StringUtils::lowerCase(item.getClass().getId(item))][owner] += count; + mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } @@ -1053,7 +1053,7 @@ namespace MWMechanics void getFollowers (const MWWorld::Ptr& actor, std::set& out) { - std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(actor); for(std::list::iterator it = followers.begin();it != followers.end();++it) { if (out.insert(*it).second) @@ -1310,7 +1310,7 @@ namespace MWMechanics if (ptr == getPlayer()) return false; - std::list followers = getActorsFollowing(attacker); + std::list followers = getActorsSidingWith(attacker); MWMechanics::CreatureStats& targetStats = ptr.getClass().getCreatureStats(ptr); if (std::find(followers.begin(), followers.end(), ptr) != followers.end()) { @@ -1487,6 +1487,11 @@ namespace MWMechanics mActors.getObjectsInRange(position, radius, objects); } + std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) + { + return mActors.getActorsSidingWith(actor); + } + std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index f0d1cc2a02..603888adc2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -150,6 +150,7 @@ namespace MWMechanics virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects); virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects); + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 53f12a982e..5815d8cbe3 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -11,8 +11,8 @@ namespace MWMechanics { // NOTE: determined empirically but probably need further tweaking - static const float DIST_SAME_SPOT = 1.8f; - static const float DURATION_SAME_SPOT = 1.0f; + static const float DIST_SAME_SPOT = 0.5f; + static const float DURATION_SAME_SPOT = 1.5f; static const float DURATION_TO_EVADE = 0.4f; const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] = @@ -28,15 +28,15 @@ namespace MWMechanics // // Limitation: there can be false detections, and does not test whether the // actor is facing the door. - bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr) { - if(getNearbyDoor(actor, minSqr, closed)!=MWWorld::Ptr()) + if(getNearbyDoor(actor, minSqr)!=MWWorld::Ptr()) return true; else return false; } - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minSqr) { MWWorld::CellStore *cell = actor.getCell(); @@ -44,9 +44,9 @@ namespace MWMechanics return MWWorld::Ptr(); // check interior cells only // Check all the doors in this cell - MWWorld::CellRefList& doors = cell->get(); - MWWorld::CellRefList::List& refList = doors.mList; - MWWorld::CellRefList::List::iterator it = refList.begin(); + const MWWorld::CellRefList& doors = cell->getReadOnlyDoors(); + const MWWorld::CellRefList::List& refList = doors.mList; + MWWorld::CellRefList::List::const_iterator it = refList.begin(); osg::Vec3f pos(actor.getRefData().getPosition().asVec3()); /// TODO: How to check whether the actor is facing a door? Below code is for @@ -59,13 +59,13 @@ namespace MWMechanics /// opposite of the code in World::activateDoor() ::confused:: for (; it != refList.end(); ++it) { - MWWorld::LiveCellRef& ref = *it; - if((pos - ref.mData.getPosition().asVec3()).length2() < minSqr) - if((closed && ref.mData.getLocalRotation().rot[2] == 0) || - (!closed && ref.mData.getLocalRotation().rot[2] >= 1)) - { - return MWWorld::Ptr(&ref, actor.getCell()); // found, stop searching - } + const MWWorld::LiveCellRef& ref = *it; + if((pos - ref.mData.getPosition().asVec3()).length2() < minSqr + && ref.mData.getPosition().rot[2] == ref.mRef.getPosition().rot[2]) + { + // FIXME cast + return MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); // found, stop searching + } } return MWWorld::Ptr(); // none found } @@ -114,20 +114,23 @@ namespace MWMechanics * t = how long before considered stuck * u = how long to move sideways * - * DIST_SAME_SPOT is calibrated for movement speed of around 150. - * A rat has walking speed of around 30, so we need to adjust for - * that. */ bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration) { const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); + // actors can move at most 60 fps (the physics framerate). + // the max() can be removed if we implement physics interpolation. + float movementDuration = std::max(1/60.f, duration); + if(mDistSameSpot == -1) - mDistSameSpot = DIST_SAME_SPOT * (cls.getSpeed(actor) / 150); + mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor); + + float distSameSpot = mDistSameSpot * movementDuration; + + bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; - bool samePosition = (std::abs(pos.pos[0] - mPrevX) < mDistSameSpot) && - (std::abs(pos.pos[1] - mPrevY) < mDistSameSpot); // update position mPrevX = pos.pos[0]; mPrevY = pos.pos[1]; @@ -160,13 +163,13 @@ namespace MWMechanics { mStuckDuration = 0; mWalkState = State_Evade; + chooseEvasionDirection(); } } } /* FALL THROUGH */ case State_Evade: { - chooseEvasionDirection(samePosition); mEvadeDuration += duration; if(mEvadeDuration < DURATION_TO_EVADE) return true; @@ -188,16 +191,13 @@ namespace MWMechanics actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1]; } - void ObstacleCheck::chooseEvasionDirection(bool samePosition) + void ObstacleCheck::chooseEvasionDirection() { // change direction if attempt didn't work - if (samePosition && (0 < mEvadeDuration)) + ++mEvadeDirectionIndex; + if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS) { - ++mEvadeDirectionIndex; - if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS) - { - mEvadeDirectionIndex = 0; - } + mEvadeDirectionIndex = 0; } } diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index ecff00a5c2..98cc4e7a08 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -17,14 +17,12 @@ namespace MWMechanics /// tests actor's proximity to a closed door by default bool proximityToDoor(const MWWorld::Ptr& actor, - float minSqr = MIN_DIST_TO_DOOR_SQUARED, - bool closed = true); + float minSqr = MIN_DIST_TO_DOOR_SQUARED); /// Returns door pointer within range. No guarentee is given as too which one /** \return Pointer to the door, or NULL if none exists **/ MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, - float minSqr = MIN_DIST_TO_DOOR_SQUARED, - bool closed = true); + float minSqr = MIN_DIST_TO_DOOR_SQUARED); class ObstacleCheck { @@ -65,7 +63,7 @@ namespace MWMechanics float mDistSameSpot; // take account of actor's speed int mEvadeDirectionIndex; - void chooseEvasionDirection(bool samePosition); + void chooseEvasionDirection(); }; } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 70d097687e..ab8784beb1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -124,7 +124,7 @@ namespace MWMechanics } if (spell->mData.mType == ESM::Spell::ST_Power) - return stats.getSpells().canUsePower(spell->mId) ? 100 : 0; + return stats.getSpells().canUsePower(spell) ? 100 : 0; if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; @@ -365,26 +365,6 @@ namespace MWMechanics bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); - // Try absorbing if it's a spell - // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. - bool absorbed = false; - if (spell && caster != target && target.getClass().isActor()) - { - float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); - absorbed = (Misc::Rng::roll0to99() < 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) { @@ -404,6 +384,26 @@ namespace MWMechanics && target.getClass().isActor()) MWBase::Environment::get().getWindowManager()->setEnemy(target); + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per spell absorption 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()) + { + float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); + absorbed = (Misc::Rng::roll0to99() < 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); + } + } + float magnitudeMult = 1; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) { @@ -452,42 +452,18 @@ namespace MWMechanics float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= magnitudeMult; - bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - if (target.getClass().isActor() && hasDuration && effectIt->mDuration > 0) + if (!target.getClass().isActor()) { - ActiveSpells::ActiveEffect effect; - effect.mEffectId = effectIt->mEffectID; - effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; - effect.mDuration = static_cast(effectIt->mDuration); - effect.mMagnitude = magnitude; - - targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); - - appliedLastingEffects.push_back(effect); - - // For absorb effects, also apply the effect to the caster - but with a negative - // magnitude, since we're transfering stats from the target to the caster - if (!caster.isEmpty() && caster.getClass().isActor()) - { - for (int i=0; i<5; ++i) - { - if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) - { - std::vector effects; - ActiveSpells::ActiveEffect effect_ = effect; - effect_.mMagnitude *= -1; - effects.push_back(effect_); - // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, - effects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); - } - } - } + // non-actor objects have no list of active magic effects, so have to apply instantly + if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) + continue; } - else + else // target.getClass().isActor() == true { - if (hasDuration && target.getClass().isActor()) + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + if (hasDuration && effectIt->mDuration == 0) { + // duration 0 means apply full magnitude instantly bool wasDead = target.getClass().getCreatureStats(target).isDead(); effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude); bool isDead = target.getClass().getCreatureStats(target).isDead(); @@ -495,8 +471,38 @@ namespace MWMechanics if (!wasDead && isDead) MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); } - else if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) - continue; + else + { + // add to list of active effects, to apply in next frame + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; + effect.mDuration = static_cast(effectIt->mDuration); + effect.mMagnitude = magnitude; + + targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); + + appliedLastingEffects.push_back(effect); + + // For absorb effects, also apply the effect to the caster - but with a negative + // magnitude, since we're transfering stats from the target to the caster + if (!caster.isEmpty() && caster.getClass().isActor()) + { + for (int i=0; i<5; ++i) + { + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::ActiveEffect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + effects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); + } + } + } + } } // Re-casting a summon effect will remove the creature from previous castings of that effect. @@ -823,7 +829,7 @@ namespace MWMechanics // A power can be used once per 24h if (spell->mData.mType == ESM::Spell::ST_Power) - stats.getSpells().usePower(spell->mId); + stats.getSpells().usePower(spell); } if (mCaster == getPlayer() && spellIncreasesSkill(spell)) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 330d78a201..a88b6b2633 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -25,9 +25,24 @@ namespace MWMechanics return mSpells.end(); } + const ESM::Spell* Spells::getSpell(const std::string& id) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + bool Spells::hasSpell(const std::string &spell) const + { + return hasSpell(getSpell(spell)); + } + + bool Spells::hasSpell(const ESM::Spell *spell) const + { + return mSpells.find(spell) != mSpells.end(); + } + void Spells::add (const ESM::Spell* spell) { - if (mSpells.find (spell->mId)==mSpells.end()) + if (mSpells.find (spell)==mSpells.end()) { std::map random; @@ -48,32 +63,32 @@ namespace MWMechanics corprus.mWorsenings = 0; corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[spell->mId] = corprus; + mCorprusSpells[spell] = corprus; } - mSpells.insert (std::make_pair (spell->mId, random)); + mSpells.insert (std::make_pair (spell, random)); } } void Spells::add (const std::string& spellId) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - add(spell); + add(getSpell(spellId)); } void Spells::remove (const std::string& spellId) { - std::string lower = Misc::StringUtils::lowerCase(spellId); - TContainer::iterator iter = mSpells.find (lower); - std::map::iterator corprusIt = mCorprusSpells.find(lower); + const ESM::Spell* spell = getSpell(spellId); + TContainer::iterator iter = mSpells.find (spell); + + std::map::iterator corprusIt = mCorprusSpells.find(spell); // if it's corprus, remove negative and keep positive effects if (corprusIt != mCorprusSpells.end()) { - worsenCorprus(lower); - if (mPermanentSpellEffects.find(lower) != mPermanentSpellEffects.end()) + worsenCorprus(spell); + if (mPermanentSpellEffects.find(spell) != mPermanentSpellEffects.end()) { - MagicEffects & effects = mPermanentSpellEffects[lower]; + MagicEffects & effects = mPermanentSpellEffects[spell]; for (MagicEffects::Collection::const_iterator effectIt = effects.begin(); effectIt != effects.end();) { const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->first.mId); @@ -101,8 +116,7 @@ namespace MWMechanics for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + const ESM::Spell *spell = iter->first; if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) @@ -120,7 +134,7 @@ namespace MWMechanics } } - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) { effects += it->second; } @@ -145,11 +159,10 @@ namespace MWMechanics bool Spells::isSpellActive(const std::string &id) const { - TContainer::const_iterator found = mSpells.find(id); + TContainer::const_iterator found = mSpells.find(getSpell(id)); if (found != mSpells.end()) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (id); + const ESM::Spell *spell = found->first; return (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse); @@ -161,9 +174,7 @@ namespace MWMechanics { for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Disease) return true; } @@ -175,9 +186,7 @@ namespace MWMechanics { for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Blight) return true; } @@ -189,9 +198,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Disease) mSpells.erase(iter++); else @@ -203,9 +210,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell)) mSpells.erase(iter++); else @@ -217,9 +222,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (hasCorprusEffect(spell)) mSpells.erase(iter++); else @@ -231,9 +234,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Curse) mSpells.erase(iter++); else @@ -245,7 +246,7 @@ namespace MWMechanics { for (TIterator it = begin(); it != end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + const ESM::Spell* spell = it->first; // these are the spell types that are permanently in effect if (!(spell->mData.mType == ESM::Spell::ST_Ability) @@ -268,14 +269,13 @@ namespace MWMechanics } } - void Spells::worsenCorprus(const std::string &corpSpellId) + void Spells::worsenCorprus(const ESM::Spell* spell) { - mCorprusSpells[corpSpellId].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[corpSpellId].mWorsenings++; + mCorprusSpells[spell].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; + mCorprusSpells[spell].mWorsenings++; // update worsened effects - mPermanentSpellEffects[corpSpellId] = MagicEffects(); - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().find(corpSpellId); + mPermanentSpellEffects[spell] = MagicEffects(); int i=0; for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt, ++i) { @@ -283,12 +283,12 @@ namespace MWMechanics 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); + if (mSpells[spell].find(i) != mSpells[spell].end()) + random = mSpells[spell].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)); + magnitude *= std::max(1, mCorprusSpells[spell].mWorsenings); + mPermanentSpellEffects[spell].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); } } } @@ -305,43 +305,47 @@ namespace MWMechanics return false; } - const std::map &Spells::getCorprusSpells() const + const std::map &Spells::getCorprusSpells() const { return mCorprusSpells; } - bool Spells::canUsePower(const std::string &power) const + bool Spells::canUsePower(const ESM::Spell* spell) const { - std::map::const_iterator it = mUsedPowers.find(Misc::StringUtils::lowerCase(power)); + std::map::const_iterator it = mUsedPowers.find(spell); if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) return true; else return false; } - void Spells::usePower(const std::string &power) + void Spells::usePower(const ESM::Spell* spell) { - mUsedPowers[Misc::StringUtils::lowerCase(power)] = MWBase::Environment::get().getWorld()->getTimeStamp(); + mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::readState(const ESM::SpellState &state) { - for (TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { // Discard spells that are no longer available due to changed content files const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (spell) { - mSpells[it->first] = it->second; + mSpells[spell] = it->second; if (it->first == state.mSelectedSpell) mSelectedSpell = it->first; } } - // 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); + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) + continue; + mUsedPowers[spell] = MWWorld::TimeStamp(it->second); + } for (std::map >::const_iterator it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) @@ -350,33 +354,35 @@ namespace MWMechanics if (!spell) continue; - mPermanentSpellEffects[it->first] = MagicEffects(); + mPermanentSpellEffects[spell] = 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); + mPermanentSpellEffects[spell].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); - } + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) // Discard unavailable corprus spells + continue; + mCorprusSpells[spell].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; + mCorprusSpells[spell].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); } } void Spells::writeState(ESM::SpellState &state) const { - state.mSpells = mSpells; + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + state.mSpells.insert(std::make_pair(it->first->mId, it->second)); + state.mSelectedSpell = mSelectedSpell; - 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 = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) + state.mUsedPowers[it->first->mId] = it->second.toEsm(); - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + 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) @@ -388,13 +394,13 @@ namespace MWMechanics effectList.push_back(info); } - state.mPermanentSpellEffects[it->first] = effectList; + state.mPermanentSpellEffects[it->first->mId] = effectList; } - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + 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(); + state.mCorprusSpells[it->first->mId].mWorsenings = mCorprusSpells.at(it->first).mWorsenings; + state.mCorprusSpells[it->first->mId].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 6b41499395..1b1993d5ef 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -31,7 +31,9 @@ namespace MWMechanics { public: - typedef std::map > TContainer; // ID, + typedef const ESM::Spell* SpellKey; + + typedef std::map > TContainer; // ID, typedef TContainer::const_iterator TIterator; struct CorprusStats @@ -47,23 +49,26 @@ namespace MWMechanics 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; + 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 mUsedPowers; + + std::map mCorprusSpells; - std::map mCorprusSpells; + /// Get spell from ID, throws exception if not found + const ESM::Spell* getSpell(const std::string& id) const; public: - void worsenCorprus(const std::string &corpSpellId); + void worsenCorprus(const ESM::Spell* spell); static bool hasCorprusEffect(const ESM::Spell *spell); - const std::map & getCorprusSpells() const; + const std::map & getCorprusSpells() const; - bool canUsePower (const std::string& power) const; - void usePower (const std::string& power); + bool canUsePower (const ESM::Spell* spell) const; + void usePower (const ESM::Spell* spell); void purgeCommonDisease(); void purgeBlightDisease(); @@ -74,7 +79,8 @@ namespace MWMechanics TIterator end() const; - bool hasSpell(const std::string& spell) const { return mSpells.find(Misc::StringUtils::lowerCase(spell)) != mSpells.end(); } + bool hasSpell(const std::string& spell) const; + bool hasSpell(const ESM::Spell* spell) const; void add (const std::string& spell); ///< Adding a spell that is already listed in *this is a no-op. diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 541026ee18..636e199074 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -1,5 +1,7 @@ #include "summoning.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -34,7 +36,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, "", ptr.getRefData().getPosition().asVec3()); } - else + else if (creatureActorId != -1) { // 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. @@ -132,25 +134,34 @@ namespace MWMechanics if (!creatureID.empty()) { MWWorld::CellStore* store = mActor.getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().setPosition(ipos); - - MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - - // Make the summoned creature follow its master and help in fights - AiFollow package(mActor.getCellRef().getRefId()); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); - int creatureActorId = summonedCreatureStats.getActorId(); - - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); - - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); - if (anim) + int creatureActorId = -1; + try + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); + ref.getPtr().getCellRef().setPosition(ipos); + + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + + // Make the summoned creature follow its master and help in fights + AiFollow package(mActor.getCellRef().getRefId()); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); + if (anim) + { + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); + } + } + catch (std::exception& e) { - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_Start"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1, false); + std::cerr << "Failed to spawn summoned creature: " << e.what() << std::endl; + // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log } creatureMap.insert(std::make_pair(*it, creatureActorId)); diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 94d93e7d7a..cc46897d13 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -1,12 +1,11 @@ #include "actor.hpp" -#include - #include #include #include -#include +#include +#include #include "../mwworld/class.hpp" @@ -17,7 +16,7 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) +Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) , mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(false) , mInternalCollisionMode(true) @@ -77,7 +76,7 @@ void Actor::updateCollisionMask() mCollisionWorld->removeCollisionObject(mCollisionObject.get()); int collisionMask = CollisionType_World | CollisionType_HeightMap; if (mExternalCollisionMode) - collisionMask |= CollisionType_Actor | CollisionType_Projectile; + collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door; if (mCanWaterWalk) collisionMask |= CollisionType_Water; mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); @@ -95,6 +94,11 @@ void Actor::updatePosition() mCollisionObject->setWorldTransform(tr); } +osg::Vec3f Actor::getPosition() const +{ + return toOsg(mCollisionObject->getWorldTransform().getOrigin()); +} + void Actor::updateRotation () { btTransform tr = mCollisionObject->getWorldTransform(); @@ -110,12 +114,14 @@ void Actor::updateScale() float scale = mPtr.getCellRef().getScale(); osg::Vec3f scaleVec(scale,scale,scale); - if (!mPtr.getClass().isNpc()) - mPtr.getClass().adjustScale(mPtr, scaleVec); - + mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; mShape->setLocalScaling(toBullet(mScale)); + scaleVec = osg::Vec3f(scale,scale,scale); + mPtr.getClass().adjustScale(mPtr, scaleVec, true); + mRenderingScale = scaleVec; + updatePosition(); } @@ -124,6 +130,11 @@ osg::Vec3f Actor::getHalfExtents() const return osg::componentMultiply(mHalfExtents, mScale); } +osg::Vec3f Actor::getRenderingHalfExtents() const +{ + return osg::componentMultiply(mHalfExtents, mRenderingScale); +} + void Actor::setInertialForce(const osg::Vec3f &force) { mForce = force; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 7a12f549d4..03193c675e 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -13,7 +13,7 @@ class btCollisionWorld; class btCollisionShape; class btCollisionObject; -namespace NifBullet +namespace Resource { class BulletShapeInstance; } @@ -31,7 +31,12 @@ namespace MWPhysics mPtr = updated; } - MWWorld::Ptr getPtr() const + MWWorld::Ptr getPtr() + { + return mPtr; + } + + MWWorld::ConstPtr getPtr() const { return mPtr; } @@ -43,7 +48,7 @@ namespace MWPhysics class Actor : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); + Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); ~Actor(); /** @@ -66,10 +71,23 @@ namespace MWPhysics void updatePosition(); /** - * Returns the (scaled) half extents + * Returns the half extents of the collision body (scaled according to collision scale) */ osg::Vec3f getHalfExtents() const; + /** + * Returns the position of the collision body + * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. + */ + osg::Vec3f getPosition() const; + + /** + * Returns the half extents of the collision body (scaled according to rendering scale) + * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, + * most likely to make environment collision testing easier. However in some cases (swimming level) we want the actual scale. + */ + osg::Vec3f getRenderingHalfExtents() const; + /** * Sets the current amount of inertial force (incl. gravity) affecting this physic actor */ @@ -118,6 +136,7 @@ namespace MWPhysics osg::Quat mRotation; osg::Vec3f mScale; + osg::Vec3f mRenderingScale; osg::Vec3f mPosition; osg::Vec3f mForce; diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp index 0f083ab352..0d6a32fc09 100644 --- a/apps/openmw/mwphysics/collisiontype.hpp +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -6,8 +6,9 @@ namespace MWPhysics enum CollisionType { CollisionType_World = 1<<0, - CollisionType_Actor = 1<<1, - CollisionType_HeightMap = 1<<2, + CollisionType_Door = 1<<1, + CollisionType_Actor = 1<<2, + CollisionType_HeightMap = 1<<3, CollisionType_Projectile = 1<<4, CollisionType_Water = 1<<5 }; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index e666161da9..f6883ae35d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -17,11 +16,12 @@ #include #include -#include #include #include +#include #include +#include #include // FindRecIndexVisitor @@ -218,6 +218,7 @@ namespace MWPhysics resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; collisionWorld->rayTest(from, to, resultCallback1); + if (resultCallback1.hasHit() && ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 30 || getSlope(tracer.mPlaneNormal) > sMaxSlope)) @@ -233,9 +234,8 @@ namespace MWPhysics } static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, - bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld - , std::map& collisionTracker - , std::map& standingCollisionTracker) + bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld, + std::map& standingCollisionTracker) { const ESM::Position& refpos = ptr.getRefData().getPosition(); osg::Vec3f position(refpos.asVec3()); @@ -257,11 +257,16 @@ namespace MWPhysics btCollisionObject *colobj = physicActor->getCollisionObject(); osg::Vec3f halfExtents = physicActor->getHalfExtents(); + + // NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos). + // That means the collision shape used for moving this actor is in a different spot than the collision shape + // other actors are using to collide against this actor. + // While this is strictly speaking wrong, it's needed for MW compatibility. position.z() += halfExtents.z(); static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get() .find("fSwimHeightScale")->getFloat(); - float swimlevel = waterlevel + halfExtents.z() - (halfExtents.z() * 2 * fSwimHeightScale); + float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); ActorTracer tracer; osg::Vec3f inertia = physicActor->getInertialForce(); @@ -283,6 +288,11 @@ namespace MWPhysics velocity = velocity + physicActor->getInertialForce(); } } + + // dead actors underwater will float to the surface + if (ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) + velocity = osg::Vec3f(0,0,1) * 25; + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; // Now that we have the effective movement vector, apply wind forces to it @@ -334,13 +344,6 @@ namespace MWPhysics newPosition = tracer.mEndPos; // ok to move, so set newPosition break; } - else - { - const btCollisionObject* standingOn = tracer.mHitObject; - const PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); - if (ptrHolder) - collisionTracker[ptr] = ptrHolder->getPtr(); - } } else { @@ -406,7 +409,7 @@ namespace MWPhysics && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor) { const btCollisionObject* standingOn = tracer.mHitObject; - const PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); + PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); if (ptrHolder) standingCollisionTracker[ptr] = ptrHolder->getPtr(); @@ -510,8 +513,9 @@ namespace MWPhysics class Object : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance) + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance) : mShapeInstance(shapeInstance) + , mSolid(true) { mPtr = ptr; @@ -546,6 +550,22 @@ namespace MWPhysics return mCollisionObject.get(); } + /// Return solid flag. Not used by the object itself, true by default. + bool isSolid() const + { + return mSolid; + } + + void setSolid(bool solid) + { + mSolid = solid; + } + + bool isAnimated() const + { + return !mShapeInstance->mAnimatedShapes.empty(); + } + void animateCollisionShapes(btCollisionWorld* collisionWorld) { if (mShapeInstance->mAnimatedShapes.empty()) @@ -555,12 +575,12 @@ namespace MWPhysics btCompoundShape* compound = dynamic_cast(mShapeInstance->getCollisionShape()); - for (std::map::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it) + for (std::map::iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end();) { int recIndex = it->first; int shapeIndex = it->second; - NifOsg::FindRecIndexVisitor visitor(recIndex); + NifOsg::FindGroupByRecIndex visitor(recIndex); mPtr.getRefData().getBaseNode()->accept(visitor); if (!visitor.mFound) { @@ -570,6 +590,24 @@ namespace MWPhysics osg::NodePath path = visitor.mFoundPath; path.erase(path.begin()); + + // Attempt to remove "animated" shapes that are not actually animated + // We may get these because the BulletNifLoader does not know if a .kf file with additional controllers will be attached later on. + // On the first animateCollisionShapes call, we'll consider the graph completely loaded (with extra controllers and what not), + // so now we can better decide if the shape is really animated. + bool animated = false; + for (osg::NodePath::iterator nodePathIt = path.begin(); nodePathIt != path.end(); ++nodePathIt) + { + osg::Node* node = *nodePathIt; + if (node->getUpdateCallback()) + animated = true; + } + if (!animated) + { + mShapeInstance->mAnimatedShapes.erase(it++); + break; + } + osg::Matrixf matrix = osg::computeLocalToWorld(path); osg::Vec3f scale = matrix.getScale(); matrix.orthoNormalize(matrix); @@ -582,6 +620,8 @@ namespace MWPhysics compound->getChildShape(shapeIndex)->setLocalScaling(compound->getLocalScaling() * toBullet(scale)); compound->updateChildTransform(shapeIndex, transform); + + ++it; } collisionWorld->updateSingleAabb(mCollisionObject.get()); @@ -589,13 +629,14 @@ namespace MWPhysics private: std::auto_ptr mCollisionObject; - osg::ref_ptr mShapeInstance; + osg::ref_ptr mShapeInstance; + bool mSolid; }; // --------------------------------------------------------------- PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) - : mShapeManager(new NifBullet::BulletShapeManager(resourceSystem->getVFS())) + : mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) , mWaterHeight(0) @@ -656,6 +697,35 @@ namespace MWPhysics return mDebugDrawEnabled; } + void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr) + { + ObjectMap::iterator found = mObjects.find(ptr); + if (found == mObjects.end()) + return; + + found->second->setSolid(false); + } + + bool PhysicsSystem::isOnSolidGround (const MWWorld::Ptr& actor) const + { + const Actor* physactor = getActor(actor); + if (!physactor || !physactor->getOnGround()) + return false; + + CollisionMap::const_iterator found = mStandingCollisions.find(actor); + if (found == mStandingCollisions.end()) + return true; // assume standing on terrain (which is a non-object, so not collision tracked) + + ObjectMap::const_iterator foundObj = mObjects.find(found->second); + if (foundObj == mObjects.end()) + return false; + + if (!foundObj->second->isSolid()) + return false; + + return true; + } + class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback { const btCollisionObject* mMe; @@ -700,7 +770,7 @@ namespace MWPhysics } }; - std::pair PhysicsSystem::getHitContact(const MWWorld::Ptr& actor, + std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orient, float queryDistance) @@ -720,24 +790,54 @@ namespace MWPhysics object.setWorldTransform(btTransform(toBullet(orient), toBullet(center))); const btCollisionObject* me = NULL; - Actor* physactor = getActor(actor); + const Actor* physactor = getActor(actor); if (physactor) me = physactor->getCollisionObject(); DeepestNotMeContactTestResultCallback resultCallback(me, toBullet(origin)); resultCallback.m_collisionFilterGroup = CollisionType_Actor; - resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; + resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; mCollisionWorld->contactTest(&object, resultCallback); if (resultCallback.mObject) { - const PtrHolder* holder = static_cast(resultCallback.mObject->getUserPointer()); + PtrHolder* holder = static_cast(resultCallback.mObject->getUserPointer()); if (holder) return std::make_pair(holder->getPtr(), toOsg(resultCallback.mContactPoint)); } return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); } + float PhysicsSystem::getHitDistance(const osg::Vec3f &point, const MWWorld::ConstPtr &target) const + { + btCollisionObject* targetCollisionObj = NULL; + const Actor* actor = getActor(target); + if (actor) + targetCollisionObj = actor->getCollisionObject(); + if (!targetCollisionObj) + return 0.f; + + btTransform rayFrom; + rayFrom.setIdentity(); + rayFrom.setOrigin(toBullet(point)); + + // target the collision object's world origin, this should be the center of the collision object + btTransform rayTo; + rayTo.setIdentity(); + rayTo.setOrigin(targetCollisionObj->getWorldTransform().getOrigin()); + + btCollisionWorld::ClosestRayResultCallback cb(rayFrom.getOrigin(), rayTo.getOrigin()); + + btCollisionWorld::rayTestSingle(rayFrom, rayTo, targetCollisionObj, targetCollisionObj->getCollisionShape(), targetCollisionObj->getWorldTransform(), cb); + if (!cb.hasHit()) + { + // didn't hit the target. this could happen if point is already inside the collision box + return 0.f; + } + else + return (point - toOsg(cb.m_hitPointWorld)).length(); + } + class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: @@ -757,7 +857,7 @@ namespace MWPhysics const btCollisionObject* mMe; }; - PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::Ptr ignore, int mask, int group) + PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore, int mask, int group) const { btVector3 btFrom = toBullet(from); btVector3 btTo = toBullet(to); @@ -765,7 +865,7 @@ namespace MWPhysics const btCollisionObject* me = NULL; if (!ignore.isEmpty()) { - Actor* actor = getActor(ignore); + const Actor* actor = getActor(ignore); if (actor) me = actor->getCollisionObject(); } @@ -792,7 +892,7 @@ namespace MWPhysics { btCollisionWorld::ClosestConvexResultCallback callback(toBullet(from), toBullet(to)); callback.m_collisionFilterGroup = 0xff; - callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; + callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; btSphereShape shape(radius); const btQuaternion btrot = btQuaternion::getIdentity(); @@ -812,22 +912,18 @@ namespace MWPhysics return result; } - bool PhysicsSystem::getLineOfSight(const MWWorld::Ptr &actor1, const MWWorld::Ptr &actor2) + bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { - Actor* physactor1 = getActor(actor1); - Actor* physactor2 = getActor(actor2); + const Actor* physactor1 = getActor(actor1); + const Actor* physactor2 = getActor(actor2); if (!physactor1 || !physactor2) return false; - osg::Vec3f halfExt1 = physactor1->getHalfExtents(); - osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); - pos1.z() += halfExt1.z()*2*0.9f; // eye level - osg::Vec3f halfExt2 = physactor2->getHalfExtents(); - osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); - pos2.z() += halfExt2.z()*2*0.9f; + osg::Vec3f pos1 (physactor1->getPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level + osg::Vec3f pos2 (physactor2->getPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); - RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap); + RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); return !result.mHit; } @@ -869,18 +965,43 @@ namespace MWPhysics } } - osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::Ptr &actor) + osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const { - Actor* physactor = getActor(actor); + const Actor* physactor = getActor(actor); if (physactor) return physactor->getHalfExtents(); else return osg::Vec3f(); } + osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const + { + const Actor* physactor = getActor(actor); + if (physactor) + return physactor->getRenderingHalfExtents(); + else + return osg::Vec3f(); + } + + osg::Vec3f PhysicsSystem::getPosition(const MWWorld::ConstPtr &actor) const + { + const Actor* physactor = getActor(actor); + if (physactor) + return physactor->getPosition(); + else + return osg::Vec3f(); + } + class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback { public: + ContactTestResultCallback(const btCollisionObject* testedAgainst) + : mTestedAgainst(testedAgainst) + { + } + + const btCollisionObject* mTestedAgainst; + std::vector mResult; #if BT_BULLET_VERSION >= 281 @@ -889,30 +1010,34 @@ namespace MWPhysics const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; + if (collisionObject == mTestedAgainst) + collisionObject = col1Wrap->m_collisionObject; #else virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, const btCollisionObject* col1, int partId1, int index1) { const btCollisionObject* collisionObject = col0; + if (collisionObject == mTestedAgainst) + collisionObject = col1; #endif - const PtrHolder* holder = static_cast(collisionObject->getUserPointer()); + PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder) mResult.push_back(holder->getPtr()); return 0.f; } }; - std::vector PhysicsSystem::getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask) + std::vector PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const { btCollisionObject* me = NULL; - ObjectMap::iterator found = mObjects.find(ptr); + ObjectMap::const_iterator found = mObjects.find(ptr); if (found != mObjects.end()) me = found->second->getCollisionObject(); else return std::vector(); - ContactTestResultCallback resultCallback; + ContactTestResultCallback resultCallback (me); resultCallback.m_collisionFilterGroup = collisionGroup; resultCallback.m_collisionFilterMask = collisionMask; mCollisionWorld->contactTest(me, resultCallback); @@ -948,16 +1073,19 @@ namespace MWPhysics } } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { - osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); - if (!shapeInstance->getCollisionShape()) + osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + if (!shapeInstance || !shapeInstance->getCollisionShape()) return; Object *obj = new Object(ptr, shapeInstance); mObjects.insert(std::make_pair(ptr, obj)); - mCollisionWorld->addCollisionObject(obj->getCollisionObject(), CollisionType_World, + if (obj->isAnimated()) + mAnimatedObjects.insert(obj); + + mCollisionWorld->addCollisionObject(obj->getCollisionObject(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } @@ -967,6 +1095,9 @@ namespace MWPhysics if (found != mObjects.end()) { mCollisionWorld->removeCollisionObject(found->second->getCollisionObject()); + + mAnimatedObjects.erase(found->second); + delete found->second; mObjects.erase(found); } @@ -1015,7 +1146,6 @@ namespace MWPhysics mActors.insert(std::make_pair(updated, actor)); } - updateCollisionMapPtr(mCollisions, old, updated); updateCollisionMapPtr(mStandingCollisions, old, updated); } @@ -1027,12 +1157,20 @@ namespace MWPhysics return NULL; } + const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const + { + ActorMap::const_iterator found = mActors.find(ptr); + if (found != mActors.end()) + return found->second; + return NULL; + } + void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); - float scale = ptr.getCellRef().getScale(); if (found != mObjects.end()) { + float scale = ptr.getCellRef().getScale(); found->second->setScale(scale); mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); return; @@ -1082,9 +1220,10 @@ namespace MWPhysics } } - void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) - { - osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { + osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + if (!shapeInstance) + return; Actor* actor = new Actor(ptr, shapeInstance, mCollisionWorld); mActors.insert(std::make_pair(ptr, actor)); @@ -1122,7 +1261,6 @@ namespace MWPhysics void PhysicsSystem::clearQueuedMovement() { mMovementQueue.clear(); - mCollisions.clear(); mStandingCollisions.clear(); } @@ -1134,7 +1272,6 @@ namespace MWPhysics if(mTimeAccum >= 1.0f/60.0f) { // Collision events should be available on every frame - mCollisions.clear(); mStandingCollisions.clear(); const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -1166,9 +1303,9 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - osg::Vec3f newpos = MovementSolver::move(iter->first, physicActor, iter->second, mTimeAccum, + osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum, world->isFlying(iter->first), - waterlevel, slowFall, mCollisionWorld, mCollisions, mStandingCollisions); + waterlevel, slowFall, mCollisionWorld, mStandingCollisions); float heightDiff = newpos.z() - oldHeight; @@ -1187,8 +1324,8 @@ namespace MWPhysics void PhysicsSystem::stepSimulation(float dt) { - for (ObjectMap::iterator it = mObjects.begin(); it != mObjects.end(); ++it) - it->second->animateCollisionShapes(mCollisionWorld); + for (std::set::iterator it = mAnimatedObjects.begin(); it != mAnimatedObjects.end(); ++it) + (*it)->animateCollisionShapes(mCollisionWorld); CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); @@ -1200,7 +1337,7 @@ namespace MWPhysics mDebugDrawer->step(); } - bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::Ptr &object) const + bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it) { @@ -1210,7 +1347,7 @@ namespace MWPhysics return false; } - void PhysicsSystem::getActorsStandingOn(const MWWorld::Ptr &object, std::vector &out) const + void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector &out) const { for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it) { @@ -1219,23 +1356,16 @@ namespace MWPhysics } } - bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr &actor, const MWWorld::Ptr &object) const + bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { - for (CollisionMap::const_iterator it = mCollisions.begin(); it != mCollisions.end(); ++it) - { - if (it->first == actor && it->second == object) - return true; - } - return false; + std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); + return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); } - void PhysicsSystem::getActorsCollidingWith(const MWWorld::Ptr &object, std::vector &out) const + void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr &object, std::vector &out) const { - for (CollisionMap::const_iterator it = mCollisions.begin(); it != mCollisions.end(); ++it) - { - if (it->second == object) - out.push_back(it->first); - } + std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); + out.insert(out.end(), collisions.begin(), collisions.end()); } void PhysicsSystem::disableWater() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index db8da2886f..f53d7e3d9b 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,7 +22,7 @@ namespace MWRender class DebugDrawer; } -namespace NifBullet +namespace Resource { class BulletShapeManager; } @@ -56,12 +57,13 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); Actor* getActor(const MWWorld::Ptr& ptr); + const Actor* getActor(const MWWorld::ConstPtr& ptr) const; // Object or Actor void remove (const MWWorld::Ptr& ptr); @@ -80,14 +82,21 @@ namespace MWPhysics void stepSimulation(float dt); void debugDraw(); - std::vector getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with + std::vector getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with osg::Vec3f traceDown(const MWWorld::Ptr &ptr, float maxHeight); - std::pair getHitContact(const MWWorld::Ptr& actor, + std::pair getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orientation, float queryDistance); + + /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the + /// target vector hits the collision shape and then calculates distance from the intersection point. + /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be successful. + /// \note Only Actor targets are supported at the moment. + float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const; + struct RayResult { bool mHit; @@ -97,18 +106,25 @@ namespace MWPhysics }; /// @param me Optional, a Ptr to ignore in the list of results - RayResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::Ptr ignore = MWWorld::Ptr(), int mask = - CollisionType_World|CollisionType_HeightMap|CollisionType_Actor, int group=0xff); + RayResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore = MWWorld::ConstPtr(), int mask = + CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const; RayResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius); /// Return true if actor1 can see actor2. - bool getLineOfSight(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); + bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const; bool isOnGround (const MWWorld::Ptr& actor); /// Get physical half extents (scaled) of the given actor. - osg::Vec3f getHalfExtents(const MWWorld::Ptr& actor); + osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor) const; + + /// @see MWPhysics::Actor::getRenderingHalfExtents + osg::Vec3f getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const; + + /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. + /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. + osg::Vec3f getPosition(const MWWorld::ConstPtr& actor) const; /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to applyQueuedMovement. @@ -123,20 +139,26 @@ namespace MWPhysics /// 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; + bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& 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; + void getActorsStandingOn(const MWWorld::ConstPtr& 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; + bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& 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; + void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; bool toggleDebugRendering(); + /// Mark the given object as a 'non-solid' object. A non-solid object means that + /// \a isOnSolidGround will return false for actors standing on that object. + void markAsNonSolid (const MWWorld::ConstPtr& ptr); + + bool isOnSolidGround (const MWWorld::Ptr& actor) const; + private: void updateWater(); @@ -146,12 +168,14 @@ namespace MWPhysics btCollisionDispatcher* mDispatcher; btCollisionWorld* mCollisionWorld; - std::auto_ptr mShapeManager; + std::auto_ptr mShapeManager; - typedef std::map ObjectMap; + typedef std::map ObjectMap; ObjectMap mObjects; - typedef std::map ActorMap; + std::set mAnimatedObjects; // stores pointers to elements in mObjects + + typedef std::map ActorMap; ActorMap mActors; typedef std::map, HeightField*> HeightFieldMap; @@ -159,11 +183,9 @@ namespace MWPhysics bool mDebugDrawEnabled; - // 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. + // Tracks standing collisions happening during a single frame. + // This will detect standing on an object, but won't detect running e.g. against a wall. typedef std::map CollisionMap; - CollisionMap mCollisions; CollisionMap mStandingCollisions; // replaces all occurences of 'old' in the map by 'updated', no matter if its a key or value diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b2fa873102..68a073731d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -18,6 +19,7 @@ #include #include +#include #include #include // KeyframeHolder @@ -31,6 +33,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -174,72 +177,93 @@ namespace return 0.0f; } + /// @brief Base class for visitors that remove nodes from a scene graph. + /// Subclasses need to fill the mToRemove vector. + /// To use, node->accept(removeVisitor); removeVisitor.remove(); + class RemoveVisitor : public osg::NodeVisitor + { + public: + RemoveVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void remove() + { + for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + it->second->removeChild(it->first); + } + + protected: + // + typedef std::vector > RemoveVec; + std::vector > mToRemove; + }; // Removes all drawables from a graph. - class RemoveDrawableVisitor : public osg::NodeVisitor + class RemoveDrawableVisitor : public RemoveVisitor { public: - RemoveDrawableVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + virtual void apply(osg::Geode &geode) { + applyImpl(geode); } - virtual void apply(osg::Geode &node) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + virtual void apply(osg::Drawable& drw) { - // Not safe to remove in apply(), since the visitor is still iterating the child list - osg::Group* parent = node.getParent(0); - // prune nodes that would be empty after the removal - if (parent->getNumChildren() == 1 && parent->getDataVariance() == osg::Object::STATIC) - mToRemove.push_back(parent); - else - mToRemove.push_back(&node); - traverse(node); + applyImpl(drw); } +#endif - void remove() + void applyImpl(osg::Node& node) { - for (std::vector::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + osg::NodePath::iterator parent = getNodePath().end()-2; + // We know that the parent is a Group because only Groups can have children. + osg::Group* parentGroup = static_cast(*parent); + + // Try to prune nodes that would be empty after the removal + if (parent != getNodePath().begin()) { - osg::Node* node = *it; - if (node->getNumParents()) - node->getParent(0)->removeChild(node); + // This could be extended to remove the parent's parent, and so on if they are empty as well. + // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. + osg::Group* parentParent = static_cast(*(parent - 1)); + if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) + { + mToRemove.push_back(std::make_pair(parentGroup, parentParent)); + return; + } } - } - private: - std::vector mToRemove; + mToRemove.push_back(std::make_pair(&node, parentGroup)); + } }; - class RemoveTriBipVisitor : public osg::NodeVisitor + class RemoveTriBipVisitor : public RemoveVisitor { public: - RemoveTriBipVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + virtual void apply(osg::Geode &node) { + applyImpl(node); } - virtual void apply(osg::Geode &node) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + virtual void apply(osg::Drawable& drw) { - const std::string toFind = "tri bip"; - if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) - { - // Not safe to remove in apply(), since the visitor is still iterating the child list - mToRemove.push_back(&node); - } + applyImpl(drw); } +#endif - void remove() + void applyImpl(osg::Node& node) { - for (std::vector::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + const std::string toFind = "tri bip"; + if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) { - osg::Node* node = *it; - if (node->getNumParents()) - node->getParent(0)->removeChild(node); + osg::Group* parent = static_cast(*(getNodePath().end()-2)); + // Not safe to remove in apply(), since the visitor is still iterating the child list + mToRemove.push_back(std::make_pair(&node, parent)); } } - - private: - std::vector mToRemove; }; } @@ -288,6 +312,7 @@ namespace MWRender Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : mInsert(parentNode) + , mSkeleton(NULL) , mPtr(ptr) , mResourceSystem(resourceSystem) , mAccumulate(1.f, 1.f, 0.f) @@ -315,10 +340,8 @@ namespace MWRender void Animation::setActive(bool active) { - if (SceneUtil::Skeleton* skel = dynamic_cast(mObjectRoot.get())) - { - skel->setActive(active); - } + if (mSkeleton) + mSkeleton->setActive(active); } void Animation::updatePtr(const MWWorld::Ptr &ptr) @@ -368,19 +391,21 @@ namespace MWRender void Animation::addAnimSource(const std::string &model) { std::string kfname = model; - Misc::StringUtils::toLower(kfname); + Misc::StringUtils::lowerCaseInPlace(kfname); if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); + else + return; if(!mResourceSystem->getVFS()->exists(kfname)) return; boost::shared_ptr animsrc; animsrc.reset(new AnimSource); - animsrc->mKeyframes = mResourceSystem->getSceneManager()->getKeyframes(kfname); + animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); - if (animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) + if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) return; for (NifOsg::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); @@ -493,7 +518,16 @@ namespace MWRender } if (mTextKeyListener) - mTextKeyListener->handleTextKey(groupname, key, map); + { + try + { + mTextKeyListener->handleTextKey(groupname, key, map); + } + catch (std::exception& e) + { + std::cerr << "Error handling text key " << evt << ": " << e.what() << std::endl; + } + } } void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, @@ -942,6 +976,7 @@ namespace MWRender mObjectRoot->getParent(0)->removeChild(mObjectRoot); } mObjectRoot = NULL; + mSkeleton = NULL; mNodeMap.clear(); mActiveControllers.clear(); @@ -949,18 +984,29 @@ namespace MWRender mAccumCtrl = NULL; if (!forceskeleton) - mObjectRoot = mResourceSystem->getSceneManager()->createInstance(model, mInsert); + { + osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model, mInsert); + mObjectRoot = created->asGroup(); + if (!mObjectRoot) + { + mInsert->removeChild(created); + mObjectRoot = new osg::Group; + mObjectRoot->addChild(created); + mInsert->addChild(mObjectRoot); + } + } else { - osg::ref_ptr newObjectRoot = mResourceSystem->getSceneManager()->createInstance(model); - if (!dynamic_cast(newObjectRoot.get())) + osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr skel = dynamic_cast(created.get()); + if (!skel) { - osg::ref_ptr skel = new SceneUtil::Skeleton; - skel->addChild(newObjectRoot); - newObjectRoot = skel; + skel = new SceneUtil::Skeleton; + skel->addChild(created); } - mInsert->addChild(newObjectRoot); - mObjectRoot = newObjectRoot; + mSkeleton = skel.get(); + mObjectRoot = skel; + mInsert->addChild(mObjectRoot); } if (previousStateset) @@ -989,17 +1035,17 @@ namespace MWRender osg::Group* Animation::getObjectRoot() { - return static_cast(mObjectRoot.get()); + return mObjectRoot.get(); } osg::Group* Animation::getOrCreateObjectRoot() { if (mObjectRoot) - return static_cast(mObjectRoot.get()); + return mObjectRoot.get(); mObjectRoot = new osg::Group; mInsert->addChild(mObjectRoot); - return static_cast(mObjectRoot.get()); + return mObjectRoot.get(); } void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) @@ -1064,8 +1110,8 @@ namespace MWRender } osg::ref_ptr lightSource = new SceneUtil::LightSource; - osg::Light* light = new osg::Light; - lightSource->setLight(light); + osg::ref_ptr light (new osg::Light); + lightSource->setNodeMask(Mask_Lighting); const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); @@ -1095,6 +1141,8 @@ namespace MWRender light->setAmbient(osg::Vec4f(0,0,0,1)); light->setSpecular(osg::Vec4f(0,0,0,0)); + lightSource->setLight(light); + osg::ref_ptr ctrl (new SceneUtil::LightController); ctrl->setDiffuse(light->getDiffuse()); if (esmLight->mData.mFlags & ESM::Light::Flicker) @@ -1111,25 +1159,6 @@ namespace MWRender attachTo->addChild(lightSource); } - class DisableFreezeOnCullVisitor : public osg::NodeVisitor - { - public: - DisableFreezeOnCullVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { - } - - virtual void apply(osg::Geode &geode) - { - for (unsigned int i=0; i(drw)) - partsys->setFreezeOnCull(false); - } - } - }; - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) { if (!mObjectRoot.get()) @@ -1163,7 +1192,7 @@ namespace MWRender node->accept(findMaxLengthVisitor); // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters - DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; node->accept(disableFreezeOnCullVisitor); params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); @@ -1309,21 +1338,31 @@ namespace MWRender } else { - if (!mGlowLight) + effect += 3; + float radius = effect * 66.f; + float linearAttenuation = 0.5f / effect; + + if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) { - mGlowLight = new SceneUtil::LightSource; - mGlowLight->setLight(new osg::Light); - osg::Light* light = mGlowLight->getLight(); + if (mGlowLight) + { + mInsert->removeChild(mGlowLight); + mGlowLight = NULL; + } + + osg::ref_ptr light (new osg::Light); light->setDiffuse(osg::Vec4f(0,0,0,0)); light->setSpecular(osg::Vec4f(0,0,0,0)); light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f)); + light->setLinearAttenuation(linearAttenuation); + + mGlowLight = new SceneUtil::LightSource; + mGlowLight->setNodeMask(Mask_Lighting); mInsert->addChild(mGlowLight); + mGlowLight->setLight(light); } - effect += 3; - osg::Light* light = mGlowLight->getLight(); - mGlowLight->setRadius(effect * 66.f); - light->setLinearAttenuation(0.5f/effect); + mGlowLight->setRadius(radius); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 1e46cc71a5..213e33f673 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -24,6 +24,7 @@ namespace NifOsg namespace SceneUtil { class LightSource; + class Skeleton; } namespace MWRender @@ -61,6 +62,9 @@ public: private: osg::ref_ptr mNode; + + void operator= (const PartHolder&); + PartHolder(const PartHolder&); }; typedef boost::shared_ptr PartHolderPtr; @@ -204,7 +208,8 @@ protected: osg::ref_ptr mInsert; - osg::ref_ptr mObjectRoot; + osg::ref_ptr mObjectRoot; + SceneUtil::Skeleton* mSkeleton; // The node expected to accumulate movement during movement animations. osg::ref_ptr mAccumRoot; @@ -439,6 +444,7 @@ public: virtual void setHeadYaw(float yawRadians); virtual float getHeadPitch() const; virtual float getHeadYaw() const; + virtual void setAccurateAiming(bool enabled) {} private: Animation(const Animation&); diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index c6d7935c54..eaf36cf857 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -78,6 +78,7 @@ void DebugDrawer::step() mWorld->debugDrawWorld(); mDrawArrays->setCount(mVertices->size()); mVertices->dirty(); + mGeometry->dirtyBound(); } } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index fb6573d65f..88934414f6 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -1,8 +1,9 @@ #include "camera.hpp" -#include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -42,7 +43,8 @@ namespace MWRender { Camera::Camera (osg::Camera* camera) - : mCamera(camera), + : mHeightScale(1.f), + mCamera(camera), mAnimation(NULL), mFirstPersonView(true), mPreviewMode(false), @@ -93,7 +95,7 @@ namespace MWRender osg::Vec3d position = worldMat.getTrans(); if (!isFirstPerson()) - position.z() += mHeight; + position.z() += mHeight * mHeightScale; return position; } @@ -372,11 +374,17 @@ namespace MWRender mTrackingNode = mAnimation->getNode("Camera"); if (!mTrackingNode) mTrackingNode = mAnimation->getNode("Head"); + mHeightScale = 1.f; } else { mAnimation->setViewMode(NpcAnimation::VM_Normal); - mTrackingNode = mTrackingPtr.getRefData().getBaseNode(); + SceneUtil::PositionAttitudeTransform* transform = mTrackingPtr.getRefData().getBaseNode(); + mTrackingNode = transform; + if (transform) + mHeightScale = transform->getScale().z(); + else + mHeightScale = 1.f; } rotateCamera(getPitch(), getYaw(), false); } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index a655e1c1f8..fab63cd3f2 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -29,6 +29,7 @@ namespace MWRender MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; + float mHeightScale; osg::ref_ptr mCamera; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 93afeda25b..a6f68b5d4c 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -30,6 +30,7 @@ namespace MWRender public: DrawOnceCallback () : mRendered(false) + , mLastRenderedFrame(0) { } @@ -38,13 +39,14 @@ namespace MWRender if (!mRendered) { mRendered = true; + + mLastRenderedFrame = nv->getTraversalNumber(); + traverse(node, nv); } else { node->setNodeMask(0); } - - traverse(node, nv); } void redrawNextFrame() @@ -52,8 +54,14 @@ namespace MWRender mRendered = false; } + unsigned int getLastRenderedFrame() const + { + return mLastRenderedFrame; + } + private: bool mRendered; + unsigned int mLastRenderedFrame; }; CharacterPreview::CharacterPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem, @@ -157,11 +165,10 @@ namespace MWRender void CharacterPreview::rebuild() { - delete mAnimation; - mAnimation = NULL; + mAnimation.reset(NULL); - mAnimation = new NpcAnimation(mCharacter, mNode, mResourceSystem, true, true, - (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + mAnimation.reset(new NpcAnimation(mCharacter, mNode, mResourceSystem, true, true, + (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal))); onSetup(); @@ -194,7 +201,7 @@ namespace MWRender void InventoryPreview::update() { - if (!mAnimation) + if (!mAnimation.get()) return; mAnimation->showWeapons(true); @@ -262,9 +269,19 @@ namespace MWRender int InventoryPreview::getSlotSelected (int posX, int posY) { - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, posX, posY)); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_ONE); + float projX = (posX / mCamera->getViewport()->width()) * 2 - 1.f; + float projY = (posY / mCamera->getViewport()->height()) * 2 - 1.f; + // With Intersector::WINDOW, the intersection ratios are slightly inaccurate. Seems to be a + // precision issue - compiling with OSG_USE_FLOAT_MATRIX=0, Intersector::WINDOW works ok. + // Using Intersector::PROJECTION results in better precision because the start/end points and the model matrices + // don't go through as many transformations. + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, projX, projY)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); osgUtil::IntersectionVisitor visitor(intersector); + visitor.setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); + // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly + visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); mCamera->setNodeMask(~0); @@ -287,7 +304,7 @@ namespace MWRender void InventoryPreview::onSetup() { osg::Vec3f scale (1.f, 1.f, 1.f); - mCharacter.getClass().adjustScale(mCharacter, scale); + mCharacter.getClass().adjustScale(mCharacter, scale, true); mNode->setScale(scale); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 0f85cc3bf9..2b7984b009 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -2,6 +2,9 @@ #define MWRENDER_CHARACTERPREVIEW_H #include +#include + +#include #include @@ -61,7 +64,7 @@ namespace MWRender MWWorld::Ptr mCharacter; - MWRender::NpcAnimation* mAnimation; + std::auto_ptr mAnimation; osg::ref_ptr mNode; std::string mCurrentAnimGroup; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f46736a394..7c447182f2 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -2,12 +2,11 @@ #include -#include - #include #include #include #include +#include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 3445e4189b..9e8a545f85 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -62,9 +62,8 @@ namespace class CameraUpdateGlobalCallback : public osg::NodeCallback { public: - CameraUpdateGlobalCallback(osg::Camera* cam, MWRender::GlobalMap* parent) + CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) : mRendered(false) - , mCamera(cam) , mParent(parent) { } @@ -73,7 +72,7 @@ namespace { if (mRendered) { - mCamera->setNodeMask(0); + node->setNodeMask(0); return; } @@ -82,13 +81,12 @@ namespace if (!mRendered) { mRendered = true; - mParent->markForRemoval(mCamera); + mParent->markForRemoval(static_cast(node)); } } private: bool mRendered; - osg::ref_ptr mCamera; MWRender::GlobalMap* mParent; }; @@ -167,7 +165,7 @@ namespace MWRender int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); int texelX = (x-mMinX) * mCellSize + cellX; - int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY); + int texelY = (y-mMinY) * mCellSize + cellY; unsigned char r,g,b; @@ -253,6 +251,7 @@ namespace MWRender camera->setProjectionMatrix(osg::Matrix::identity()); camera->setProjectionResizePolicy(osg::Camera::FIXED); camera->setRenderOrder(osg::Camera::PRE_RENDER); + y = mHeight - y - height; // convert top-left origin to bottom-left camera->setViewport(x, y, width, height); if (clear) @@ -263,11 +262,14 @@ namespace MWRender else camera->setClearMask(GL_NONE); - camera->setUpdateCallback(new CameraUpdateGlobalCallback(camera, this)); + camera->setUpdateCallback(new CameraUpdateGlobalCallback(this)); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->attach(osg::Camera::COLOR_BUFFER, mOverlayTexture); + // no need for a depth buffer + camera->setImplicitBufferAttachmentMask(osg::DisplaySettings::IMPLICIT_COLOR_BUFFER_ATTACHMENT); + if (cpuCopy) { // Attach an image to copy the render back to the CPU when finished @@ -288,10 +290,12 @@ namespace MWRender { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); osg::ref_ptr depth = new osg::Depth; - depth->setFunction(osg::Depth::ALWAYS); - geom->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - geom->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); - geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + depth->setWriteMask(0); + osg::StateSet* stateset = geom->getOrCreateStateSet(); + stateset->setAttribute(depth); + stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); osg::ref_ptr geode = new osg::Geode; geode->addDrawable(geom); camera->addChild(geode); @@ -308,12 +312,12 @@ namespace MWRender return; int originX = (cellX - mMinX) * mCellSize; - int originY = (cellY - mMinY) * mCellSize; + int originY = (cellY - mMinY + 1) * mCellSize; // +1 because we want the top left corner of the cell, not the bottom left if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; - requestOverlayTextureUpdate(originX, originY, mCellSize, mCellSize, localMapTexture, false, true); + requestOverlayTextureUpdate(originX, mHeight - originY, mCellSize, mCellSize, localMapTexture, false, true); } void GlobalMap::clear() diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 91c17c06f1..07ae7cdae7 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -70,7 +70,7 @@ namespace MWRender private: /** * Request rendering a 2d quad onto mOverlayTexture. - * x, y, width and height are the destination coordinates. + * x, y, width and height are the destination coordinates (top-left coordinate origin) * @param cpuCopy copy the resulting render onto mOverlayImage as well? */ void requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index fe685f97c0..9d5950a90e 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -30,22 +31,21 @@ namespace class CameraLocalUpdateCallback : public osg::NodeCallback { public: - CameraLocalUpdateCallback(osg::Camera* cam, MWRender::LocalMap* parent) + CameraLocalUpdateCallback(MWRender::LocalMap* parent) : mRendered(false) - , mCamera(cam) , mParent(parent) { } - virtual void operator()(osg::Node*, osg::NodeVisitor*) + virtual void operator()(osg::Node* node, osg::NodeVisitor*) { if (mRendered) - mCamera->setNodeMask(0); + node->setNodeMask(0); if (!mRendered) { mRendered = true; - mParent->markForRemoval(mCamera); + mParent->markForRemoval(static_cast(node)); } // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, @@ -55,7 +55,6 @@ namespace private: bool mRendered; - osg::ref_ptr mCamera; MWRender::LocalMap* mParent; }; @@ -173,7 +172,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setCullMask(Mask_Scene|Mask_Water|Mask_Terrain); + camera->setCullMask(Mask_Scene|Mask_SimpleWater|Mask_Terrain); camera->setNodeMask(Mask_RenderToTexture); osg::ref_ptr stateset = new osg::StateSet; @@ -205,7 +204,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setStateSet(stateset); camera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); camera->setViewport(0, 0, mMapResolution, mMapResolution); - camera->setUpdateCallback(new CameraLocalUpdateCallback(camera, this)); + camera->setUpdateCallback(new CameraLocalUpdateCallback(this)); return camera; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index cba6c56962..4e367b3b18 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -32,6 +33,7 @@ #include "camera.hpp" #include "rotatecontroller.hpp" #include "renderbin.hpp" +#include "vismask.hpp" namespace { @@ -273,14 +275,18 @@ NpcAnimation::~NpcAnimation() mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableListener, bool disableSounds, ViewMode viewMode) +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, + bool disableListener, bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) : Animation(ptr, parentNode, resourceSystem), mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), mNpcType(Type_Normal), - mSoundsDisabled(disableSounds) + mFirstPersonFieldOfView(firstPersonFieldOfView), + mSoundsDisabled(disableSounds), + mAccurateAiming(false), + mAimingFactor(0.f) { mNpc = mPtr.get()->mBase; @@ -333,6 +339,37 @@ public: osg::ref_ptr mDepth; }; +/// Overrides Field of View to given value for rendering the subgraph. +/// Must be added as cull callback. +class OverrideFieldOfViewCallback : public osg::NodeCallback +{ +public: + OverrideFieldOfViewCallback(float fov) + : mFov(fov) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + osg::RefMatrix* projectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + float fov, aspect, zNear, zFar; + if (projectionMatrix->getPerspective(fov, aspect, zNear, zFar)) + { + fov = mFov; + projectionMatrix->makePerspective(fov, aspect, zNear, zFar); + cv->pushProjectionMatrix(projectionMatrix); + traverse(node, nv); + cv->popProjectionMatrix(); + } + else + traverse(node, nv); + } + +private: + float mFov; +}; + void NpcAnimation::setRenderBin() { if (mViewMode == VM_FirstPerson) @@ -441,6 +478,8 @@ void NpcAnimation::updateNpcBase() } else { + mObjectRoot->setNodeMask(Mask_FirstPerson); + mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); if(isWerewolf) addAnimSource(smodel); else @@ -724,7 +763,14 @@ osg::Vec3f NpcAnimation::runAnimation(float timepassed) if (mFirstPersonNeckController) { - mFirstPersonNeckController->setRotate(osg::Quat(mPtr.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))); + if (mAccurateAiming) + mAimingFactor = 1.f; + else + mAimingFactor = std::max(0.f, mAimingFactor - timepassed * 0.5f); + + float rotateFactor = 0.75f + 0.25f * mAimingFactor; + + mFirstPersonNeckController->setRotate(osg::Quat(mPtr.getRefData().getPosition().rot[0] * rotateFactor, osg::Vec3f(-1,0,0))); mFirstPersonNeckController->setOffset(mFirstPersonOffset); } @@ -1070,4 +1116,9 @@ void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) mHeadAnimationTime->updatePtr(updated); } +void NpcAnimation::setAccurateAiming(bool enabled) +{ + mAccurateAiming = enabled; +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index b4272226d9..c5fc62f9cc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -61,12 +61,17 @@ private: int mPartPriorities[ESM::PRT_Count]; osg::Vec3f mFirstPersonOffset; + // Field of view to use when rendering first person meshes + float mFirstPersonFieldOfView; boost::shared_ptr mHeadAnimationTime; boost::shared_ptr mWeaponAnimationTime; bool mSoundsDisabled; + bool mAccurateAiming; + float mAimingFactor; + void updateNpcBase(); PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, @@ -99,11 +104,15 @@ public: * @param viewMode */ NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableListener = false, - bool disableSounds = false, ViewMode viewMode=VM_Normal); + bool disableSounds = false, ViewMode viewMode=VM_Normal, float firstPersonFieldOfView=55.f); virtual ~NpcAnimation(); virtual void enableHeadAnimation(bool enable); + /// 1: the first person meshes follow the camera's rotation completely + /// 0: the first person meshes follow the camera with a reduced factor, so you can look down at your own hands + virtual void setAccurateAiming(bool enabled); + virtual void setWeaponGroup(const std::string& group); virtual osg::Vec3f runAnimation(float timepassed); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index bdefdcafa3..f58ebb9174 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -4,8 +4,8 @@ #include #include -#include #include +#include #include #include @@ -13,6 +13,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" @@ -54,11 +55,19 @@ namespace for (std::vector::iterator it = partsysVector.begin(); it != partsysVector.end(); ++it) geode.removeDrawable(*it); } +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + virtual void apply(osg::Drawable& drw) + { + if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) + mToRemove.push_back(partsys); + } +#endif void remove() { for (std::vector >::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) { + // FIXME: a Drawable might have more than one parent osg::Node* node = *it; if (node->getNumParents()) node->getParent(0)->removeChild(node); @@ -107,7 +116,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) else cellnode = found->second; - osg::ref_ptr insert (new osg::PositionAttitudeTransform); + osg::ref_ptr insert (new SceneUtil::PositionAttitudeTransform); cellnode->addChild(insert); insert->getOrCreateUserDataContainer()->addUserObject(new PtrHolder(ptr)); @@ -116,6 +125,11 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) insert->setPosition(osg::Vec3(f[0], f[1], f[2])); + const float scale = ptr.getCellRef().getScale(); + osg::Vec3f scaleVec(scale, scale, scale); + ptr.getClass().adjustScale(ptr, scaleVec, true); + insert->setScale(scaleVec); + ptr.getRefData().setBaseNode(insert); } @@ -249,4 +263,13 @@ Animation* Objects::getAnimation(const MWWorld::Ptr &ptr) return NULL; } +const Animation* Objects::getAnimation(const MWWorld::ConstPtr &ptr) const +{ + PtrAnimationMap::const_iterator iter = mObjects.find(ptr); + if(iter != mObjects.end()) + return iter->second; + + return NULL; +} + } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 5c7ea32f49..3d0c92cb43 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -57,7 +57,7 @@ public: }; class Objects{ - typedef std::map PtrAnimationMap; + typedef std::map PtrAnimationMap; typedef std::map > CellMap; CellMap mCellSceneNodes; @@ -81,6 +81,7 @@ public: void insertCreature (const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); Animation* getAnimation(const MWWorld::Ptr &ptr); + const Animation* getAnimation(const MWWorld::ConstPtr &ptr) const; bool removeObject (const MWWorld::Ptr& ptr); ///< \return found? diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index cae6541af1..ee9d93c0f2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -26,11 +25,15 @@ #include #include #include +#include #include #include +#include "../mwworld/fallback.hpp" +#include "../mwworld/cellstore.hpp" + #include "sky.hpp" #include "effectmanager.hpp" #include "npcanimation.hpp" @@ -122,14 +125,24 @@ namespace MWRender bool mWireframe; }; - RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback) + RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, + const MWWorld::Fallback* fallback, const std::string& resourcePath) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mFogDepth(0.f) + , mUnderwaterColor(fallback->getFallbackColour("Water_UnderwaterColor")) + , mUnderwaterWeight(fallback->getFallbackFloat("Water_UnderwaterColorWeight")) + , mUnderwaterFog(0.f) + , mUnderwaterIndoorFog(fallback->getFallbackFloat("Water_UnderwaterIndoorFog")) , mNightEyeFactor(0.f) + , mFieldOfViewOverride(0.f) + , mFieldOfViewOverridden(false) { + resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); + osg::ref_ptr lightRoot = new SceneUtil::LightManager; + lightRoot->setLightingMask(Mask_Lighting); mLightRoot = lightRoot; lightRoot->setStartLight(1); @@ -145,7 +158,7 @@ namespace MWRender mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); - mWater.reset(new Water(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback)); + mWater.reset(new Water(mRootNode, lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain)); @@ -155,7 +168,7 @@ namespace MWRender mViewer->setLightingMode(osgViewer::View::NO_LIGHT); osg::ref_ptr source = new osg::LightSource; - source->setNodeMask(SceneUtil::Mask_Lit); + source->setNodeMask(Mask_Lighting); mSunLight = new osg::Light; source->setLight(mSunLight); mSunLight->setDiffuse(osg::Vec4f(0,0,0,1)); @@ -190,13 +203,17 @@ namespace MWRender mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); - mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor)); + mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - mFieldOfView = Settings::Manager::getFloat("field of view", "General"); + mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); + mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera"); updateProjectionMatrix(); mStateUpdater->setFogEnd(mViewDistance); + + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); } RenderingManager::~RenderingManager() @@ -213,6 +230,11 @@ namespace MWRender return mResourceSystem; } + osg::Group* RenderingManager::getLightRoot() + { + return mLightRoot.get(); + } + void RenderingManager::setNightEyeFactor(float factor) { if (factor != mNightEyeFactor) @@ -260,6 +282,7 @@ namespace MWRender { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(colour); + mSunLight->setSpecular(colour); } void RenderingManager::setSunDirection(const osg::Vec3f &direction) @@ -344,13 +367,14 @@ namespace MWRender { osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); - configureFog (cell->mAmbi.mFogDensity, color); + configureFog (cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, color); } - void RenderingManager::configureFog(float fogDepth, const osg::Vec4f &color) + void RenderingManager::configureFog(float fogDepth, float underwaterFog, const osg::Vec4f &color) { mFogDepth = fogDepth; mFogColor = color; + mUnderwaterFog = underwaterFog; } SkyManager* RenderingManager::getSkyManager() @@ -371,11 +395,12 @@ namespace MWRender osg::Vec3f focal, cameraPos; mCamera->getPosition(focal, cameraPos); + mCurrentCameraPos = cameraPos; if (mWater->isUnderwater(cameraPos)) { - setFogColor(osg::Vec4f(0.090195f, 0.115685f, 0.12745f, 1.f)); - mStateUpdater->setFogStart(0.f); - mStateUpdater->setFogEnd(1000); + setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); + mStateUpdater->setFogStart(mViewDistance * (1 - mUnderwaterFog)); + mStateUpdater->setFogEnd(mViewDistance); } else { @@ -426,6 +451,9 @@ namespace MWRender void RenderingManager::scaleObject(const MWWorld::Ptr &ptr, const osg::Vec3f &scale) { ptr.getRefData().getBaseNode()->setScale(scale); + + if (ptr == mCamera->getTrackingPtr()) // update height of camera + mCamera->processViewChange(); } void RenderingManager::removeObject(const MWWorld::Ptr &ptr) @@ -437,11 +465,13 @@ namespace MWRender void RenderingManager::setWaterEnabled(bool enabled) { mWater->setEnabled(enabled); + mSky->setWaterEnabled(enabled); } void RenderingManager::setWaterHeight(float height) { mWater->setHeight(height); + mSky->setWaterHeight(height); } class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback @@ -474,6 +504,15 @@ namespace MWRender mutable bool mDone; }; + + class NoTraverseCallback : public osg::NodeCallback + { + public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + } + }; + void RenderingManager::screenshot(osg::Image *image, int w, int h) { osg::ref_ptr rttCamera (new osg::Camera); @@ -482,12 +521,9 @@ namespace MWRender rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - rttCamera->setClearColor(mViewer->getCamera()->getClearColor()); - rttCamera->setClearMask(mViewer->getCamera()->getClearMask()); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix()); rttCamera->setViewport(0, 0, w, h); - rttCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); osg::ref_ptr texture (new osg::Texture2D); texture->setInternalFormat(GL_RGB); @@ -500,6 +536,7 @@ namespace MWRender image->setDataType(GL_UNSIGNED_BYTE); image->setPixelFormat(texture->getInternalFormat()); + rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mLightRoot); rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); @@ -509,10 +546,18 @@ namespace MWRender osg::ref_ptr callback (new NotifyDrawCompletedCallback); rttCamera->setFinalDrawCallback(callback); - mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); callback->waitTillDone(); + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + rttCamera->removeChildren(0, rttCamera->getNumChildren()); rttCamera->setGraphicsContext(NULL); mRootNode->removeChild(rttCamera); @@ -586,23 +631,27 @@ namespace MWRender } - RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + osg::ref_ptr createIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) { - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, - origin, dest)); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - - osgUtil::IntersectionVisitor intersectionVisitor(intersector); - int mask = intersectionVisitor.getTraversalMask(); - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water); + osg::ref_ptr intersectionVisitor( new osgUtil::IntersectionVisitor(intersector)); + int mask = intersectionVisitor->getTraversalMask(); + mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) mask &= ~(Mask_Actor|Mask_Player); - intersectionVisitor.setTraversalMask(mask); + intersectionVisitor->setTraversalMask(mask); + return intersectionVisitor; + } + + RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + { + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, + origin, dest)); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - mRootNode->accept(intersectionVisitor); + mRootNode->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); return getIntersectionResult(intersector); } @@ -621,17 +670,7 @@ namespace MWRender intersector->setEnd(end); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - osgUtil::IntersectionVisitor intersectionVisitor(intersector); - int mask = intersectionVisitor.getTraversalMask(); - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water); - if (ignorePlayer) - mask &= ~(Mask_Player); - if (ignoreActors) - mask &= ~(Mask_Actor|Mask_Player); - - intersectionVisitor.setTraversalMask(mask); - - mViewer->getCamera()->accept(intersectionVisitor); + mViewer->getCamera()->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); return getIntersectionResult(intersector); } @@ -667,16 +706,19 @@ namespace MWRender return mObjects->getAnimation(ptr); } - MWRender::Animation* RenderingManager::getPlayerAnimation() + const MWRender::Animation* RenderingManager::getAnimation(const MWWorld::ConstPtr &ptr) const { - return mPlayerAnimation.get(); + if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) + return mPlayerAnimation.get(); + + return mObjects->getAnimation(ptr); } void RenderingManager::setupPlayer(const MWWorld::Ptr &player) { if (!mPlayerNode) { - mPlayerNode = new osg::PositionAttitudeTransform; + mPlayerNode = new SceneUtil::PositionAttitudeTransform; mPlayerNode->setNodeMask(Mask_Player); mLightRoot->addChild(mPlayerNode); } @@ -691,7 +733,8 @@ namespace MWRender void RenderingManager::renderPlayer(const MWWorld::Ptr &player) { - mPlayerAnimation.reset(new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0)); + mPlayerAnimation.reset(new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, false, NpcAnimation::VM_Normal, + mFirstPersonFieldOfView)); mCamera->setAnimation(mPlayerAnimation.get()); mCamera->attachTo(player); @@ -725,25 +768,29 @@ namespace MWRender mWater->removeEmitter(ptr); } + void RenderingManager::emitWaterRipple(const osg::Vec3f &pos) + { + mWater->emitRipple(pos); + } + void RenderingManager::updateProjectionMatrix() { double aspect = mViewer->getCamera()->getViewport()->aspectRatio(); - mViewer->getCamera()->setProjectionMatrixAsPerspective(mFieldOfView, aspect, mNearClip, mViewDistance); + float fov = mFieldOfView; + if (mFieldOfViewOverridden) + fov = mFieldOfViewOverride; + mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); } void RenderingManager::updateTextureFiltering() { - osg::Texture::FilterMode min = osg::Texture::LINEAR_MIPMAP_NEAREST; - osg::Texture::FilterMode mag = osg::Texture::LINEAR; - - if (Settings::Manager::getString("texture filtering", "General") == "trilinear") - min = osg::Texture::LINEAR_MIPMAP_LINEAR; - - int maxAnisotropy = Settings::Manager::getInt("anisotropy", "General"); - - mViewer->stopThreading(); - mResourceSystem->getTextureManager()->setFilterSettings(min, mag, maxAnisotropy); - mViewer->startThreading(); + mResourceSystem->getTextureManager()->setFilterSettings( + Settings::Manager::getString("texture mag filter", "General"), + Settings::Manager::getString("texture min filter", "General"), + Settings::Manager::getString("texture mipmap", "General"), + Settings::Manager::getInt("anisotropy", "General"), + mViewer + ); } void RenderingManager::updateAmbient() @@ -767,9 +814,9 @@ namespace MWRender { for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { - if (it->first == "General" && it->second == "field of view") + if (it->first == "Camera" && it->second == "field of view") { - mFieldOfView = Settings::Manager::getFloat("field of view", "General"); + mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); updateProjectionMatrix(); } else if (it->first == "Camera" && it->second == "viewing distance") @@ -778,8 +825,12 @@ namespace MWRender mStateUpdater->setFogEnd(mViewDistance); updateProjectionMatrix(); } - else if (it->first == "General" && (it->second == "texture filtering" || it->second == "anisotropy")) + else if (it->first == "General" && (it->second == "texture filter" || + it->second == "texture mipmap" || + it->second == "anisotropy")) updateTextureFiltering(); + else if (it->first == "Water") + mWater->processChangedSettings(changed); } } @@ -833,6 +884,11 @@ namespace MWRender return mCamera.get(); } + const osg::Vec3f &RenderingManager::getCameraPosition() const + { + return mCurrentCameraPos; + } + void RenderingManager::togglePOV() { mCamera->toggleViewMode(); @@ -864,4 +920,23 @@ namespace MWRender mCamera->setCameraDistance(-factor/120.f*10, true, true); } + void RenderingManager::overrideFieldOfView(float val) + { + if (mFieldOfViewOverridden != true || mFieldOfViewOverride != val) + { + mFieldOfViewOverridden = true; + mFieldOfViewOverride = val; + updateProjectionMatrix(); + } + } + + void RenderingManager::resetFieldOfView() + { + if (mFieldOfViewOverridden == true) + { + mFieldOfViewOverridden = false; + updateProjectionMatrix(); + } + } + } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index def3ea4bba..58012078c4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -57,13 +57,16 @@ namespace MWRender class RenderingManager : public MWRender::RenderingInterface { public: - RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback); + RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, + const MWWorld::Fallback* fallback, const std::string& resourcePath); ~RenderingManager(); MWRender::Objects& getObjects(); Resource::ResourceSystem* getResourceSystem(); + osg::Group* getLightRoot(); + void setNightEyeFactor(float factor); void setAmbientColour(const osg::Vec4f& colour); @@ -78,7 +81,7 @@ namespace MWRender void configureAmbient(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell); - void configureFog(float fogDepth, const osg::Vec4f& colour); + void configureFog(float fogDepth, float underwaterFog, const osg::Vec4f& colour); void addCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store); @@ -133,10 +136,11 @@ namespace MWRender void update(float dt, bool paused); Animation* getAnimation(const MWWorld::Ptr& ptr); - Animation* getPlayerAnimation(); + const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; void addWaterRippleEmitter(const MWWorld::Ptr& ptr); void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); + void emitWaterRipple(const osg::Vec3f& pos); void updatePlayerPtr(const MWWorld::Ptr &ptr); @@ -158,6 +162,7 @@ namespace MWRender void resetCamera(); float getCameraDistance() const; Camera* getCamera(); + const osg::Vec3f& getCameraPosition() const; void togglePOV(); void togglePreviewMode(bool enable); bool toggleVanityMode(bool enable); @@ -165,6 +170,11 @@ namespace MWRender void togglePlayerLooking(bool enable); void changeVanityModeScale(float factor); + /// temporarily override the field of view with given value. + void overrideFieldOfView(float val); + /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. + void resetFieldOfView(); + private: void updateProjectionMatrix(); void updateTextureFiltering(); @@ -185,12 +195,17 @@ namespace MWRender std::auto_ptr mSky; std::auto_ptr mEffectManager; std::auto_ptr mPlayerAnimation; - osg::ref_ptr mPlayerNode; + osg::ref_ptr mPlayerNode; std::auto_ptr mCamera; + osg::Vec3f mCurrentCameraPos; osg::ref_ptr mStateUpdater; float mFogDepth; + osg::Vec4f mUnderwaterColor; + float mUnderwaterWeight; + float mUnderwaterFog; + float mUnderwaterIndoorFog; osg::Vec4f mFogColor; osg::Vec4f mAmbientColor; @@ -198,7 +213,10 @@ namespace MWRender float mNearClip; float mViewDistance; + float mFieldOfViewOverride; + bool mFieldOfViewOverridden; float mFieldOfView; + float mFirstPersonFieldOfView; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index a7637f2e1a..f232ea475d 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -55,6 +56,11 @@ namespace depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + osg::ref_ptr polygonOffset (new osg::PolygonOffset); + polygonOffset->setUnits(-1); + polygonOffset->setFactor(-1); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); osg::ref_ptr mat (new osg::Material); @@ -121,7 +127,6 @@ void RippleSimulation::update(float dt) } osg::Vec3f currentPos (it->mPtr.getRefData().getPosition().asVec3()); - currentPos.z() = 0; // Z is set by the Scene Node if ( (currentPos - it->mLastEmitPosition).length() > 10 // Only emit when close to the water surface, not above it and not too deep in the water @@ -130,18 +135,18 @@ void RippleSimulation::update(float dt) { it->mLastEmitPosition = currentPos; + currentPos.z() = mParticleNode->getPosition().z(); + if (mParticleSystem->numParticles()-mParticleSystem->numDeadParticles() > 500) continue; // TODO: remove the oldest particle to make room? - osgParticle::Particle* p = mParticleSystem->createParticle(NULL); - p->setPosition(currentPos); - p->setAngle(osg::Vec3f(0,0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); + emitRipple(currentPos); } } } -void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) +void RippleSimulation::addEmitter(const MWWorld::ConstPtr& ptr, float scale, float force) { Emitter newEmitter; newEmitter.mPtr = ptr; @@ -151,7 +156,7 @@ void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float fo mEmitters.push_back (newEmitter); } -void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr) +void RippleSimulation::removeEmitter (const MWWorld::ConstPtr& ptr) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { @@ -163,7 +168,7 @@ void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr) } } -void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) +void RippleSimulation::updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { @@ -179,7 +184,7 @@ void RippleSimulation::removeCell(const MWWorld::CellStore *store) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) { - if (it->mPtr.getCell() == store && it->mPtr != MWMechanics::getPlayer()) + if ((it->mPtr.isInCell() && it->mPtr.getCell() == store) && it->mPtr != MWMechanics::getPlayer()) { it = mEmitters.erase(it); } @@ -188,6 +193,16 @@ void RippleSimulation::removeCell(const MWWorld::CellStore *store) } } +void RippleSimulation::emitRipple(const osg::Vec3f &pos) +{ + if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) + { + osgParticle::Particle* p = mParticleSystem->createParticle(NULL); + p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); + p->setAngle(osg::Vec3f(0,0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); + } +} + void RippleSimulation::setWaterHeight(float height) { mParticleNode->setPosition(osg::Vec3f(0,0,height)); diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 1717cca573..a4e12f2756 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -8,6 +8,7 @@ namespace osg { class Group; + class PositionAttitudeTransform; } namespace osgParticle @@ -30,7 +31,7 @@ namespace MWRender struct Emitter { - MWWorld::Ptr mPtr; + MWWorld::ConstPtr mPtr; osg::Vec3f mLastEmitPosition; float mScale; float mForce; @@ -46,11 +47,13 @@ namespace MWRender void update(float dt); /// adds an emitter, position will be tracked automatically - void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); - void removeEmitter (const MWWorld::Ptr& ptr); - void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void addEmitter (const MWWorld::ConstPtr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter (const MWWorld::ConstPtr& ptr); + void updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr); void removeCell(const MWWorld::CellStore* store); + void emitRipple(const osg::Vec3f& pos); + /// Change the height of the water surface, thus moving all ripples with it void setWaterHeight(float height); diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 8c3758cb00..456a6dd200 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -27,7 +27,7 @@ protected: bool mEnabled; osg::Quat mRotate; - osg::ref_ptr mRelativeTo; + osg::Node* mRelativeTo; }; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8de8a61fc1..20e3dc07c7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -35,6 +37,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -250,6 +253,8 @@ public: // That's not a problem though, children of this node can be culled just fine // Just make sure you do not place a CameraRelativeTransform deep in the scene graph setCullingActive(false); + + addCullCallback(new CullCallback); } CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) @@ -259,8 +264,18 @@ public: META_Node(MWRender, CameraRelativeTransform) - virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const + const osg::Vec3f& getLastEyePoint() const { + return mEyePoint; + } + + virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const + { + if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + mEyePoint = static_cast(nv)->getEyePoint(); + } + if (_referenceFrame==RELATIVE_RF) { matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); @@ -277,6 +292,51 @@ public: { return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); } + + class CullCallback : public osg::NodeCallback + { + public: + virtual void operator() (osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + // XXX have to remove unwanted culling plane of the water reflection camera + + // Remove all planes that aren't from the standard frustum + unsigned int numPlanes = 4; + if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) + ++numPlanes; + if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) + ++numPlanes; + + int mask = 0x1; + int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); + for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + { + if (i >= numPlanes) + { + // turn off this culling plane + resultMask &= (~mask); + } + + mask <<= 1; + } + + cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); + cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); + + cv->getProjectionCullingStack().back().pushCurrentMask(); + cv->getCurrentCullingSet().pushCurrentMask(); + + traverse(node, nv); + + cv->getProjectionCullingStack().back().popCurrentMask(); + cv->getCurrentCullingSet().popCurrentMask(); + } + }; +private: + // eyePoint for the current frame + mutable osg::Vec3f mEyePoint; }; class ModVertexAlphaVisitor : public osg::NodeVisitor @@ -293,39 +353,81 @@ public: for (unsigned int i=0; iasGeometry(); - if (!geom) - continue; - - osg::ref_ptr colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); - for (unsigned int i=0; isize(); ++i) + osg::ref_ptr colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); + for (unsigned int i=0; isize(); ++i) + { + float alpha = 1.f; + if (mMeshType == 0) alpha = i%2 ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row + else if (mMeshType == 1) { - float alpha = 1.f; - if (mMeshType == 0) alpha = i%2 ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row - else if (mMeshType == 1) - { - if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row - else if (i>= 33 && i <= 48) alpha = 0.25098; // second row - else alpha = 1.f; - } - else if (mMeshType == 2) - { - osg::Vec4Array* origColors = static_cast(geom->getColorArray()); - alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; - } - - (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); + if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row + else if (i>= 33 && i <= 48) alpha = 0.25098; // second row + else alpha = 1.f; + } + else if (mMeshType == 2) + { + osg::Vec4Array* origColors = static_cast(geom->getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; } - geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); } + + geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); } private: int mMeshType; }; +/// @brief Hides the node subgraph if the eye point is below water. +/// @note Must be added as cull callback. +/// @note Meant to be used on a node that is child of a CameraRelativeTransform. +/// The current eye point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. +class UnderwaterSwitchCallback : public osg::NodeCallback +{ +public: + UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) + : mCameraRelativeTransform(cameraRelativeTransform) + , mEnabled(true) + , mWaterLevel(0.f) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::Vec3f eyePoint = mCameraRelativeTransform->getLastEyePoint(); + + if (mEnabled && eyePoint.z() < mWaterLevel) + return; + + traverse(node, nv); + } + + void setEnabled(bool enabled) + { + mEnabled = enabled; + } + void setWaterLevel(float waterLevel) + { + mWaterLevel = waterLevel; + } + +private: + osg::ref_ptr mCameraRelativeTransform; + bool mEnabled; + float mWaterLevel; +}; + /// A base class for the sun and moons. class CelestialBody { @@ -367,6 +469,7 @@ public: , mUpdater(new Updater) { mTransform->addUpdateCallback(mUpdater); + mTransform->setNodeMask(Mask_Sun); osg::ref_ptr sunTex = textureManager.getTexture2D("textures/tx_sun_05.dds", osg::Texture::CLAMP, @@ -1014,20 +1117,27 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mSunEnabled(true) { osg::ref_ptr skyroot (new CameraRelativeTransform); + skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); mRootNode = skyroot; - // By default render before the world is rendered - mRootNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); + mEarlyRenderBinRoot = new osg::Group; + // render before the world is rendered + mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); + // Prevent unwanted clipping by water reflection camera's clipping plane + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); + mRootNode->addChild(mEarlyRenderBinRoot); + + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } void SkyManager::create() { assert(!mCreated); - mAtmosphereDay = mSceneManager->createInstance("meshes/sky_atmosphere.nif", mRootNode); + mAtmosphereDay = mSceneManager->createInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(0); mAtmosphereDay->accept(modAtmosphere); @@ -1036,7 +1146,7 @@ void SkyManager::create() mAtmosphereNightNode = new osg::PositionAttitudeTransform; mAtmosphereNightNode->setNodeMask(0); - mRootNode->addChild(mAtmosphereNightNode); + mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); osg::ref_ptr atmosphereNight; if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) @@ -1049,14 +1159,14 @@ void SkyManager::create() mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getTextureManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - mSun.reset(new Sun(mRootNode, *mSceneManager->getTextureManager())); + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getTextureManager())); const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); - mMasser.reset(new Moon(mRootNode, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mRootNode, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); + mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); mCloudNode = new osg::PositionAttitudeTransform; - mRootNode->addChild(mCloudNode); + mEarlyRenderBinRoot->addChild(mCloudNode); mCloudMesh = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); ModVertexAlphaVisitor modClouds(1); mCloudMesh->accept(modClouds); @@ -1073,9 +1183,9 @@ void SkyManager::create() osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(false); - mRootNode->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - mRootNode->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); - mRootNode->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); mMoonScriptColor = fallback->getFallbackColour("Moons_Script_Color"); @@ -1147,14 +1257,13 @@ public: mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); } - // Helper for adding AlphaFader to a subgraph + // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: SetupVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { - mAlphaFader = new AlphaFader; } virtual void apply(osg::Node &node) @@ -1164,7 +1273,7 @@ public: if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) { SceneUtil::CompositeStateSetUpdater* composite = NULL; -#if OSG_MIN_VERSION_REQUIRED(3,3,3) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); #else osg::NodeCallback* callback = node.getUpdateCallback(); @@ -1176,22 +1285,26 @@ public: callback = callback->getNestedCallback(); } + osg::ref_ptr alphaFader (new AlphaFader); + if (composite) - composite->addController(mAlphaFader); + composite->addController(alphaFader); else - node.addUpdateCallback(mAlphaFader); + node.addUpdateCallback(alphaFader); + + mAlphaFaders.push_back(alphaFader); } } traverse(node); } - osg::ref_ptr getAlphaFader() + std::vector > getAlphaFaders() { - return mAlphaFader; + return mAlphaFaders; } private: - osg::ref_ptr mAlphaFader; + std::vector > mAlphaFaders; }; private: @@ -1229,6 +1342,7 @@ void SkyManager::createRain() stateset->setNestRenderBins(false); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); @@ -1264,6 +1378,8 @@ void SkyManager::createRain() mRainFader = new RainFader; mRainNode->addUpdateCallback(mRainFader); + mRainNode->addCullCallback(mUnderwaterSwitch); + mRainNode->setNodeMask(Mask_WeatherParticles); mRootNode->addChild(mRainNode); } @@ -1383,6 +1499,14 @@ void SkyManager::setWeather(const WeatherResult& weather) { mCurrentParticleEffect = weather.mParticleEffect; + // cleanup old particles + if (mParticleEffect) + { + mParticleNode->removeChild(mParticleEffect); + mParticleEffect = NULL; + mParticleFaders.clear(); + } + if (mCurrentParticleEffect.empty()) { if (mParticleNode) @@ -1390,14 +1514,14 @@ void SkyManager::setWeather(const WeatherResult& weather) mRootNode->removeChild(mParticleNode); mParticleNode = NULL; } - mParticleEffect = NULL; - mParticleFader = NULL; } else { if (!mParticleNode) { mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addCullCallback(mUnderwaterSwitch); + mParticleNode->setNodeMask(Mask_WeatherParticles); mRootNode->addChild(mParticleNode); } mParticleEffect = mSceneManager->createInstance(mCurrentParticleEffect, mParticleNode); @@ -1407,7 +1531,10 @@ void SkyManager::setWeather(const WeatherResult& weather) AlphaFader::SetupVisitor alphaFaderSetupVisitor; mParticleEffect->accept(alphaFaderSetupVisitor); - mParticleFader = alphaFaderSetupVisitor.getAlphaFader(); + mParticleFaders = alphaFaderSetupVisitor.getAlphaFaders(); + + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; + mParticleEffect->accept(disableFreezeOnCullVisitor); } } @@ -1425,11 +1552,13 @@ void SkyManager::setWeather(const WeatherResult& weather) { mNextClouds = weather.mNextCloudTexture; - std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + if (!mNextClouds.empty()) + { + std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); - if (!texture.empty()) mCloudUpdater2->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, osg::Texture::REPEAT, osg::Texture::REPEAT)); + } } if (mCloudBlendFactor != weather.mCloudBlendFactor) @@ -1441,17 +1570,15 @@ void SkyManager::setWeather(const WeatherResult& weather) mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0 : 0); } - if (mCloudColour != weather.mSunColor) + if (mCloudColour != weather.mFogColor) { - // FIXME: this doesn't look correct - osg::Vec4f clr( weather.mSunColor.r()*0.7f + weather.mAmbientColor.r()*0.7f, - weather.mSunColor.g()*0.7f + weather.mAmbientColor.g()*0.7f, - weather.mSunColor.b()*0.7f + weather.mAmbientColor.b()*0.7f, 1.f); + osg::Vec4f clr (weather.mFogColor); + clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); mCloudUpdater->setEmissionColor(clr); mCloudUpdater2->setEmissionColor(clr); - mCloudColour = weather.mSunColor; + mCloudColour = weather.mFogColor; } if (mSkyColour != weather.mSkyColor) @@ -1488,8 +1615,8 @@ void SkyManager::setWeather(const WeatherResult& weather) if (mRainFader) mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? - if (mParticleFader) - mParticleFader->setAlpha(weather.mEffectFade); + for (std::vector >::const_iterator it = mParticleFaders.begin(); it != mParticleFaders.end(); ++it) + (*it)->setAlpha(weather.mEffectFade); } void SkyManager::sunEnable() @@ -1543,4 +1670,14 @@ void SkyManager::setGlareTimeOfDayFade(float val) mSun->setGlareTimeOfDayFade(val); } +void SkyManager::setWaterHeight(float height) +{ + mUnderwaterSwitch->setWaterLevel(height); +} + +void SkyManager::setWaterEnabled(bool enabled) +{ + mUnderwaterSwitch->setEnabled(enabled); +} + } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 072083d270..0caadaa073 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -35,6 +35,7 @@ namespace MWRender class RainShooter; class RainFader; class AlphaFader; + class UnderwaterSwitchCallback; struct WeatherResult { @@ -100,6 +101,8 @@ namespace MWRender float mMoonAlpha; }; + ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered + /// relative to the camera (e.g. weather particle effects) class SkyManager { public: @@ -144,6 +147,12 @@ namespace MWRender void setGlareTimeOfDayFade(float val); + /// Enable or disable the water plane (used to remove underwater weather particles) + void setWaterEnabled(bool enabled); + + /// Set height of water plane (used to remove underwater weather particles) + void setWaterHeight(float height); + private: void create(); ///< no need to call this, automatically done on first enable() @@ -155,10 +164,12 @@ namespace MWRender Resource::SceneManager* mSceneManager; osg::ref_ptr mRootNode; + osg::ref_ptr mEarlyRenderBinRoot; osg::ref_ptr mParticleNode; osg::ref_ptr mParticleEffect; - osg::ref_ptr mParticleFader; + std::vector > mParticleFaders; + osg::ref_ptr mUnderwaterSwitch; osg::ref_ptr mCloudNode; diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index f9a9083f01..ed1d8b6779 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -20,7 +20,7 @@ namespace MWRender MWWorld::Store::iterator it = esmStore.get().begin(); for (; it != esmStore.get().end(); ++it) { - ESM::Land* land = const_cast(&*it); // TODO: fix store interface + const ESM::Land* land = &*it; land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX); } } @@ -69,7 +69,7 @@ namespace MWRender { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - return esmStore.get().find(index, plugin); + return esmStore.get().search(index, plugin); } } diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index 38fcfe6487..dd6e85e2ca 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -15,20 +15,29 @@ namespace MWRender Mask_Actor = (1<<3), Mask_Player = (1<<4), Mask_Sky = (1<<5), - Mask_Water = (1<<6), - Mask_Terrain = (1<<7), + Mask_Water = (1<<6), // choose Water or SimpleWater depending on detail required + Mask_SimpleWater = (1<<7), + Mask_Terrain = (1<<8), + Mask_FirstPerson = (1<<9), + + // child of Sky + Mask_Sun = (1<<10), + Mask_WeatherParticles = (1<<11), + + // child of Water // top level masks - Mask_Scene = (1<<8), - Mask_GUI = (1<<9), + Mask_Scene = (1<<12), + Mask_GUI = (1<<13), // Set on a Geode - Mask_ParticleSystem = (1<<10), + Mask_ParticleSystem = (1<<14), // Set on cameras within the main scene graph - Mask_RenderToTexture = (1<<11) + Mask_RenderToTexture = (1<<15), - // reserved: (1<<16) for SceneUtil::Mask_Lit + // Set on a camera's cull mask to enable the LightManager + Mask_Lighting = (1<<16) }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 03ab58e6be..cd1f4c5113 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -2,14 +2,27 @@ #include +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include +#include + +#include + +#include +#include #include +#include #include #include @@ -17,8 +30,13 @@ #include #include +#include + #include +#include "../mwworld/cellstore.hpp" +#include "../mwworld/fallback.hpp" + #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" @@ -65,55 +83,386 @@ namespace waterGeom->setVertexArray(verts); waterGeom->setTexCoordArray(0, texcoords); + osg::ref_ptr normal (new osg::Vec3Array); + normal->push_back(osg::Vec3f(0,0,1)); + waterGeom->setNormalArray(normal, osg::Array::BIND_OVERALL); + waterGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,verts->size())); return waterGeom; } - void createWaterStateSet(Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) +} + +namespace MWRender +{ + +// -------------------------------------------------------------------------------------------------------------------------------- + +/// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. +/// Also handles flipping of the plane when the eye point goes below it. +/// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); +class ClipCullNode : public osg::Group +{ + class PlaneCullCallback : public osg::NodeCallback { - osg::ref_ptr stateset (new osg::StateSet); + public: + /// @param cullPlane The culling plane (in world space). + PlaneCullCallback(const osg::Plane* cullPlane) + : osg::NodeCallback() + , mCullPlane(cullPlane) + { + } - osg::ref_ptr material (new osg::Material); - material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.7f)); - material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - material->setColorMode(osg::Material::OFF); - stateset->setAttributeAndModes(material, osg::StateAttribute::ON); + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); - osg::ref_ptr depth (new osg::Depth); - depth->setWriteMask(false); - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + osg::Plane plane = *mCullPlane; + plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); + + osg::Vec3d eyePoint = cv->getEyePoint(); + if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) + plane.flip(); + + cv->getProjectionCullingStack().back().getFrustum().add(plane); + + traverse(node, nv); + + // undo + cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); + } + + private: + const osg::Plane* mCullPlane; + }; - stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); + class FlipCallback : public osg::NodeCallback + { + public: + FlipCallback(const osg::Plane* cullPlane) + : mCullPlane(cullPlane) + { + } - std::vector > textures; - for (int i=0; i<32; ++i) + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { - std::ostringstream texname; - texname << "textures/water/water" << std::setw(2) << std::setfill('0') << i << ".dds"; - textures.push_back(resourceSystem->getTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + osgUtil::CullVisitor* cv = static_cast(nv); + osg::Vec3d eyePoint = cv->getEyePoint(); + + osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + + // apply the height of the plane + // we can't apply this height in the addClipPlane() since the "flip the below graph" function would otherwise flip the height as well + modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * ((*mCullPlane)[3] * -1)); + + // flip the below graph if the eye point is above the plane + if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) + { + modelViewMatrix->preMultScale(osg::Vec3(1,1,-1)); + } + + // move the plane back along its normal a little bit to prevent bleeding at the water shore + const float clipFudge = -5; + modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); + + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); + traverse(node, nv); + cv->popModelViewMatrix(); } - osg::ref_ptr controller (new NifOsg::FlipController(0, 2/32.f, textures)); - controller->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); - node->addUpdateCallback(controller); - node->setStateSet(stateset); - stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); + private: + const osg::Plane* mCullPlane; + }; + +public: + ClipCullNode() + { + addCullCallback (new PlaneCullCallback(&mPlane)); + + mClipNodeTransform = new osg::Group; + mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); + addChild(mClipNodeTransform); + + mClipNode = new osg::ClipNode; + + mClipNodeTransform->addChild(mClipNode); } + void setPlane (const osg::Plane& plane) + { + if (plane == mPlane) + return; + mPlane = plane; + + mClipNode->getClipPlaneList().clear(); + mClipNode->addClipPlane(new osg::ClipPlane(0, osg::Plane(mPlane.getNormal(), 0))); // mPlane.d() applied in FlipCallback + mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); + mClipNode->setCullingActive(false); + } + +private: + osg::ref_ptr mClipNodeTransform; + osg::ref_ptr mClipNode; + + osg::Plane mPlane; +}; + +// Node callback to entirely skip the traversal. +class NoTraverseCallback : public osg::NodeCallback +{ +public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + // no traverse() + } +}; + +/// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. +/// The offset works around graphics artifacts that occured with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). +/// Must be added as a Cull callback. +class FudgeCallback : public osg::NodeCallback +{ +public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + const float fudge = 0.2; + if (std::abs(cv->getEyeLocal().z()) < fudge) + { + float diff = fudge - cv->getEyeLocal().z(); + osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + + if (cv->getEyeLocal().z() > 0) + modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,-diff)); + else + modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,diff)); + + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); + traverse(node, nv); + cv->popModelViewMatrix(); + } + else + traverse(node, nv); + } +}; + +osg::ref_ptr readShader (osg::Shader::Type type, const std::string& file, const std::map& defineMap = std::map()) +{ + osg::ref_ptr shader (new osg::Shader(type)); + + // use boost in favor of osg::Shader::readShaderFile, to handle utf-8 path issues on Windows + boost::filesystem::ifstream inStream; + inStream.open(boost::filesystem::path(file)); + std::stringstream strstream; + strstream << inStream.rdbuf(); + + std::string shaderSource = strstream.str(); + + for (std::map::const_iterator it = defineMap.begin(); it != defineMap.end(); ++it) + { + size_t pos = shaderSource.find(it->first); + if (pos != std::string::npos) + shaderSource.replace(pos, it->first.length(), it->second); + } + + shader->setShaderSource(shaderSource); + return shader; } -namespace MWRender +osg::ref_ptr readPngImage (const std::string& file) { + // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows + boost::filesystem::ifstream inStream; + inStream.open(file, std::ios_base::in | std::ios_base::binary); + if (inStream.fail()) + std::cerr << "Failed to open " << file << std::endl; + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!reader) + { + std::cerr << "Failed to read " << file << ", no png readerwriter found" << std::endl; + return osg::ref_ptr(); + } + osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); + if (!result.success()) + std::cerr << "Failed to read " << file << ": " << result.message() << " code " << result.status() << std::endl; -// -------------------------------------------------------------------------------------------------------------------------------- + return result.getImage(); +} + + +class Refraction : public osg::Camera +{ +public: + Refraction() + { + unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); + setRenderOrder(osg::Camera::PRE_RENDER); + setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + setReferenceFrame(osg::Camera::RELATIVE_RF); + + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setNodeMask(Mask_RenderToTexture); + setViewport(0, 0, rttSize, rttSize); + + // No need for Update traversal since the scene is already updated as part of the main scene graph + // A double update would mess with the light collection (in addition to being plain redundant) + setUpdateCallback(new NoTraverseCallback); + + // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog + getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + + mClipCullNode = new ClipCullNode; + addChild(mClipCullNode); + + mRefractionTexture = new osg::Texture2D; + mRefractionTexture->setTextureSize(rttSize, rttSize); + mRefractionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mRefractionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mRefractionTexture->setInternalFormat(GL_RGB); + mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); + + mRefractionDepthTexture = new osg::Texture2D; + mRefractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); + mRefractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); + mRefractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mRefractionDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); + mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); + } + + void setScene(osg::Node* scene) + { + if (mScene) + mClipCullNode->removeChild(mScene); + mScene = scene; + mClipCullNode->addChild(scene); + } + + void setWaterLevel(float waterLevel) + { + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel))); + } + + osg::Texture2D* getRefractionTexture() const + { + return mRefractionTexture.get(); + } + + osg::Texture2D* getRefractionDepthTexture() const + { + return mRefractionDepthTexture.get(); + } + +private: + osg::ref_ptr mClipCullNode; + osg::ref_ptr mRefractionTexture; + osg::ref_ptr mRefractionDepthTexture; + osg::ref_ptr mScene; +}; + +class Reflection : public osg::Camera +{ +public: + Reflection() + { + setRenderOrder(osg::Camera::PRE_RENDER); + setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + setReferenceFrame(osg::Camera::RELATIVE_RF); -Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, const MWWorld::Fallback* fallback) + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting); + setNodeMask(Mask_RenderToTexture); + + unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); + setViewport(0, 0, rttSize, rttSize); + + // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph + // A double update would mess with the light collection (in addition to being plain redundant) + setUpdateCallback(new NoTraverseCallback); + + mReflectionTexture = new osg::Texture2D; + mReflectionTexture->setInternalFormat(GL_RGB); + mReflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); + + // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. + osg::ref_ptr frontFace (new osg::FrontFace); + frontFace->setMode(osg::FrontFace::CLOCKWISE); + getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + + mClipCullNode = new ClipCullNode; + addChild(mClipCullNode); + } + + void setWaterLevel(float waterLevel) + { + setViewMatrix(osg::Matrix::translate(0,0,-waterLevel) * osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,waterLevel)); + + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,1), osg::Vec3d(0,0,waterLevel))); + } + + void setScene(osg::Node* scene) + { + if (mScene) + mClipCullNode->removeChild(mScene); + mScene = scene; + mClipCullNode->addChild(scene); + } + + osg::Texture2D* getReflectionTexture() const + { + return mReflectionTexture.get(); + } + +private: + osg::ref_ptr mReflectionTexture; + osg::ref_ptr mClipCullNode; + osg::ref_ptr mScene; +}; + +/// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. +class DepthClampCallback : public osg::Drawable::DrawCallback +{ +public: + virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const + { + static bool supported = osg::isGLExtensionOrVersionSupported(renderInfo.getState()->getContextID(), "GL_ARB_depth_clamp", 3.3); + if (!supported) + { + drawable->drawImplementation(renderInfo); + return; + } + + glEnable(GL_DEPTH_CLAMP); + + drawable->drawImplementation(renderInfo); + + // restore default + glDisable(GL_DEPTH_CLAMP); + } +}; + +Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, + const MWWorld::Fallback* fallback, const std::string& resourcePath) : mParent(parent) + , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) + , mFallback(fallback) + , mResourcePath(resourcePath) , mEnabled(true) , mToggled(true) , mTop(0) @@ -121,27 +470,184 @@ Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUt mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); osg::ref_ptr waterGeom = createWaterGeometry(CELL_SIZE*150, 40, 900); + waterGeom->setDrawCallback(new DepthClampCallback); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(waterGeom); - geode->setNodeMask(Mask_Water); + mWaterGeode = new osg::Geode; + mWaterGeode->addDrawable(waterGeom); + mWaterGeode->setNodeMask(Mask_Water); if (ico) - ico->add(geode); - - createWaterStateSet(mResourceSystem, geode); + ico->add(mWaterGeode); mWaterNode = new osg::PositionAttitudeTransform; - mWaterNode->addChild(geode); + mWaterNode->addChild(mWaterGeode); + mWaterNode->addCullCallback(new FudgeCallback); - mParent->addChild(mWaterNode); + // simple water fallback for the local map + osg::ref_ptr geode2 (osg::clone(mWaterGeode.get(), osg::CopyOp::DEEP_COPY_NODES)); + createSimpleWaterStateSet(geode2, mFallback->getFallbackFloat("Water_Map_Alpha")); + geode2->setNodeMask(Mask_SimpleWater); + mWaterNode->addChild(geode2); + + mSceneRoot->addChild(mWaterNode); setHeight(mTop); + + updateWaterMaterial(); +} + +void Water::updateWaterMaterial() +{ + if (mReflection) + { + mParent->removeChild(mReflection); + mReflection = NULL; + } + if (mRefraction) + { + mParent->removeChild(mRefraction); + mRefraction = NULL; + } + + if (Settings::Manager::getBool("shader", "Water")) + { + mReflection = new Reflection; + mReflection->setWaterLevel(mTop); + mReflection->setScene(mSceneRoot); + mParent->addChild(mReflection); + + if (Settings::Manager::getBool("refraction", "Water")) + { + mRefraction = new Refraction; + mRefraction->setWaterLevel(mTop); + mRefraction->setScene(mSceneRoot); + mParent->addChild(mRefraction); + } + + createShaderWaterStateSet(mWaterGeode, mReflection, mRefraction); + } + else + createSimpleWaterStateSet(mWaterGeode, mFallback->getFallbackFloat("Water_World_Alpha")); + + updateVisible(); +} + +void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) +{ + osg::ref_ptr stateset (new osg::StateSet); + + osg::ref_ptr material (new osg::Material); + material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, alpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + material->setColorMode(osg::Material::OFF); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON); + + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + + osg::ref_ptr depth (new osg::Depth); + depth->setWriteMask(false); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + + stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); + + node->setStateSet(stateset); + + std::vector > textures; + int frameCount = mFallback->getFallbackInt("Water_SurfaceFrameCount"); + std::string texture = mFallback->getFallbackString("Water_SurfaceTexture"); + for (int i=0; igetTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + } + + if (!textures.size()) + return; + + float fps = mFallback->getFallbackFloat("Water_SurfaceFPS"); + + osg::ref_ptr controller (new NifOsg::FlipController(0, 1.f/fps, textures)); + controller->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + node->setUpdateCallback(controller); + + stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); +} + +void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction) +{ + // use a define map to conditionally compile the shader + std::map defineMap; + defineMap.insert(std::make_pair(std::string("@refraction_enabled"), std::string(refraction ? "1" : "0"))); + + osg::ref_ptr vertexShader (readShader(osg::Shader::VERTEX, mResourcePath + "/shaders/water_vertex.glsl", defineMap)); + osg::ref_ptr fragmentShader (readShader(osg::Shader::FRAGMENT, mResourcePath + "/shaders/water_fragment.glsl", defineMap)); + + osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); + normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + normalMap->setMaxAnisotropy(16); + normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); + normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + normalMap->getImage()->flipVertical(); + + osg::ref_ptr shaderStateset = new osg::StateSet; + shaderStateset->addUniform(new osg::Uniform("normalMap", 0)); + shaderStateset->addUniform(new osg::Uniform("reflectionMap", 1)); + + shaderStateset->setTextureAttributeAndModes(0, normalMap, osg::StateAttribute::ON); + shaderStateset->setTextureAttributeAndModes(1, reflection->getReflectionTexture(), osg::StateAttribute::ON); + if (refraction) + { + shaderStateset->setTextureAttributeAndModes(2, refraction->getRefractionTexture(), osg::StateAttribute::ON); + shaderStateset->setTextureAttributeAndModes(3, refraction->getRefractionDepthTexture(), osg::StateAttribute::ON); + shaderStateset->addUniform(new osg::Uniform("refractionMap", 2)); + shaderStateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); + shaderStateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); + } + else + { + shaderStateset->setMode(GL_BLEND, osg::StateAttribute::ON); + + shaderStateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); + + osg::ref_ptr depth (new osg::Depth); + depth->setWriteMask(false); + shaderStateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + node->setStateSet(shaderStateset); + node->setUpdateCallback(NULL); +} + +void Water::processChangedSettings(const Settings::CategorySettingVector& settings) +{ + updateWaterMaterial(); } Water::~Water() { mParent->removeChild(mWaterNode); + + if (mReflection) + { + mParent->removeChild(mReflection); + mReflection = NULL; + } + if (mRefraction) + { + mParent->removeChild(mRefraction); + mRefraction = NULL; + } } void Water::setEnabled(bool enabled) @@ -156,6 +662,11 @@ void Water::changeCell(const MWWorld::CellStore* store) mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY)); else mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); + + // create a new StateSet to prevent threading issues + osg::ref_ptr nodeStateSet (new osg::StateSet); + nodeStateSet->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWaterNode->getPosition()))); + mWaterNode->setStateSet(nodeStateSet); } void Water::setHeight(const float height) @@ -167,6 +678,11 @@ void Water::setHeight(const float height) osg::Vec3f pos = mWaterNode->getPosition(); pos.z() = height; mWaterNode->setPosition(pos); + + if (mReflection) + mReflection->setWaterLevel(mTop); + if (mRefraction) + mRefraction->setWaterLevel(mTop); } void Water::update(float dt) @@ -176,7 +692,12 @@ void Water::update(float dt) void Water::updateVisible() { - mWaterNode->setNodeMask(mEnabled && mToggled ? ~0 : 0); + bool visible = mEnabled && mToggled; + mWaterNode->setNodeMask(visible ? ~0 : 0); + if (mRefraction) + mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0); + if (mReflection) + mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0); } bool Water::toggle() @@ -211,6 +732,11 @@ void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) mSimulation->updateEmitterPtr(old, ptr); } +void Water::emitRipple(const osg::Vec3f &pos) +{ + mSimulation->emitRipple(pos); +} + void Water::removeCell(const MWWorld::CellStore *store) { mSimulation->removeCell(store); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 519cd51819..b26782873f 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -1,14 +1,19 @@ #ifndef OPENMW_MWRENDER_WATER_H #define OPENMW_MWRENDER_WATER_H +#include + #include +#include -#include "../mwworld/cellstore.hpp" +#include namespace osg { class Group; class PositionAttitudeTransform; + class Geode; + class Node; } namespace osgUtil @@ -24,11 +29,15 @@ namespace Resource namespace MWWorld { class Fallback; + class CellStore; + class Ptr; } namespace MWRender { + class Refraction; + class Reflection; class RippleSimulation; /// Water rendering @@ -37,12 +46,20 @@ namespace MWRender static const int CELL_SIZE = 8192; osg::ref_ptr mParent; + osg::ref_ptr mSceneRoot; osg::ref_ptr mWaterNode; + osg::ref_ptr mWaterGeode; Resource::ResourceSystem* mResourceSystem; + const MWWorld::Fallback* mFallback; osg::ref_ptr mIncrementalCompileOperation; std::auto_ptr mSimulation; + osg::ref_ptr mRefraction; + osg::ref_ptr mReflection; + + const std::string mResourcePath; + bool mEnabled; bool mToggled; float mTop; @@ -50,8 +67,18 @@ namespace MWRender osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); + void createSimpleWaterStateSet(osg::Node* node, float alpha); + + /// @param reflection the reflection camera (required) + /// @param refraction the refraction camera (optional) + void createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction); + + void updateWaterMaterial(); + public: - Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const MWWorld::Fallback* fallback); + Water(osg::Group* parent, osg::Group* sceneRoot, + Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const MWWorld::Fallback* fallback, + const std::string& resourcePath); ~Water(); void setEnabled(bool enabled); @@ -64,6 +91,8 @@ namespace MWRender void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); void removeEmitter (const MWWorld::Ptr& ptr); void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void emitRipple(const osg::Vec3f& pos); + void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell void clearRipples(); @@ -73,6 +102,7 @@ namespace MWRender void update(float dt); + void processChangedSettings(const Settings::CategorySettingVector& settings); }; } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 78c84141a6..db60d14d18 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -1,5 +1,8 @@ #include "aiextensions.hpp" +#include +#include + #include #include @@ -8,6 +11,7 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/aiactivate.hpp" @@ -18,13 +22,11 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "interpretercontext.hpp" #include "ref.hpp" -#include - -#include "../mwbase/mechanicsmanager.hpp" namespace MWScript { @@ -144,6 +146,11 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetStore().get().find(cellID); + MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); @@ -424,7 +431,7 @@ namespace MWScript MWWorld::Ptr targetPtr; if (creatureStats.getAiSequence().getCombatTarget (targetPtr)) { - if (targetPtr.getCellRef().getRefId() == testedTargetId) + if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) targetsAreEqual = true; } runtime.push(int(targetsAreEqual)); diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 5dd3cf4119..513c1cc42e 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -123,7 +123,7 @@ namespace MWScript const MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); - Misc::StringUtils::toLower(current); + Misc::StringUtils::lowerCaseInPlace(current); bool match = current.length()>=name.length() && current.substr (0, name.length())==name; @@ -144,7 +144,9 @@ namespace MWScript return; } MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); - if (cell->getCell()->hasWater()) + if (cell->isExterior()) + runtime.push(0.f); // vanilla oddity, return 0 even though water is actually at -1 + else if (cell->getCell()->hasWater()) runtime.push (cell->getWaterLevel()); else runtime.push (-std::numeric_limits::max()); diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 397e0cac5a..254da56d6e 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -116,7 +116,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - ::Misc::StringUtils::toLower(cell); + ::Misc::StringUtils::lowerCaseInPlace(cell); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." @@ -129,7 +129,7 @@ namespace MWScript for (; it != cells.extEnd(); ++it) { std::string name = it->mName; - ::Misc::StringUtils::toLower(name); + ::Misc::StringUtils::lowerCaseInPlace(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->addVisitedLocation ( it->mName, diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index b0d4d3f2df..7a6afe2e0c 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -145,7 +145,7 @@ namespace MWScript // 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); + mTargetId = reference.getCellRef().getRefId(); } int InterpreterContext::getLocalShort (int index) const @@ -601,9 +601,9 @@ namespace MWScript return mTargetId; } - void InterpreterContext::updatePtr(const MWWorld::Ptr& updated) + void InterpreterContext::updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) { - if (!mReference.isEmpty()) + if (!mReference.isEmpty() && base == mReference) mReference = updated; } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index d3841befdf..3c43444cc7 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -167,7 +167,7 @@ namespace MWScript MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) - void updatePtr(const MWWorld::Ptr& updated); + void updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has been moved to a new cell. virtual std::string getTargetId() const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 32d86f55ad..9c63511b27 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -37,7 +37,7 @@ namespace void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) { - for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) + for (std::vector::iterator it = list->mList.begin(); it != list->mList.end(); ++it) { if (it->mLevel == level && itemId == it->mId) return; @@ -188,7 +188,12 @@ namespace MWScript 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); + + float xr = ptr.getCellRef().getPosition().rot[0]; + float yr = ptr.getCellRef().getPosition().rot[1]; + float zr = ptr.getCellRef().getPosition().rot[2]; + + MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); } } }; @@ -417,9 +422,17 @@ namespace MWScript if(key < 0 || key > 32767 || *end != '\0') key = ESM::MagicEffect::effectStringToId(effect); - runtime.push(ptr.getClass().getCreatureStats(ptr).getMagicEffects().get( - MWMechanics::EffectKey(key)).getMagnitude() > 0); - } + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + if (it->first.mId == key && it->second.getModifier() > 0) + { + runtime.push(1); + return; + } + } + runtime.push(0); + } }; template @@ -1038,6 +1051,11 @@ namespace MWScript msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; } + if (ptr.getRefData().isDeletedByContentFile()) + msg << "[Deleted by content file]" << std::endl; + if (!ptr.getRefData().getCount()) + msg << "[Deleted]" << std::endl; + msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; if (ptr.isInCell()) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index e4ad71875f..2a504f2aba 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -32,7 +32,7 @@ namespace { - std::string getDialogueActorFaction(MWWorld::Ptr actor) + std::string getDialogueActorFaction(MWWorld::ConstPtr actor) { std::string factionId = actor.getClass().getPrimaryFaction(actor); if (factionId.empty()) @@ -530,7 +530,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr actor = R()(runtime, false); + MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -543,7 +543,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -562,7 +562,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr actor = R()(runtime, false); + MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -575,7 +575,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -601,7 +601,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr actor = R()(runtime, false); + MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -614,7 +614,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -633,7 +633,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0) @@ -645,7 +645,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -739,7 +739,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionId; @@ -756,7 +756,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWMechanics::getPlayer(); runtime.push ( @@ -771,7 +771,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -791,7 +791,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, value); @@ -805,7 +805,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -825,7 +825,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, @@ -867,14 +867,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::ConstPtr ptr = R()(runtime); std::string race = runtime.getStringLiteral(runtime[0].mInteger); - ::Misc::StringUtils::toLower(race); + ::Misc::StringUtils::lowerCaseInPlace(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; - ::Misc::StringUtils::toLower(npcRace); + ::Misc::StringUtils::lowerCaseInPlace(npcRace); runtime.push (npcRace == race); } @@ -899,7 +899,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -911,7 +911,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { @@ -931,7 +931,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -958,7 +958,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -1127,6 +1127,7 @@ namespace MWScript { MWBase::Environment::get().getWorld()->undeleteObject(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 679a8d2dea..60847e745b 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include @@ -85,33 +85,19 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - Interpreter::Type_Float angle = runtime[0].mFloat; + Interpreter::Type_Float angle = osg::DegreesToRadians(runtime[0].mFloat); runtime.pop(); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); - float az = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2]); - - MWWorld::LocalRotation localRot = ptr.getRefData().getLocalRotation(); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; + float az = ptr.getRefData().getPosition().rot[2]; if (axis == "x") - { - localRot.rot[0] = 0; - ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); - } else if (axis == "y") - { - localRot.rot[1] = 0; - ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); - } else if (axis == "z") - { - localRot.rot[2] = 0; - ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); - } else throw std::runtime_error ("invalid rotation axis: " + axis); } @@ -200,7 +186,7 @@ namespace MWScript runtime.push(ptr.getRefData().getPosition().pos[2]); } else - throw std::runtime_error ("invalid axis: " + axis); + throw std::runtime_error ("invalid axis: " + axis); } }; @@ -246,7 +232,7 @@ namespace MWScript else throw std::runtime_error ("invalid axis: " + axis); - dynamic_cast(runtime.getContext()).updatePtr(updated); + dynamic_cast(runtime.getContext()).updatePtr(ptr,updated); } }; @@ -314,21 +300,23 @@ namespace MWScript } catch(std::exception&) { - const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); + // cell not found, move to exterior instead (vanilla PositionCell compatibility) + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); if(!cell) { - runtime.getContext().report ("unknown cell (" + cellID + ")"); - std::cerr << "unknown cell (" << cellID << ")\n"; + std::string error = "PositionCell: unknown interior cell (" + cellID + "), moving to exterior instead"; + runtime.getContext().report (error); + std::cerr << error << std::endl; } } if(store) { - MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); - ptr = MWWorld::Ptr(ptr.getBase(), store); - dynamic_cast(runtime.getContext()).updatePtr(ptr); + MWWorld::Ptr base = ptr; + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); + dynamic_cast(runtime.getContext()).updatePtr(base,ptr); float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); @@ -337,7 +325,7 @@ namespace MWScript // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } @@ -374,17 +362,17 @@ namespace MWScript // another morrowind oddity: player will be moved to the exterior cell at this location, // non-player actors will move within the cell they are in. + MWWorld::Ptr base = ptr; if (ptr == MWMechanics::getPlayer()) { MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); - MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); - ptr = MWWorld::Ptr(ptr.getBase(), cell); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); } else { ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); } - dynamic_cast(runtime.getContext()).updatePtr(ptr); + dynamic_cast(runtime.getContext()).updatePtr(base,ptr); float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); @@ -416,7 +404,7 @@ namespace MWScript runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); - Interpreter::Type_Float zRot = runtime[0].mFloat; + Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); MWWorld::CellStore* store = 0; @@ -443,7 +431,7 @@ namespace MWScript pos.pos[1] = y; pos.pos[2] = z; pos.rot[0] = pos.rot[1] = 0; - pos.rot[2] = zRot; + pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); @@ -468,10 +456,14 @@ namespace MWScript runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); - Interpreter::Type_Float zRot = runtime[0].mFloat; + Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (!player.isInCell()) + throw std::runtime_error("player not in a cell"); + MWWorld::CellStore* store = NULL; if (player.getCell()->isExterior()) { @@ -487,7 +479,7 @@ namespace MWScript pos.pos[1] = y; pos.pos[2] = z; pos.rot[0] = pos.rot[1] = 0; - pos.rot[2] = zRot; + pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); @@ -519,6 +511,9 @@ namespace MWScript if (count<0) throw std::runtime_error ("count must be non-negative"); + if (!actor.isInCell()) + throw std::runtime_error ("actor is not in a cell"); + for (int i=0; ilocalRotateObject(ptr,ax+rotation,ay,az); - } + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az); else if (axis == "y") - { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay+rotation,az); - } + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); else if (axis == "z") - { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,az+rotation); - } + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); else throw std::runtime_error ("invalid rotation axis: " + axis); } @@ -603,14 +592,14 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - Interpreter::Type_Float rotation = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); + Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); const float *objRot = ptr.getRefData().getPosition().rot; - float ax = osg::RadiansToDegrees(objRot[0]); - float ay = osg::RadiansToDegrees(objRot[1]); - float az = osg::RadiansToDegrees(objRot[2]); + float ax = objRot[0]; + float ay = objRot[1]; + float az = objRot[2]; if (axis == "x") { @@ -641,15 +630,13 @@ namespace MWScript if (!ptr.isInCell()) return; - MWWorld::LocalRotation rot; - rot.rot[0] = 0; - rot.rot[1] = 0; - rot.rot[2] = 0; - ptr.getRefData().setLocalRotation(rot); + float xr = ptr.getCellRef().getPosition().rot[0]; + float yr = ptr.getCellRef().getPosition().rot[1]; + float zr = ptr.getCellRef().getPosition().rot[2]; - MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true); + MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); - dynamic_cast(runtime.getContext()).updatePtr( + dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); @@ -767,8 +754,8 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition); interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 6a586e81d5..66b7e09e3a 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -6,16 +6,6 @@ #include #include -extern "C" { -#ifndef HAVE_LIBSWRESAMPLE -// FIXME: remove this section once libswresample is packaged for Debian -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 -} - #include namespace MWSound diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index da8e58964b..b27e60c9f5 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -22,13 +22,7 @@ extern "C" // 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 diff --git a/apps/openmw/mwsound/libavwrapper.cpp b/apps/openmw/mwsound/libavwrapper.cpp deleted file mode 100644 index 40be671760..0000000000 --- a/apps/openmw/mwsound/libavwrapper.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef HAVE_LIBSWRESAMPLE -extern "C" -{ -#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 packaged for Debian */ - -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 index 12fe8ae4d0..326c59c079 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -8,49 +8,63 @@ namespace MWSound { - void analyzeLoudness(const std::vector &data, int sampleRate, ChannelConfig chans, - SampleType type, std::vector &out, float valuesPerSecond) - { - int samplesPerSegment = static_cast(sampleRate / valuesPerSecond); - int numSamples = bytesToFrames(data.size(), chans, type); - int advance = framesToBytes(1, chans, type); +void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sampleRate, ChannelConfig chans, SampleType type, float valuesPerSecond) +{ + int samplesPerSegment = static_cast(sampleRate / valuesPerSecond); + int numSamples = bytesToFrames(data.size(), chans, type); + int advance = framesToBytes(1, chans, type); - out.reserve(numSamples/samplesPerSegment); + mSamplesPerSec = valuesPerSecond; + mSamples.clear(); + mSamples.reserve(numSamples/samplesPerSegment); - int segment=0; - int sample=0; - while (segment < 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) { - 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) { - // 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; + 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. } - float rms = 0; // root mean square - if (samplesAdded > 0) - rms = std::sqrt(sum / samplesAdded); - out.push_back(rms); - ++segment; + sum += value*value; + ++samplesAdded; + ++sample; } + + float rms = 0; // root mean square + if (samplesAdded > 0) + rms = std::sqrt(sum / samplesAdded); + mSamples.push_back(rms); + ++segment; } + mReady = true; +} + + +float Sound_Loudness::getLoudnessAtTime(float sec) const +{ + if(mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) + return 0.0f; + + size_t index = static_cast(sec * mSamplesPerSec); + index = std::max(0, std::min(index, mSamples.size()-1)); + return mSamples[index]; +} + } diff --git a/apps/openmw/mwsound/loudness.hpp b/apps/openmw/mwsound/loudness.hpp index df727bd0b2..366d29de51 100644 --- a/apps/openmw/mwsound/loudness.hpp +++ b/apps/openmw/mwsound/loudness.hpp @@ -1,20 +1,40 @@ +#ifndef GAME_SOUND_LOUDNESS_H +#define GAME_SOUND_LOUDNESS_H + +#include + #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); +class Sound_Loudness { + // Loudness sample info + float mSamplesPerSec; + std::vector mSamples; + volatile bool mReady; + +public: + Sound_Loudness() : mSamplesPerSec(0.0f), mReady(false) { } + + /** + * 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 valuesPerSecond How many loudness values per second of audio to compute. + */ + void analyzeLoudness(const std::vector& data, int sampleRate, + ChannelConfig chans, SampleType type, + float valuesPerSecond); + + bool isReady() { return mReady; } + float getLoudnessAtTime(float sec) const; +}; } + +#endif /* GAME_SOUND_LOUDNESS_H */ diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 47889051ae..554b62d264 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -60,7 +60,8 @@ namespace MWSound virtual double getAudioClock() { - return mAudioTrack->getTimeOffset(); + return (double)getSampleOffset()/(double)mAVStream->codec->sample_rate - + MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack); } virtual void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) @@ -85,11 +86,13 @@ namespace MWSound public: ~MovieAudioDecoder() { + if(mAudioTrack.get()) + MWBase::Environment::get().getSoundManager()->stopTrack(mAudioTrack); mAudioTrack.reset(); mDecoderBridge.reset(); } - MWBase::SoundPtr mAudioTrack; + MWBase::SoundStreamPtr mAudioTrack; boost::shared_ptr mDecoderBridge; }; @@ -160,7 +163,8 @@ namespace MWSound boost::shared_ptr decoder(new MWSound::MovieAudioDecoder(videoState)); decoder->setupFormat(); - MWBase::SoundPtr sound = MWBase::Environment::get().getSoundManager()->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + MWBase::SoundStreamPtr sound = sndMgr->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie); if (!sound.get()) { decoder.reset(); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index a984fffa97..d440206329 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -19,9 +19,51 @@ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif +#ifndef ALC_SOFT_HRTF +#define ALC_SOFT_HRTF 1 +#define ALC_HRTF_SOFT 0x1992 +#define ALC_DONT_CARE_SOFT 0x0002 +#define ALC_HRTF_STATUS_SOFT 0x1993 +#define ALC_HRTF_DISABLED_SOFT 0x0000 +#define ALC_HRTF_ENABLED_SOFT 0x0001 +#define ALC_HRTF_DENIED_SOFT 0x0002 +#define ALC_HRTF_REQUIRED_SOFT 0x0003 +#define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 +#define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 +#define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 +#define ALC_HRTF_SPECIFIER_SOFT 0x1995 +#define ALC_HRTF_ID_SOFT 0x1996 +typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); +typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); +#ifdef AL_ALEXT_PROTOTYPES +ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); +ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); +#endif +#endif + + +#define MAKE_PTRID(id) ((void*)(uintptr_t)id) +#define GET_PTRID(ptr) ((ALuint)(uintptr_t)ptr) + namespace { - const int loudnessFPS = 20; // loudness values per second of audio + +const int sLoudnessFPS = 20; // loudness values per second of audio + +// Helper to get an OpenAL extension function +template +void convertPointer(T& dest, R src) +{ + memcpy(&dest, &src, sizeof(src)); +} + +template +void getFunc(T& func, ALCdevice *device, const char *name) +{ + void* funcPtr = alcGetProcAddress(device, name); + convertPointer(func, funcPtr); +} + } namespace MWSound @@ -147,41 +189,30 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) return AL_NONE; } -static ALint getBufferSampleCount(ALuint buf) -{ - ALint size, bits, channels; - - alGetBufferi(buf, AL_SIZE, &size); - alGetBufferi(buf, AL_BITS, &bits); - alGetBufferi(buf, AL_CHANNELS, &channels); - throwALerror(); - - return size / channels * 8 / bits; -} // // A streaming OpenAL sound. // -class OpenAL_SoundStream : public Sound +class OpenAL_SoundStream { static const ALuint sNumBuffers = 6; static const ALfloat sBufferLength; - OpenAL_Output &mOutput; - +private: ALuint mSource; + ALuint mBuffers[sNumBuffers]; + ALint mCurrentBufIdx; ALenum mFormat; ALsizei mSampleRate; ALuint mBufferSize; - - ALuint mSamplesQueued; + ALuint mFrameSize; + ALint mSilence; DecoderPtr mDecoder; volatile bool mIsFinished; - volatile bool mIsInitialBatchEnqueued; void updateAll(bool local); @@ -191,44 +222,52 @@ class OpenAL_SoundStream : public Sound friend class OpenAL_Output; public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags); - virtual ~OpenAL_SoundStream(); + OpenAL_SoundStream(ALuint src, DecoderPtr decoder); + ~OpenAL_SoundStream(); - virtual void stop(); - virtual bool isPlaying(); - virtual double getTimeOffset(); - virtual void update(); + bool isPlaying(); + double getStreamDelay() const; + double getStreamOffset() const; - void play(); bool process(); + ALint refillQueue(); }; - const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; + // // A background streaming thread (keeps active streams processed) // struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; - boost::recursive_mutex mMutex; + + typedef std::vector > DecoderLoudnessVec; + DecoderLoudnessVec mDecoderLoudness; + + volatile bool mQuitNow; + boost::mutex mMutex; + boost::condition_variable mCondVar; boost::thread mThread; StreamThread() - : mThread(boost::ref(*this)) + : mQuitNow(false), mThread(boost::ref(*this)) { } ~StreamThread() { - mThread.interrupt(); + mQuitNow = true; + mMutex.lock(); mMutex.unlock(); + mCondVar.notify_all(); + mThread.join(); } // boost::thread entry point void operator()() { - while(1) + boost::unique_lock lock(mMutex); + while(!mQuitNow) { - mMutex.lock(); StreamVec::iterator iter = mStreams.begin(); while(iter != mStreams.end()) { @@ -237,33 +276,68 @@ struct OpenAL_Output::StreamThread { else ++iter; } - mMutex.unlock(); - boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + + // Only do one loudness decode at a time, in case it takes particularly long we don't + // want to block up anything. + DecoderLoudnessVec::iterator dliter = mDecoderLoudness.begin(); + if(dliter != mDecoderLoudness.end()) + { + DecoderPtr decoder = dliter->first; + Sound_Loudness *loudness = dliter->second; + mDecoderLoudness.erase(dliter); + lock.unlock(); + + std::vector data; + ChannelConfig chans = ChannelConfig_Mono; + SampleType type = SampleType_Int16; + int srate = 48000; + try { + decoder->getInfo(&srate, &chans, &type); + decoder->readAll(data); + } + catch(std::exception &e) { + std::cerr<< "Failed to decode audio: "<analyzeLoudness(data, srate, chans, type, static_cast(sLoudnessFPS)); + lock.lock(); + continue; + } + mCondVar.timed_wait(lock, boost::posix_time::milliseconds(50)); } } void add(OpenAL_SoundStream *stream) { - mMutex.lock(); + boost::unique_lock lock(mMutex); if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end()) + { mStreams.push_back(stream); - mMutex.unlock(); + lock.unlock(); + mCondVar.notify_all(); + } } void remove(OpenAL_SoundStream *stream) { - mMutex.lock(); + boost::lock_guard lock(mMutex); StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream); - if(iter != mStreams.end()) - mStreams.erase(iter); - mMutex.unlock(); + if(iter != mStreams.end()) mStreams.erase(iter); } void removeAll() { - mMutex.lock(); + boost::lock_guard lock(mMutex); mStreams.clear(); - mMutex.unlock(); + mDecoderLoudness.clear(); + } + + void add(DecoderPtr decoder, Sound_Loudness *loudness) + { + boost::unique_lock lock(mMutex); + mDecoderLoudness.push_back(std::make_pair(decoder, loudness)); + lock.unlock(); + mCondVar.notify_all(); } private: @@ -272,12 +346,9 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) - : Sound(osg::Vec3f(0.f, 0.f, 0.f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) - , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true), mIsInitialBatchEnqueued(false) +OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) + : mSource(src), mCurrentBufIdx(0), mFrameSize(0), mSilence(0), mDecoder(decoder), mIsFinished(false) { - throwALerror(); - alGenBuffers(sNumBuffers, mBuffers); throwALerror(); try @@ -290,10 +361,16 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode mFormat = getALFormat(chans, type); mSampleRate = srate; - mBufferSize = static_cast(sBufferLength*srate); - mBufferSize = framesToBytes(mBufferSize, chans, type); + switch(type) + { + case SampleType_UInt8: mSilence = 0x80; break; + case SampleType_Int16: mSilence = 0x00; break; + case SampleType_Float32: mSilence = 0x00; break; + } - mOutput.mActiveSounds.push_back(this); + mFrameSize = framesToBytes(1, chans, type); + mBufferSize = static_cast(sBufferLength*srate); + mBufferSize *= mFrameSize; } catch(std::exception&) { @@ -301,47 +378,14 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode alGetError(); throw; } + mIsFinished = false; } OpenAL_SoundStream::~OpenAL_SoundStream() { - mOutput.mStreamThread->remove(this); - - alSourceStop(mSource); - alSourcei(mSource, AL_BUFFER, 0); - - mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); mDecoder->close(); - - mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(), - mOutput.mActiveSounds.end(), this)); -} - -void OpenAL_SoundStream::play() -{ - alSourceStop(mSource); - alSourcei(mSource, AL_BUFFER, 0); - throwALerror(); - mSamplesQueued = 0; - mIsFinished = false; - mIsInitialBatchEnqueued = false; - mOutput.mStreamThread->add(this); -} - -void OpenAL_SoundStream::stop() -{ - mOutput.mStreamThread->remove(this); - mIsFinished = true; - mIsInitialBatchEnqueued = false; - - alSourceStop(mSource); - alSourcei(mSource, AL_BUFFER, 0); - throwALerror(); - mSamplesQueued = 0; - - mDecoder->rewind(); } bool OpenAL_SoundStream::isPlaying() @@ -356,294 +400,108 @@ bool OpenAL_SoundStream::isPlaying() return !mIsFinished; } -double OpenAL_SoundStream::getTimeOffset() +double OpenAL_SoundStream::getStreamDelay() const { ALint state = AL_STOPPED; - ALfloat offset = 0.0f; - double t; + double d = 0.0; + ALint offset; - mOutput.mStreamThread->mMutex.lock(); - alGetSourcef(mSource, AL_SEC_OFFSET, &offset); + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state == AL_PLAYING || state == AL_PAUSED) - t = (double)(mDecoder->getSampleOffset() - mSamplesQueued)/(double)mSampleRate + offset; - else - t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; - mOutput.mStreamThread->mMutex.unlock(); + { + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + ALint inqueue = mBufferSize/mFrameSize*queued - offset; + d = (double)inqueue / (double)mSampleRate; + } throwALerror(); - return t; + return d; } -void OpenAL_SoundStream::updateAll(bool local) +double OpenAL_SoundStream::getStreamOffset() const { - alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance); - alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance); - if(local) + ALint state = AL_STOPPED; + ALint offset; + double t; + + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING || state == AL_PAUSED) { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE); + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + ALint inqueue = mBufferSize/mFrameSize*queued - offset; + t = (double)(mDecoder->getSampleOffset() - inqueue) / (double)mSampleRate; } else { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); - } - alSourcei(mSource, AL_LOOPING, AL_FALSE); - - update(); -} - -void OpenAL_SoundStream::update() -{ - ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = mPitch; - if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) - { - gain *= 0.9f; - pitch *= 0.7f; + /* Underrun, or not started yet. The decoder offset is where we'll play + * next. */ + t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; } - alSourcef(mSource, AL_GAIN, gain); - alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); + return t; } bool OpenAL_SoundStream::process() { try { - bool finished = mIsFinished; - ALint processed, state; - - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - throwALerror(); - - if(processed > 0) + if(refillQueue() > 0) { - std::vector data(mBufferSize); - do { - ALuint bufid = 0; - size_t got; - - alSourceUnqueueBuffers(mSource, 1, &bufid); - mSamplesQueued -= getBufferSampleCount(bufid); - processed--; - - if(finished) - continue; - - got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - mSamplesQueued += getBufferSampleCount(bufid); - } - } while(processed > 0); - throwALerror(); - } - else if (!mIsInitialBatchEnqueued) { // nothing enqueued yet - std::vector data(mBufferSize); - - for(ALuint i = 0;i < sNumBuffers && !finished;i++) + ALint state; + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state != AL_PLAYING && state != AL_PAUSED) { - size_t got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - ALuint bufid = mBuffers[i]; - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - throwALerror(); - mSamplesQueued += getBufferSampleCount(bufid); - } - } - mIsInitialBatchEnqueued = true; - } - - if(state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued = 0; - - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - if(queued > 0) + refillQueue(); alSourcePlay(mSource); - throwALerror(); + } } - - mIsFinished = finished; } catch(std::exception&) { std::cout<< "Error updating stream \""<getName()<<"\"" < 0) { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); + ALuint buf; + alSourceUnqueueBuffers(mSource, 1, &buf); + --processed; } - alSourcei(mSource, AL_LOOPING, (mFlags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - update(); -} - -void OpenAL_Sound::update() -{ - ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = mPitch; - - if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if(!mIsFinished && (ALuint)queued < sNumBuffers) { - gain *= 0.9f; - pitch *= 0.7f; - } - - alSourcef(mSource, AL_GAIN, gain); - alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - throwALerror(); -} - -void OpenAL_Sound3D::update() -{ - ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = mPitch; - if((mPos - mOutput.mPos).length2() > mMaxDistance*mMaxDistance) - gain = 0.0f; - else if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) - { - gain *= 0.9f; - pitch *= 0.7f; + std::vector data(mBufferSize); + for(;!mIsFinished && (ALuint)queued < sNumBuffers;++queued) + { + size_t got = mDecoder->read(&data[0], data.size()); + if(got < data.size()) + { + mIsFinished = true; + memset(&data[got], mSilence, data.size()-got); + } + if(got > 0) + { + ALuint bufid = mBuffers[mCurrentBufIdx]; + alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + mCurrentBufIdx = (mCurrentBufIdx+1) % sNumBuffers; + } + } } - alSourcef(mSource, AL_GAIN, gain); - alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - throwALerror(); + return queued; } @@ -737,14 +595,6 @@ void OpenAL_Output::deinit() alDeleteSources(1, &mFreeSources[i]); mFreeSources.clear(); - mBufferRefs.clear(); - mUnusedBuffers.clear(); - while(!mBufferCache.empty()) - { - alDeleteBuffers(1, &mBufferCache.begin()->second.mALBuffer); - mBufferCache.erase(mBufferCache.begin()); - } - alcMakeContextCurrent(0); if(mContext) alcDestroyContext(mContext); @@ -757,42 +607,133 @@ void OpenAL_Output::deinit() } -const CachedSound& OpenAL_Output::getBuffer(const std::string &fname) +std::vector OpenAL_Output::enumerateHrtf() { - ALuint buf = 0; + if(!mDevice) + fail("Device not initialized"); + + std::vector ret; + if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + return ret; + + LPALCGETSTRINGISOFT alcGetStringiSOFT = 0; + getFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); + + ALCint num_hrtf; + alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + ret.reserve(num_hrtf); + for(ALCint i = 0;i < num_hrtf;++i) + { + const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + ret.push_back(entry); + } + + return ret; +} - NameMap::iterator iditer = mBufferCache.find(fname); - if(iditer != mBufferCache.end()) +void OpenAL_Output::enableHrtf(const std::string &hrtfname, bool auto_enable) +{ + if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + { + std::cerr<< "HRTF extension not present" < attrs; + attrs.push_back(ALC_HRTF_SOFT); + attrs.push_back(auto_enable ? ALC_DONT_CARE_SOFT : ALC_TRUE); + if(!hrtfname.empty()) { - buf = iditer->second.mALBuffer; - if(mBufferRefs[buf]++ == 0) + ALCint index = -1; + ALCint num_hrtf; + alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + for(ALCint i = 0;i < num_hrtf;++i) + { + const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + if(hrtfname == entry) + { + index = i; + break; + } + } + + if(index < 0) + std::cerr<< "Failed to find HRTF name \""<second; + ALCint hrtf_state; + alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); + if(!hrtf_state) + std::cerr<< "Failed to enable HRTF" < data; - ChannelConfig chans; - SampleType type; - ALenum format; - int srate; +void OpenAL_Output::disableHrtf() +{ + if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + { + std::cerr<< "HRTF extension not present" < attrs; + attrs.push_back(ALC_HRTF_SOFT); + attrs.push_back(ALC_FALSE); + attrs.push_back(0); + alcResetDeviceSOFT(mDevice, &attrs[0]); + + ALCint hrtf_state; + alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); + if(hrtf_state) + std::cerr<< "Failed to disable HRTF" <mResourceMgr->exists(file)) + if(decoder->mResourceMgr->exists(fname)) + decoder->open(fname); + else { + std::string file = fname; std::string::size_type pos = file.rfind('.'); if(pos != std::string::npos) file = file.substr(0, pos)+".mp3"; + decoder->open(file); } - decoder->open(file); + + std::vector data; + ChannelConfig chans; + SampleType type; + ALenum format; + int srate; decoder->getInfo(&srate, &chans, &type); format = getALFormat(chans, type); @@ -800,210 +741,375 @@ const CachedSound& OpenAL_Output::getBuffer(const std::string &fname) decoder->readAll(data); decoder->close(); - CachedSound cached; - analyzeLoudness(data, srate, chans, type, cached.mLoudnessVector, static_cast(loudnessFPS)); + ALuint buf = 0; + try { + alGenBuffers(1, &buf); + alBufferData(buf, format, &data[0], data.size(), srate); + throwALerror(); + } + catch(...) { + if(buf && alIsBuffer(buf)) + alDeleteBuffers(1, &buf); + throw; + } + return MAKE_PTRID(buf); +} + +void OpenAL_Output::unloadSound(Sound_Handle data) +{ + ALuint buffer = GET_PTRID(data); + // Make sure no sources are playing this buffer before unloading it. + SoundVec::const_iterator iter = mActiveSounds.begin(); + for(;iter != mActiveSounds.end();++iter) + { + if(!(*iter)->mHandle) + continue; + + ALuint source = GET_PTRID((*iter)->mHandle); + ALint srcbuf; + alGetSourcei(source, AL_BUFFER, &srcbuf); + if((ALuint)srcbuf == buffer) + { + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); + } + } + alDeleteBuffers(1, &buffer); +} + +size_t OpenAL_Output::getSoundDataSize(Sound_Handle data) const +{ + ALuint buffer = GET_PTRID(data); + ALint size = 0; - alGenBuffers(1, &buf); + alGetBufferi(buffer, AL_SIZE, &size); throwALerror(); - alBufferData(buf, format, &data[0], data.size(), srate); - mBufferRefs[buf] = 1; - cached.mALBuffer = buf; - mBufferCache[fname] = cached; + return (ALuint)size; +} + - ALint bufsize = 0; - alGetBufferi(buf, AL_SIZE, &bufsize); - mBufferCacheMemSize += bufsize; +void OpenAL_Output::initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv) +{ + alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); + alSourcef(source, AL_MAX_DISTANCE, 1000.0f); + alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); - // NOTE: Max buffer cache: 15MB - while(mBufferCacheMemSize > 15*1024*1024) + if(useenv && mListenerEnv == Env_Underwater) { - if(mUnusedBuffers.empty()) - { - std::cout <<"No more unused buffers to clear!"<< std::endl; - break; - } + gain *= 0.9f; + pitch *= 0.7f; + } - ALuint oldbuf = mUnusedBuffers.front(); - mUnusedBuffers.pop_front(); + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); +} - NameMap::iterator nameiter = mBufferCache.begin(); - while(nameiter != mBufferCache.end()) - { - if(nameiter->second.mALBuffer == oldbuf) - mBufferCache.erase(nameiter++); - else - ++nameiter; - } +void OpenAL_Output::initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) +{ + alSourcef(source, AL_REFERENCE_DISTANCE, mindist); + alSourcef(source, AL_MAX_DISTANCE, maxdist); + alSourcef(source, AL_ROLLOFF_FACTOR, 1.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); - bufsize = 0; - alGetBufferi(oldbuf, AL_SIZE, &bufsize); - alDeleteBuffers(1, &oldbuf); - mBufferCacheMemSize -= bufsize; + if((pos - mListenerPos).length2() > maxdist*maxdist) + gain = 0.0f; + if(useenv && mListenerEnv == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; } - return mBufferCache[fname]; + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } -void OpenAL_Output::bufferFinished(ALuint buf) +void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d) { - if(mBufferRefs.at(buf)-- == 1) + if(is3d) + { + if((pos - mListenerPos).length2() > maxdist*maxdist) + gain = 0.0f; + } + if(useenv && mListenerEnv == Env_Underwater) { - mBufferRefs.erase(mBufferRefs.find(buf)); - mUnusedBuffers.push_back(buf); + gain *= 0.9f; + pitch *= 0.7f; } + + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } -MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, float basevol, float pitch, int flags,float offset) + +void OpenAL_Output::playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset) { - boost::shared_ptr sound; - ALuint src=0, buf=0; + ALuint source; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.front(); + source = mFreeSources.front(); mFreeSources.pop_front(); - try - { - buf = getBuffer(fname).mALBuffer; - sound.reset(new OpenAL_Sound(*this, src, buf, osg::Vec3f(0.f, 0.f, 0.f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); + try { + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), + sound->getIsLooping(), sound->getUseEnv()); + + alSourcef(source, AL_SEC_OFFSET, offset); + throwALerror(); + + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcePlay(source); + throwALerror(); + + mActiveSounds.push_back(sound); } - catch(std::exception&) - { - mFreeSources.push_back(src); - if(buf && alIsBuffer(buf)) - bufferFinished(buf); - alGetError(); + catch(std::exception&) { + mFreeSources.push_back(source); throw; } - sound->updateAll(true); - if(offset<0) - offset=0; - if(offset>1) - offset=1; - - alSourcei(src, AL_BUFFER, buf); - alSourcef(src, AL_SEC_OFFSET, static_cast(sound->getLength()*offset / pitch)); - alSourcePlay(src); - throwALerror(); - - return sound; + sound->mHandle = MAKE_PTRID(source); } -MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const osg::Vec3f &pos, float vol, float basevol, float pitch, - float min, float max, int flags, float offset, bool extractLoudness) +void OpenAL_Output::playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset) { - boost::shared_ptr sound; - ALuint src=0, buf=0; + ALuint source; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.front(); + source = mFreeSources.front(); mFreeSources.pop_front(); - try - { - const CachedSound& cached = getBuffer(fname); - buf = cached.mALBuffer; + try { + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), + sound->getUseEnv()); - sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); - if (extractLoudness) - sound->setLoudnessVector(cached.mLoudnessVector, static_cast(loudnessFPS)); + alSourcef(source, AL_SEC_OFFSET, offset); + throwALerror(); + + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcePlay(source); + throwALerror(); + + mActiveSounds.push_back(sound); } - catch(std::exception&) - { - mFreeSources.push_back(src); - if(buf && alIsBuffer(buf)) - bufferFinished(buf); - alGetError(); + catch(std::exception&) { + mFreeSources.push_back(source); throw; } - sound->updateAll(false); + sound->mHandle = MAKE_PTRID(source); +} + +void OpenAL_Output::finishSound(MWBase::SoundPtr sound) +{ + if(!sound->mHandle) return; + ALuint source = GET_PTRID(sound->mHandle); + sound->mHandle = 0; + + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); - if(offset<0) - offset=0; - if(offset>1) - offset=1; + mFreeSources.push_back(source); + mActiveSounds.erase(std::find(mActiveSounds.begin(), mActiveSounds.end(), sound)); +} - alSourcei(src, AL_BUFFER, buf); - alSourcef(src, AL_SEC_OFFSET, static_cast(sound->getLength()*offset / pitch)); +bool OpenAL_Output::isSoundPlaying(MWBase::SoundPtr sound) +{ + if(!sound->mHandle) return false; + ALuint source = GET_PTRID(sound->mHandle); + ALint state; - alSourcePlay(src); + alGetSourcei(source, AL_SOURCE_STATE, &state); throwALerror(); - return sound; + return state == AL_PLAYING || state == AL_PAUSED; +} + +void OpenAL_Output::updateSound(MWBase::SoundPtr sound) +{ + if(!sound->mHandle) return; + ALuint source = GET_PTRID(sound->mHandle); + + updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); } -MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, float pitch, int flags) +void OpenAL_Output::streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) { - boost::shared_ptr sound; - ALuint src; + OpenAL_SoundStream *stream = 0; + ALuint source; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.front(); + source = mFreeSources.front(); mFreeSources.pop_front(); - if((flags&MWBase::SoundManager::Play_Loop)) + if(sound->getIsLooping()) std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; - try - { - sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags)); + try { + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), + false, sound->getUseEnv()); + throwALerror(); + + stream = new OpenAL_SoundStream(source, decoder); + mStreamThread->add(stream); + mActiveStreams.push_back(sound); } - catch(std::exception&) - { - mFreeSources.push_back(src); + catch(std::exception&) { + mStreamThread->remove(stream); + delete stream; + mFreeSources.push_back(source); throw; } - sound->updateAll(true); + sound->mHandle = stream; +} + +void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) +{ + OpenAL_SoundStream *stream = 0; + ALuint source; + + if(mFreeSources.empty()) + fail("No free sources"); + source = mFreeSources.front(); + mFreeSources.pop_front(); + + if(sound->getIsLooping()) + std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; + try { + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); + throwALerror(); - sound->play(); - return sound; + stream = new OpenAL_SoundStream(source, decoder); + mStreamThread->add(stream); + mActiveStreams.push_back(sound); + } + catch(std::exception&) { + mStreamThread->remove(stream); + delete stream; + mFreeSources.push_back(source); + throw; + } + + sound->mHandle = stream; } +void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + ALuint source = stream->mSource; + + sound->mHandle = 0; + mStreamThread->remove(stream); -void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); + + mFreeSources.push_back(source); + mActiveStreams.erase(std::find(mActiveStreams.begin(), mActiveStreams.end(), sound)); + + delete stream; +} + +double OpenAL_Output::getStreamDelay(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return 0.0; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + return stream->getStreamDelay(); +} + +double OpenAL_Output::getStreamOffset(MWBase::SoundStreamPtr sound) { - mPos = pos; - mLastEnvironment = env; + if(!sound->mHandle) return 0.0; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + boost::lock_guard lock(mStreamThread->mMutex); + return stream->getStreamOffset(); +} + +bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return false; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + boost::lock_guard lock(mStreamThread->mMutex); + return stream->isPlaying(); +} + +void OpenAL_Output::updateStream(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + ALuint source = stream->mSource; + + updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); +} + + +void OpenAL_Output::startUpdate() +{ + alcSuspendContext(alcGetCurrentContext()); +} +void OpenAL_Output::finishUpdate() +{ + alcProcessContext(alcGetCurrentContext()); +} + + +void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) +{ if(mContext) { ALfloat orient[6] = { atdir.x(), atdir.y(), atdir.z(), updir.x(), updir.y(), updir.z() }; - alListener3f(AL_POSITION, mPos.x(), mPos.y(), mPos.z()); + alListenerfv(AL_POSITION, pos.ptr()); alListenerfv(AL_ORIENTATION, orient); throwALerror(); } + + mListenerPos = pos; + mListenerEnv = env; } void OpenAL_Output::pauseSounds(int types) { std::vector sources; - SoundVec::const_iterator iter = mActiveSounds.begin(); - while(iter != mActiveSounds.end()) + SoundVec::const_iterator sound = mActiveSounds.begin(); + for(;sound != mActiveSounds.end();++sound) { - const OpenAL_SoundStream *stream = dynamic_cast(*iter); - if(stream) - { - if(stream->mSource && (stream->getPlayType()&types)) - sources.push_back(stream->mSource); - } - else + if(*sound && (*sound)->mHandle && ((*sound)->getPlayType()&types)) + sources.push_back(GET_PTRID((*sound)->mHandle)); + } + StreamVec::const_iterator stream = mActiveStreams.begin(); + for(;stream != mActiveStreams.end();++stream) + { + if(*stream && (*stream)->mHandle && ((*stream)->getPlayType()&types)) { - const OpenAL_Sound *sound = dynamic_cast(*iter); - if(sound && sound->mSource && (sound->getPlayType()&types)) - sources.push_back(sound->mSource); + OpenAL_SoundStream *strm = reinterpret_cast((*stream)->mHandle); + sources.push_back(strm->mSource); } - ++iter; } if(!sources.empty()) { @@ -1015,22 +1121,20 @@ void OpenAL_Output::pauseSounds(int types) void OpenAL_Output::resumeSounds(int types) { std::vector sources; - SoundVec::const_iterator iter = mActiveSounds.begin(); - while(iter != mActiveSounds.end()) + SoundVec::const_iterator sound = mActiveSounds.begin(); + for(;sound != mActiveSounds.end();++sound) { - const OpenAL_SoundStream *stream = dynamic_cast(*iter); - if(stream) - { - if(stream->mSource && (stream->getPlayType()&types)) - sources.push_back(stream->mSource); - } - else + if(*sound && (*sound)->mHandle && ((*sound)->getPlayType()&types)) + sources.push_back(GET_PTRID((*sound)->mHandle)); + } + StreamVec::const_iterator stream = mActiveStreams.begin(); + for(;stream != mActiveStreams.end();++stream) + { + if(*stream && (*stream)->mHandle && ((*stream)->getPlayType()&types)) { - const OpenAL_Sound *sound = dynamic_cast(*iter); - if(sound && sound->mSource && (sound->getPlayType()&types)) - sources.push_back(sound->mSource); + OpenAL_SoundStream *strm = reinterpret_cast((*stream)->mHandle); + sources.push_back(strm->mSource); } - ++iter; } if(!sources.empty()) { @@ -1040,9 +1144,16 @@ void OpenAL_Output::resumeSounds(int types) } +void OpenAL_Output::loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) +{ + mStreamThread->add(decoder, loudness); +} + + OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), - mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) + : Sound_Output(mgr), mDevice(0), mContext(0) + , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) + , mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 755a0e5b62..4986cd3a03 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -16,12 +16,6 @@ namespace MWSound class SoundManager; class Sound; - struct CachedSound - { - ALuint mALBuffer; - std::vector mLoudnessVector; - }; - class OpenAL_Output : public Sound_Output { ALCdevice *mDevice; @@ -29,53 +23,65 @@ namespace MWSound typedef std::deque IDDq; IDDq mFreeSources; - IDDq mUnusedBuffers; - typedef std::map NameMap; - NameMap mBufferCache; + typedef std::vector SoundVec; + SoundVec mActiveSounds; + typedef std::vector StreamVec; + StreamVec mActiveStreams; - typedef std::map IDRefMap; - IDRefMap mBufferRefs; + osg::Vec3f mListenerPos; + Environment mListenerEnv; - uint64_t mBufferCacheMemSize; + struct StreamThread; + std::auto_ptr mStreamThread; - typedef std::vector SoundVec; - SoundVec mActiveSounds; + void initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); + void initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv); - const CachedSound& getBuffer(const std::string &fname); - void bufferFinished(ALuint buffer); + void updateCommon(ALuint source, const osg::Vec3f &pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d); - Environment mLastEnvironment; + OpenAL_Output& operator=(const OpenAL_Output &rhs); + OpenAL_Output(const OpenAL_Output &rhs); + public: virtual std::vector enumerate(); - virtual void init(const std::string &devname=""); + virtual void init(const std::string &devname=std::string()); virtual void deinit(); - /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - 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 osg::Vec3f &pos, - 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 std::vector enumerateHrtf(); + virtual void enableHrtf(const std::string &hrtfname, bool auto_enable); + virtual void disableHrtf(); + + virtual Sound_Handle loadSound(const std::string &fname); + virtual void unloadSound(Sound_Handle data); + virtual size_t getSoundDataSize(Sound_Handle data) const; + + virtual void playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset); + virtual void playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset); + virtual void finishSound(MWBase::SoundPtr sound); + virtual bool isSoundPlaying(MWBase::SoundPtr sound); + virtual void updateSound(MWBase::SoundPtr sound); + + virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound); + virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound); + virtual void finishStream(MWBase::SoundStreamPtr sound); + virtual double getStreamDelay(MWBase::SoundStreamPtr sound); + virtual double getStreamOffset(MWBase::SoundStreamPtr sound); + virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound); + virtual void updateStream(MWBase::SoundStreamPtr sound); + + virtual void startUpdate(); + virtual void finishUpdate(); virtual void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env); virtual void pauseSounds(int types); virtual void resumeSounds(int types); - OpenAL_Output& operator=(const OpenAL_Output &rhs); - OpenAL_Output(const OpenAL_Output &rhs); + virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness); OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); - - struct StreamThread; - std::auto_ptr mStreamThread; - - friend class OpenAL_Sound; - friend class OpenAL_Sound3D; - friend class OpenAL_SoundStream; - friend class SoundManager; }; #ifndef DEFAULT_OUTPUT #define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) diff --git a/apps/openmw/mwsound/sound.cpp b/apps/openmw/mwsound/sound.cpp deleted file mode 100644 index 8b6bfda822..0000000000 --- a/apps/openmw/mwsound/sound.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "sound.hpp" - -namespace MWSound -{ - - float Sound::getCurrentLoudness() - { - if (mLoudnessVector.empty()) - return 0.f; - int index = static_cast(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 96f59cea00..a59027fc12 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -1,18 +1,14 @@ #ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H -#include "soundmanagerimp.hpp" +#include "sound_output.hpp" namespace MWSound { - class Sound - { - virtual void update() = 0; - + class Sound { Sound& operator=(const Sound &rhs); Sound(const Sound &rhs); - protected: osg::Vec3f mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ float mBaseVolume; @@ -20,43 +16,66 @@ namespace MWSound float mMinDistance; float mMaxDistance; int mFlags; + float mFadeOutTime; - std::vector mLoudnessVector; - float mLoudnessFPS; + protected: + Sound_Instance mHandle; + + friend class OpenAL_Output; public: - virtual void stop() = 0; - virtual bool isPlaying() = 0; - virtual double getTimeOffset() = 0; void setPosition(const osg::Vec3f &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } - void setFadeout(float duration) { mFadeOutTime=duration; } - void setLoudnessVector(const std::vector& loudnessVector, float loudnessFPS); + void setBaseVolume(float volume) { mBaseVolume = volume; } + void setFadeout(float duration) { mFadeOutTime = duration; } + void updateFade(float duration) + { + if(mFadeOutTime > 0.0f) + { + float soundDuration = std::min(duration, mFadeOutTime); + mVolume *= (mFadeOutTime-soundDuration) / mFadeOutTime; + mFadeOutTime -= soundDuration; + } + } - /// Get loudness at the current time position on a [0,1] scale. - /// Requires that loudnessVector was filled in by the user. - float getCurrentLoudness(); + const osg::Vec3f &getPosition() const { return mPos; } + float getRealVolume() const { return mVolume * mBaseVolume; } + float getPitch() const { return mPitch; } + float getMinDistance() const { return mMinDistance; } + float getMaxDistance() const { return mMaxDistance; } MWBase::SoundManager::PlayType getPlayType() const { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } - + bool getUseEnv() const { return !(mFlags&MWBase::SoundManager::Play_NoEnv); } + bool getIsLooping() const { return mFlags&MWBase::SoundManager::Play_Loop; } + bool getDistanceCull() const { return mFlags&MWBase::SoundManager::Play_RemoveAtDistance; } + bool getIs3D() const { return mFlags&Play_3D; } Sound(const osg::Vec3f& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) - : mPos(pos) - , mVolume(vol) - , mBaseVolume(basevol) - , mPitch(pitch) - , mMinDistance(mindist) - , mMaxDistance(maxdist) - , mFlags(flags) - , mFadeOutTime(0) - , mLoudnessFPS(20) + : mPos(pos), mVolume(vol), mBaseVolume(basevol), mPitch(pitch) + , mMinDistance(mindist), mMaxDistance(maxdist), mFlags(flags) + , mFadeOutTime(0.0f), mHandle(0) { } - virtual ~Sound() { } + Sound(float vol, float basevol, float pitch, int flags) + : mPos(0.0f, 0.0f, 0.0f), mVolume(vol), mBaseVolume(basevol), mPitch(pitch) + , mMinDistance(1.0f), mMaxDistance(1000.0f), mFlags(flags) + , mFadeOutTime(0.0f), mHandle(0) + { } + }; - friend class OpenAL_Output; - friend class SoundManager; + // Same as above, but it's a different type since the output handles them differently + class Stream : public Sound { + Stream& operator=(const Stream &rhs); + Stream(const Stream &rhs); + + public: + Stream(const osg::Vec3f& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) + : Sound(pos, vol, basevol, pitch, mindist, maxdist, flags) + { } + Stream(float vol, float basevol, float pitch, int flags) + : Sound(vol, basevol, pitch, flags) + { } }; } diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp new file mode 100644 index 0000000000..8f8e43da4b --- /dev/null +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -0,0 +1,32 @@ +#ifndef GAME_SOUND_SOUND_BUFFER_H +#define GAME_SOUND_SOUND_BUFFER_H + +#include + +#include "soundmanagerimp.hpp" +#include "sound_output.hpp" +#include "loudness.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWSound +{ + class Sound_Buffer + { + public: + std::string mResourceName; + + float mVolume; + float mMinDist, mMaxDist; + + Sound_Handle mHandle; + + size_t mUses; + + Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) + : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(0), mUses(0) + { } + }; +} + +#endif /* GAME_SOUND_SOUND_BUFFER_H */ diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index a0c6fb17b9..98eba8466c 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -3,48 +3,73 @@ #include #include +#include #include "soundmanagerimp.hpp" -#include "../mwworld/ptr.hpp" - namespace MWSound { class SoundManager; struct Sound_Decoder; class Sound; + class Sound_Loudness; + + // An opaque handle for the implementation's sound buffers. + typedef void *Sound_Handle; + // An opaque handle for the implementation's sound instances. + typedef void *Sound_Instance; class Sound_Output { SoundManager &mManager; virtual std::vector enumerate() = 0; - virtual void init(const std::string &devname="") = 0; + virtual void init(const std::string &devname=std::string()) = 0; virtual void deinit() = 0; - /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - 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 osg::Vec3f &pos, - 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 std::vector enumerateHrtf() = 0; + virtual void enableHrtf(const std::string &hrtfname, bool auto_enable) = 0; + virtual void disableHrtf() = 0; + + virtual Sound_Handle loadSound(const std::string &fname) = 0; + virtual void unloadSound(Sound_Handle data) = 0; + virtual size_t getSoundDataSize(Sound_Handle data) const = 0; + + virtual void playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset) = 0; + virtual void playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset) = 0; + virtual void finishSound(MWBase::SoundPtr sound) = 0; + virtual bool isSoundPlaying(MWBase::SoundPtr sound) = 0; + virtual void updateSound(MWBase::SoundPtr sound) = 0; + + virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0; + virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0; + virtual void finishStream(MWBase::SoundStreamPtr sound) = 0; + virtual double getStreamDelay(MWBase::SoundStreamPtr sound) = 0; + virtual double getStreamOffset(MWBase::SoundStreamPtr sound) = 0; + virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound) = 0; + virtual void updateStream(MWBase::SoundStreamPtr sound) = 0; + + virtual void startUpdate() = 0; + virtual void finishUpdate() = 0; virtual void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) = 0; virtual void pauseSounds(int types) = 0; virtual void resumeSounds(int types) = 0; + // HACK: The sound output implementation really shouldn't be handling + // asynchronous loudness data loading, but it's currently where we have + // a background processing thread. + virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) = 0; + Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); protected: bool mInitialized; - osg::Vec3f mPos; Sound_Output(SoundManager &mgr) - : mManager(mgr) - , mInitialized(false) - , mPos(0.0f, 0.0f, 0.0f) + : mManager(mgr), mInitialized(false) { } public: virtual ~Sound_Output() { } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index bc97f16016..e8ceaa40f5 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -18,6 +20,7 @@ #include "../mwmechanics/actorutil.hpp" #include "sound_output.hpp" +#include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound.hpp" @@ -39,15 +42,14 @@ namespace MWSound , mMusicVolume(1.0f) , mVoiceVolume(1.0f) , mFootstepsVolume(1.0f) + , mSoundBuffers(new SoundBufferList::element_type()) + , mBufferCacheSize(0) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) , mListenerUp(0,0,1) , mPausedSoundTypes(0) { - if(!useSound) - return; - mMasterVolume = Settings::Manager::getFloat("master volume", "Sound"); mMasterVolume = std::min(std::max(mMasterVolume, 0.0f), 1.0f); mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound"); @@ -59,41 +61,67 @@ namespace MWSound mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f); + mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); + mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); + mBufferCacheMax *= 1024*1024; + mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); + + if(!useSound) + return; + + std::string hrtfname = Settings::Manager::getString("hrtf", "Sound"); + int hrtfstate = Settings::Manager::getInt("hrtf enable", "Sound"); + std::cout << "Sound output: " << SOUND_OUT << std::endl; std::cout << "Sound decoder: " << SOUND_IN << std::endl; - try - { + try { std::vector names = mOutput->enumerate(); std::cout <<"Enumerated output devices:"<< std::endl; for(size_t i = 0;i < names.size();i++) std::cout <<" "<init(devname); } - catch(std::exception &e) - { + catch(std::exception &e) { if(devname.empty()) throw; std::cerr <<"Failed to open device \""<init(); Settings::Manager::setString("device", "Sound", ""); } + + names = mOutput->enumerateHrtf(); + if(!names.empty()) + { + std::cout <<"Enumerated HRTF names:"<< std::endl; + for(size_t i = 0;i < names.size();i++) + std::cout <<" "<disableHrtf(); + else if(!hrtfname.empty()) + mOutput->enableHrtf(hrtfname, hrtfstate<0); } - catch(std::exception &e) - { + catch(std::exception &e) { std::cout <<"Sound init failed: "<begin(); + for(;sfxiter != mSoundBuffers->end();++sfxiter) + { + if(sfxiter->mHandle) + mOutput->unloadSound(sfxiter->mHandle); + sfxiter->mHandle = 0; + } + mUnusedBuffers.clear(); mOutput.reset(); } @@ -103,40 +131,154 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - // Convert a soundId to file name, and modify the volume - // according to the sounds local volume setting, minRange and - // maxRange. - std::string SoundManager::lookup(const std::string &soundId, - float &volume, float &min, float &max) + Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound) { MWBase::World* world = MWBase::Environment::get().getWorld(); - const ESM::Sound *snd = world->getStore().get().find(soundId); - - volume *= static_cast(pow(10.0, (snd->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); + static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->getFloat(); + static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + float volume, min, max; - if(snd->mData.mMinRange == 0 && snd->mData.mMaxRange == 0) + volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); + if(sound->mData.mMinRange == 0 && sound->mData.mMaxRange == 0) { - static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); min = fAudioDefaultMinDistance; max = fAudioDefaultMaxDistance; } else { - min = snd->mData.mMinRange; - max = snd->mData.mMaxRange; + min = sound->mData.mMinRange; + max = sound->mData.mMaxRange; } - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); min *= fAudioMinDistanceMult; max *= fAudioMaxDistanceMult; min = std::max(min, 1.0f); max = std::max(min, max); - return "Sound/"+snd->mSound; + Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(), + Sound_Buffer("Sound/"+sound->mSound, volume, min, max) + ); + mVFS->normalizeFilename(sfx->mResourceName); + + mBufferNameMap.insert(std::make_pair(soundId, sfx)); + + return sfx; + } + + // Lookup a soundId for its sound data (resource name, local volume, + // minRange, and maxRange) + Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const + { + NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); + if(snd != mBufferNameMap.end()) return snd->second; + return 0; + } + + // Lookup a soundId for its sound data (resource name, local volume, + // minRange, and maxRange), and ensure it's ready for use. + Sound_Buffer *SoundManager::loadSound(const std::string &soundId) + { + Sound_Buffer *sfx; + NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); + if(snd != mBufferNameMap.end()) + sfx = snd->second; + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const ESM::Sound *sound = world->getStore().get().find(soundId); + sfx = insertSound(soundId, sound); + } + + if(!sfx->mHandle) + { + sfx->mHandle = mOutput->loadSound(sfx->mResourceName); + mBufferCacheSize += mOutput->getSoundDataSize(sfx->mHandle); + + if(mBufferCacheSize > mBufferCacheMax) + { + do { + if(mUnusedBuffers.empty()) + { + std::cerr<< "No unused sound buffers to free, using "<getSoundDataSize(unused->mHandle); + mOutput->unloadSound(unused->mHandle); + unused->mHandle = 0; + + mUnusedBuffers.pop_back(); + } while(mBufferCacheSize > mBufferCacheMin); + } + mUnusedBuffers.push_front(sfx); + } + + return sfx; + } + + DecoderPtr SoundManager::loadVoice(const std::string &voicefile, Sound_Loudness **lipdata) + { + DecoderPtr decoder = getDecoder(); + // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. + if(mVFS->exists(voicefile)) + decoder->open(voicefile); + else + { + std::string file = voicefile; + std::string::size_type pos = file.rfind('.'); + if(pos != std::string::npos) + file = file.substr(0, pos)+".mp3"; + decoder->open(file); + } + + NameLoudnessRefMap::iterator lipiter = mVoiceLipNameMap.find(voicefile); + if(lipiter != mVoiceLipNameMap.end()) + { + *lipdata = lipiter->second; + return decoder; + } + + mVoiceLipBuffers.insert(mVoiceLipBuffers.end(), Sound_Loudness()); + lipiter = mVoiceLipNameMap.insert( + std::make_pair(voicefile, &mVoiceLipBuffers.back()) + ).first; + + mOutput->loadLoudnessAsync(decoder, lipiter->second); + + *lipdata = lipiter->second; + return decoder; + } + + MWBase::SoundStreamPtr SoundManager::playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); + static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + static float minDistance = std::max(fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult, 1.0f); + static float maxDistance = std::max(fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult, minDistance); + + MWBase::SoundStreamPtr sound; + float basevol = volumeFromType(Play_TypeVoice); + if(playlocal) + { + sound.reset(new Stream(1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice|Play_2D)); + mOutput->streamSound(decoder, sound); + } + else + { + sound.reset(new Stream(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance, + Play_Normal|Play_TypeVoice|Play_3D)); + mOutput->streamSound3D(decoder, sound); + } + return sound; } + // Gets the combined volume settings for the given sound type float SoundManager::volumeFromType(PlayType type) const { @@ -163,23 +305,11 @@ namespace MWSound return volume; } - bool SoundManager::isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const - { - SoundMap::const_iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) - { - if(snditer->second.first == ptr && snditer->second.second == id) - return snditer->first->isPlaying(); - ++snditer; - } - return false; - } - void SoundManager::stopMusic() { if(mMusic) - mMusic->stop(); + mOutput->finishStream(mMusic); mMusic.reset(); } @@ -189,19 +319,19 @@ namespace MWSound return; std::cout <<"Playing "<open(filename); - mMusic = mOutput->streamSound(decoder, volumeFromType(Play_TypeMusic), - 1.0f, Play_NoEnv|Play_TypeMusic); + mMusic.reset(new Stream(1.0f, volumeFromType(Play_TypeMusic), 1.0f, + Play_NoEnv|Play_TypeMusic|Play_2D)); + mOutput->streamSound(decoder, mMusic); } - catch(std::exception &e) - { + catch(std::exception &e) { std::cout << "Music Error: " << e.what() << "\n"; + mMusic.reset(); } } @@ -252,7 +382,7 @@ namespace MWSound bool SoundManager::isMusicPlaying() { - return mMusic && mMusic->isPlaying(); + return mMusic && mOutput->isStreamPlaying(mMusic); } void SoundManager::playPlaylist(const std::string &playlist) @@ -261,31 +391,37 @@ namespace MWSound startRandomTitle(); } - void SoundManager::say(const MWWorld::Ptr &ptr, const std::string& filename) + + void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename) { if(!mOutput->isInitialized()) return; try { - float basevol = volumeFromType(Play_TypeVoice); - std::string filePath = "Sound/"+filename; - const ESM::Position &pos = ptr.getRefData().getPosition(); - const osg::Vec3f objpos(pos.asVec3()); + std::string voicefile = "Sound/"+filename; + + Sound_Loudness *loudness; + mVFS->normalizeFilename(voicefile); + DecoderPtr decoder = loadVoice(voicefile, &loudness); + + if(!loudness->isReady()) + mPendingSaySounds[ptr] = std::make_pair(decoder, loudness); + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); - static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); - static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr); + if (oldIt != mActiveSaySounds.end()) + { + mOutput->finishStream(oldIt->second.first); + mActiveSaySounds.erase(oldIt); + } - float minDistance = fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult; - float maxDistance = fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult; - minDistance = std::max(minDistance, 1.f); - maxDistance = std::max(minDistance, maxDistance); + MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); - MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - minDistance, maxDistance, Play_Normal|Play_TypeVoice, 0, true); - mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); + mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness))); + } } catch(std::exception &e) { @@ -293,19 +429,18 @@ namespace MWSound } } - float SoundManager::getSaySoundLoudness(const MWWorld::Ptr &ptr) const + float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr &ptr) const { - SoundMap::const_iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); + if(snditer != mActiveSaySounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == "_say_sound") - break; - ++snditer; + MWBase::SoundStreamPtr sound = snditer->second.first; + Sound_Loudness *loudness = snditer->second.second; + float sec = mOutput->getStreamOffset(sound); + return loudness->getLoudnessAtTime(sec); } - if (snditer == mActiveSounds.end()) - return 0.f; - return snditer->first->getCurrentLoudness(); + return 0.0f; } void SoundManager::say(const std::string& filename) @@ -314,11 +449,26 @@ namespace MWSound return; try { - float basevol = volumeFromType(Play_TypeVoice); - std::string filePath = "Sound/"+filename; + std::string voicefile = "Sound/"+filename; + + Sound_Loudness *loudness; + mVFS->normalizeFilename(voicefile); + DecoderPtr decoder = loadVoice(voicefile, &loudness); + + if(!loudness->isReady()) + mPendingSaySounds[MWWorld::ConstPtr()] = std::make_pair(decoder, loudness); + else + { + SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr()); + if (oldIt != mActiveSaySounds.end()) + { + mOutput->finishStream(oldIt->second.first); + mActiveSaySounds.erase(oldIt); + } - MWBase::SoundPtr sound = mOutput->playSound(filePath, 1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice, 0); - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound")); + mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(), + std::make_pair(playVoice(decoder, osg::Vec3f(), true), loudness))); + } } catch(std::exception &e) { @@ -326,35 +476,42 @@ namespace MWSound } } - bool SoundManager::sayDone(const MWWorld::Ptr &ptr) const + bool SoundManager::sayDone(const MWWorld::ConstPtr &ptr) const { - return !isPlaying(ptr, "_say_sound"); + SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); + if(snditer != mActiveSaySounds.end()) + { + if(mOutput->isStreamPlaying(snditer->second.first)) + return false; + return true; + } + return mPendingSaySounds.find(ptr) == mPendingSaySounds.end(); } - void SoundManager::stopSay(const MWWorld::Ptr &ptr) + void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SaySoundMap::iterator snditer = mActiveSaySounds.find(ptr); + if(snditer != mActiveSaySounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == "_say_sound") - { - snditer->first->stop(); - mActiveSounds.erase(snditer++); - } - else - ++snditer; + mOutput->finishStream(snditer->second.first); + mActiveSaySounds.erase(snditer); } + mPendingSaySounds.erase(ptr); } - MWBase::SoundPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) + MWBase::SoundStreamPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) { - MWBase::SoundPtr track; + MWBase::SoundStreamPtr track; if(!mOutput->isInitialized()) return track; try { - track = mOutput->streamSound(decoder, volumeFromType(type), 1.0f, Play_NoEnv|type); + track.reset(new Stream(1.0f, volumeFromType(type), 1.0f, Play_NoEnv|type|Play_2D)); + mOutput->streamSound(decoder, track); + + TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), track); + mActiveTracks.insert(iter, track); } catch(std::exception &e) { @@ -363,6 +520,19 @@ namespace MWSound return track; } + void SoundManager::stopTrack(MWBase::SoundStreamPtr stream) + { + mOutput->finishStream(stream); + TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), stream); + if(iter != mActiveTracks.end() && *iter == stream) + mActiveTracks.erase(iter); + } + + double SoundManager::getTrackTimeDelay(MWBase::SoundStreamPtr stream) + { + return mOutput->getStreamDelay(stream); + } + MWBase::SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, PlayType type, PlayMode mode, float offset) { @@ -371,21 +541,28 @@ namespace MWSound return sound; try { + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); float basevol = volumeFromType(type); - float min, max; - std::string file = lookup(soundId, volume, min, max); - sound = mOutput->playSound(file, volume, basevol, pitch, mode|type, offset); - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D)); + mOutput->playSound(sound, sfx->mHandle, offset); + if(sfx->mUses++ == 0) + { + SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + mActiveSounds[MWWorld::ConstPtr()].push_back(std::make_pair(sound, sfx)); } catch(std::exception&) { //std::cout <<"Sound Error: "< 2000*2000) - { + if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000) return MWBase::SoundPtr(); - } - sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset); - if((mode&Play_NoTrack)) - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + if(!(mode&Play_NoPlayerLocal) && ptr == MWMechanics::getPlayer()) + { + sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D)); + mOutput->playSound(sound, sfx->mHandle, offset); + } else - mActiveSounds[sound] = std::make_pair(ptr, soundId); + { + sound.reset(new Sound(objpos, volume * sfx->mVolume, basevol, pitch, + sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D)); + mOutput->playSound3D(sound, sfx->mHandle, offset); + } + if(sfx->mUses++ == 0) + { + SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + mActiveSounds[ptr].push_back(std::make_pair(sound, sfx)); } catch(std::exception&) { //std::cout <<"Sound Error: "<isInitialized()) @@ -427,62 +615,57 @@ namespace MWSound try { // Look up the sound in the ESM data + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); float basevol = volumeFromType(type); - float min, max; - std::string file = lookup(soundId, volume, min, max); - sound = mOutput->playSound3D(file, initialPos, volume, basevol, pitch, min, max, mode|type, offset); - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + sound.reset(new Sound(initialPos, volume * sfx->mVolume, basevol, pitch, + sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D)); + mOutput->playSound3D(sound, sfx->mHandle, offset); + if(sfx->mUses++ == 0) + { + SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + mActiveSounds[MWWorld::ConstPtr()].push_back(std::make_pair(sound, sfx)); } catch(std::exception &) { //std::cout <<"Sound Error: "<first == sound) - { - snditer->first->stop(); - mActiveSounds.erase(snditer++); - } - else - ++snditer; - } + if (sound.get()) + mOutput->finishSound(sound); } - void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId) + void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == soundId) + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) { - snditer->first->stop(); - mActiveSounds.erase(snditer++); + if(sndidx->second == sfx) + mOutput->finishSound(sndidx->first); } - else - ++snditer; } } - void SoundManager::stopSound3D(const MWWorld::Ptr &ptr) + void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == ptr) - { - snditer->first->stop(); - mActiveSounds.erase(snditer++); - } - else - ++snditer; + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + mOutput->finishSound(sndidx->first); } } @@ -491,51 +674,74 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->second.first != MWWorld::Ptr() && - snditer->second.first != MWMechanics::getPlayer() && - snditer->second.first.getCell() == cell) + if(snditer->first != MWWorld::ConstPtr() && + snditer->first != MWMechanics::getPlayer() && + snditer->first.getCell() == cell) { - snditer->first->stop(); - mActiveSounds.erase(snditer++); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + mOutput->finishSound(sndidx->first); } - else - ++snditer; + ++snditer; + } + SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); + while(sayiter != mActiveSaySounds.end()) + { + if(sayiter->first != MWWorld::ConstPtr() && + sayiter->first != MWMechanics::getPlayer() && + sayiter->first.getCell() == cell) + { + mOutput->finishStream(sayiter->second.first); + } + ++sayiter; } } void SoundManager::stopSound(const std::string& soundId) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(MWWorld::ConstPtr()); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == MWWorld::Ptr() && - snditer->second.second == soundId) + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) { - snditer->first->stop(); - mActiveSounds.erase(snditer++); + if(sndidx->second == sfx) + mOutput->finishSound(sndidx->first); } - else - ++snditer; } } - void SoundManager::fadeOutSound3D(const MWWorld::Ptr &ptr, + void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId, float duration) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == soundId) + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) { - snditer->first->setFadeout(duration); + if(sndidx->second == sfx) + sndidx->first->setFadeout(duration); } - ++snditer; } } - bool SoundManager::getSoundPlaying(const MWWorld::Ptr &ptr, const std::string& soundId) const + bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr &ptr, const std::string& soundId) const { - return isPlaying(ptr, soundId); + SoundMap::const_iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) + { + Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::const_iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + { + if(sndidx->second == sfx && mOutput->isSoundPlaying(sndidx->first)) + return true; + } + } + return false; } @@ -567,7 +773,7 @@ namespace MWSound static std::string regionName = ""; static float sTimePassed = 0.0; MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Ptr player = world->getPlayerPtr(); + const MWWorld::ConstPtr player = world->getPlayerPtr(); const ESM::Cell *cell = player.getCell()->getCell(); sTimePassed += duration; @@ -636,18 +842,14 @@ namespace MWSound Environment env = Env_Normal; if (mListenerUnderwater) - { env = Env_Underwater; - //play underwater sound - if(!(mUnderwaterSound && mUnderwaterSound->isPlaying())) - mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv); - } else if(mUnderwaterSound) { - mUnderwaterSound->stop(); + mOutput->finishSound(mUnderwaterSound); mUnderwaterSound.reset(); } + mOutput->startUpdate(); mOutput->updateListener( mListenerPos, mListenerDir, @@ -656,44 +858,149 @@ namespace MWSound ); // Check if any sounds are finished playing, and trash them - // Lower volume on fading out sounds SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(!snditer->first->isPlaying()) - mActiveSounds.erase(snditer++); - else + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + while(sndidx != snditer->second.end()) { - const MWWorld::Ptr &ptr = snditer->second.first; - if(!ptr.isEmpty()) + MWWorld::ConstPtr ptr = snditer->first; + MWBase::SoundPtr sound = sndidx->first; + if(!ptr.isEmpty() && sound->getIs3D()) { const ESM::Position &pos = ptr.getRefData().getPosition(); const osg::Vec3f objpos(pos.asVec3()); - snditer->first->setPosition(objpos); + sound->setPosition(objpos); - if ((snditer->first->mFlags & Play_RemoveAtDistance) - && (mListenerPos - ptr.getRefData().getPosition().asVec3()).length2() > 2000*2000) + if(sound->getDistanceCull()) { - mActiveSounds.erase(snditer++); - continue; + if((mListenerPos - objpos).length2() > 2000*2000) + mOutput->finishSound(sound); } } - //update fade out - if(snditer->first->mFadeOutTime>0) + + if(!mOutput->isSoundPlaying(sound)) + { + mOutput->finishSound(sound); + Sound_Buffer *sfx = sndidx->second; + if(sfx->mUses-- == 1) + mUnusedBuffers.push_front(sfx); + sndidx = snditer->second.erase(sndidx); + } + else { - float soundDuration=duration; - if(soundDuration>snditer->first->mFadeOutTime) - soundDuration=snditer->first->mFadeOutTime; - snditer->first->setVolume(snditer->first->mVolume - - soundDuration / snditer->first->mFadeOutTime * snditer->first->mVolume); - snditer->first->mFadeOutTime -= soundDuration; + sound->updateFade(duration); + + mOutput->updateSound(sound); + ++sndidx; } - snditer->first->update(); + } + if(snditer->second.empty()) + mActiveSounds.erase(snditer++); + else ++snditer; + } + + SayDecoderMap::iterator penditer = mPendingSaySounds.begin(); + while(penditer != mPendingSaySounds.end()) + { + Sound_Loudness *loudness = penditer->second.second; + if(loudness->isReady()) + { + try { + DecoderPtr decoder = penditer->second.first; + decoder->rewind(); + + MWBase::SoundStreamPtr sound; + MWWorld::ConstPtr ptr = penditer->first; + + SaySoundMap::iterator old = mActiveSaySounds.find(ptr); + if (old != mActiveSaySounds.end()) + { + mOutput->finishStream(old->second.first); + mActiveSaySounds.erase(old); + } + + if(ptr == MWWorld::ConstPtr()) + sound = playVoice(decoder, osg::Vec3f(), true); + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); + sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + } + mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness))); + } + catch(std::exception &e) { + std::cerr<< "Sound Error: "<first; + MWBase::SoundStreamPtr sound = sayiter->second.first; + if(!ptr.isEmpty() && sound->getIs3D()) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); + sound->setPosition(pos); + + if(sound->getDistanceCull()) + { + if((mListenerPos - pos).length2() > 2000*2000) + mOutput->finishStream(sound); + } + } + + if(!mOutput->isStreamPlaying(sound)) + { + mOutput->finishStream(sound); + mActiveSaySounds.erase(sayiter++); + } + else + { + sound->updateFade(duration); + + mOutput->updateStream(sound); + ++sayiter; + } + } + + TrackList::iterator trkiter = mActiveTracks.begin(); + for(;trkiter != mActiveTracks.end();++trkiter) + { + MWBase::SoundStreamPtr sound = *trkiter; + if(!mOutput->isStreamPlaying(sound)) + { + mOutput->finishStream(sound); + trkiter = mActiveTracks.erase(trkiter); + } + else + { + sound->updateFade(duration); + + mOutput->updateStream(sound); + ++trkiter; } } + + if(mListenerUnderwater) + { + // Play underwater sound (after updating sounds) + if(!(mUnderwaterSound && mOutput->isSoundPlaying(mUnderwaterSound))) + mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv); + } + mOutput->finishUpdate(); } + void SoundManager::update(float duration) { if(!mOutput->isInitialized()) @@ -716,39 +1023,73 @@ namespace MWSound mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound"); + if(!mOutput->isInitialized()) + return; + mOutput->startUpdate(); SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + for(;snditer != mActiveSounds.end();++snditer) { - snditer->first->mBaseVolume = volumeFromType(snditer->first->getPlayType()); - snditer->first->update(); - ++snditer; + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + { + MWBase::SoundPtr sound = sndidx->first; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateSound(sound); + } + } + SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); + for(;sayiter != mActiveSaySounds.end();++sayiter) + { + MWBase::SoundStreamPtr sound = sayiter->second.first; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateStream(sound); + } + TrackList::iterator trkiter = mActiveTracks.begin(); + for(;trkiter != mActiveTracks.end();++trkiter) + { + MWBase::SoundStreamPtr sound = *trkiter; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateStream(sound); } if(mMusic) { - mMusic->mBaseVolume = volumeFromType(mMusic->getPlayType()); - mMusic->update(); + mMusic->setBaseVolume(volumeFromType(mMusic->getPlayType())); + mOutput->updateStream(mMusic); } + mOutput->finishUpdate(); } - void SoundManager::setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up) + void SoundManager::setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) { mListenerPos = pos; mListenerDir = dir; mListenerUp = up; - MWWorld::Ptr player = - MWMechanics::getPlayer(); - const MWWorld::CellStore *cell = player.getCell(); - - mListenerUnderwater = ((cell->getCell()->mData.mFlags&ESM::Cell::HasWater) && mListenerPos.z() < cell->getWaterLevel()); + mListenerUnderwater = underwater; } - void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + void SoundManager::updatePtr(const MWWorld::ConstPtr &old, const MWWorld::ConstPtr &updated) { - for (SoundMap::iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer) + SoundMap::iterator snditer = mActiveSounds.find(old); + if(snditer != mActiveSounds.end()) + { + SoundBufferRefPairList sndlist = snditer->second; + mActiveSounds.erase(snditer); + mActiveSounds[updated] = sndlist; + } + SaySoundMap::iterator sayiter = mActiveSaySounds.find(old); + if(sayiter != mActiveSaySounds.end()) + { + SoundLoudnessPair sndlist = sayiter->second; + mActiveSaySounds.erase(sayiter); + mActiveSaySounds[updated] = sndlist; + } + SayDecoderMap::iterator penditer = mPendingSaySounds.find(old); + if(penditer != mPendingSaySounds.end()) { - if (snditer->second.first == old) - snditer->second.first = updated; + DecoderLoudnessPair dl = penditer->second; + mPendingSaySounds.erase(penditer); + mPendingSaySounds[updated] = dl; } } @@ -819,10 +1160,29 @@ namespace MWSound void SoundManager::clear() { - for (SoundMap::iterator iter (mActiveSounds.begin()); iter!=mActiveSounds.end(); ++iter) - iter->first->stop(); - + SoundMap::iterator snditer = mActiveSounds.begin(); + for(;snditer != mActiveSounds.end();++snditer) + { + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + { + mOutput->finishSound(sndidx->first); + Sound_Buffer *sfx = sndidx->second; + if(sfx->mUses-- == 1) + mUnusedBuffers.push_front(sfx); + } + } mActiveSounds.clear(); + SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); + for(;sayiter != mActiveSaySounds.end();++sayiter) + mOutput->finishStream(sayiter->second.first); + mActiveSaySounds.clear(); + TrackList::iterator trkiter = mActiveTracks.begin(); + for(;trkiter != mActiveTracks.end();++trkiter) + mOutput->finishStream(*trkiter); + mActiveTracks.clear(); + mPendingSaySounds.clear(); + mUnderwaterSound.reset(); stopMusic(); } } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f79bfce155..695ca92a2a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -1,14 +1,17 @@ #ifndef GAME_SOUND_SOUNDMANAGER_H #define GAME_SOUND_SOUNDMANAGER_H +#include #include #include +#include #include #include #include +#include "loudness.hpp" #include "../mwbase/soundmanager.hpp" namespace VFS @@ -16,16 +19,27 @@ namespace VFS class Manager; } +namespace ESM +{ + struct Sound; +} + namespace MWSound { class Sound_Output; struct Sound_Decoder; class Sound; + class Sound_Buffer; enum Environment { Env_Normal, Env_Underwater }; + // Extra play flags, not intended for caller use + enum PlayModeEx { + Play_2D = 0, + Play_3D = 1<<31 + }; class SoundManager : public MWBase::SoundManager { @@ -43,14 +57,46 @@ namespace MWSound float mVoiceVolume; float mFootstepsVolume; - boost::shared_ptr mMusic; - std::string mCurrentPlaylist; + typedef std::auto_ptr > SoundBufferList; + // List of sound buffers, grown as needed. New enties are added to the + // back, allowing existing Sound_Buffer references/pointers to remain + // valid. + SoundBufferList mSoundBuffers; + size_t mBufferCacheMin; + size_t mBufferCacheMax; + size_t mBufferCacheSize; + + typedef std::map NameBufferMap; + NameBufferMap mBufferNameMap; + + typedef std::deque LoudnessList; + LoudnessList mVoiceLipBuffers; - typedef std::pair PtrIDPair; - typedef std::map SoundMap; + typedef std::map NameLoudnessRefMap; + NameLoudnessRefMap mVoiceLipNameMap; + + // NOTE: unused buffers are stored in front-newest order. + typedef std::deque SoundList; + SoundList mUnusedBuffers; + + typedef std::pair SoundBufferRefPair; + typedef std::vector SoundBufferRefPairList; + typedef std::map SoundMap; SoundMap mActiveSounds; - MWBase::SoundPtr mUnderwaterSound; + typedef std::pair SoundLoudnessPair; + typedef std::map SaySoundMap; + SaySoundMap mActiveSaySounds; + + typedef std::pair DecoderLoudnessPair; + typedef std::map SayDecoderMap; + SayDecoderMap mPendingSaySounds; + + typedef std::vector TrackList; + TrackList mActiveTracks; + + MWBase::SoundStreamPtr mMusic; + std::string mCurrentPlaylist; bool mListenerUnderwater; osg::Vec3f mListenerPos; @@ -59,10 +105,20 @@ namespace MWSound int mPausedSoundTypes; - std::string lookup(const std::string &soundId, - float &volume, float &min, float &max); + MWBase::SoundPtr mUnderwaterSound; + + Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); + + Sound_Buffer *lookupSound(const std::string &soundId) const; + Sound_Buffer *loadSound(const std::string &soundId); + + // Ensures the loudness/"lip" data gets loaded, and returns a decoder + // to start streaming + DecoderPtr loadVoice(const std::string &voicefile, Sound_Loudness **lipdata); + + MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); + void streamMusicFull(const std::string& filename); - bool isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const; void updateSounds(float duration); void updateRegionSound(float duration); @@ -98,7 +154,7 @@ namespace MWSound ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - virtual void say(const MWWorld::Ptr &reference, const std::string& filename); + virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename); ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -106,59 +162,66 @@ namespace MWSound ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const; + virtual bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const; ///< Is actor not speaking? - virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()); + virtual void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()); ///< Stop an actor speaking - virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const; + virtual float getSaySoundLoudness(const MWWorld::ConstPtr& 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); + virtual MWBase::SoundStreamPtr playTrack(const DecoderPtr& decoder, PlayType type); ///< Play a 2D audio track, using a custom decoder + virtual void stopTrack(MWBase::SoundStreamPtr stream); + ///< Stop the given audio track from playing + + virtual double getTrackTimeDelay(MWBase::SoundStreamPtr stream); + ///< Retives the time delay, in seconds, of the audio track (must be a sound + /// returned by \ref playTrack). Only intended to be called by the track + /// decoder's read method. + virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0); ///< Play a sound, independently of 3D-position - ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + virtual MWBase::SoundPtr playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0); ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. - ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playManualSound3D(const osg::Vec3f& initialPos, const std::string& soundId, - float volume, float pitch, PlayType type, PlayMode mode, float offset=0); - ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition. + virtual MWBase::SoundPtr playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, + float volume, float pitch, PlayType type, PlayMode mode, float offset=0); + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. + ///< @param offset Number of seconds into the sound to start playback. - ///< Play a sound from an object - ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end. + virtual void stopSound(MWBase::SoundPtr sound); + ///< Stop the given sound from playing + /// @note no-op if \a sound is null - virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId); + virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId); ///< Stop the given object from playing the given sound, - virtual void stopSound3D(const MWWorld::Ptr &reference); + virtual void stopSound3D(const MWWorld::ConstPtr &reference); ///< Stop the given object from playing all sounds. - virtual void stopSound(MWBase::SoundPtr sound); - ///< Stop the given sound handle - virtual void stopSound(const MWWorld::CellStore *cell); ///< Stop all sounds for the given cell. virtual void stopSound(const std::string& soundId); ///< Stop a non-3d looping sound - virtual void fadeOutSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float duration); + virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration); ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. - virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const; + virtual bool getSoundPlaying(const MWWorld::ConstPtr &reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? virtual void pauseSounds(int types=Play_TypeMask); @@ -169,9 +232,9 @@ namespace MWSound virtual void update(float duration); - virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up); + virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater); - virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); + virtual void updatePtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated); virtual void clear(); }; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 6a780f2ca7..89d31c4853 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -207,7 +207,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot else slot = getCurrentCharacter()->updateSlot (slot, profile); - boost::filesystem::ofstream stream (slot->mPath, std::ios::binary); + // Write to a memory stream first. If there is an exception during the save process, we don't want to trash the + // existing save file we are overwriting. + std::stringstream stream; ESM::ESMWriter writer; @@ -240,7 +242,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); - listener.setLabel("#{sNotifyMessage4}"); + listener.setLabel("#{sNotifyMessage4}", true); Loading::ScopedLoad load(&listener); @@ -262,7 +264,14 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.close(); if (stream.fail()) - throw std::runtime_error("Write operation failed"); + throw std::runtime_error("Write operation failed (memory stream)"); + + // All good, write to file + boost::filesystem::ofstream filestream (slot->mPath, std::ios::binary); + filestream << stream.rdbuf(); + + if (filestream.fail()) + throw std::runtime_error("Write operation failed (file stream)"); Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); @@ -473,9 +482,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(); - MWWorld::Ptr ptr = MWMechanics::getPlayer(); + MWWorld::ConstPtr ptr = MWMechanics::getPlayer(); - ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); + const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId(); // 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); @@ -520,7 +529,7 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { - MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::ConstPtr player = MWMechanics::getPlayer(); std::string name = player.get()->mBase->mName; return mCharacterManager.getCurrentCharacter (create, name); diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 6361b34048..c29377ecb3 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -19,19 +19,26 @@ MWWorld::Action::~Action() {} void MWWorld::Action::execute (const Ptr& actor) { - if (!mSoundId.empty()) + if(!mSoundId.empty()) { - if (mKeepSound && actor == MWMechanics::getPlayer()) + if(mKeepSound && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Normal,mSoundOffset); + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Normal, mSoundOffset + ); else { bool local = mTarget.isEmpty() || !mTarget.isInCell(); // no usable target - - MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget, - mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, - mKeepSound ? MWBase::SoundManager::Play_NoTrack : MWBase::SoundManager::Play_Normal, - mSoundOffset); + if(mKeepSound) + MWBase::Environment::get().getSoundManager()->playSound3D( + (local ? actor : mTarget).getRefData().getPosition().asVec3(), + mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, + MWBase::SoundManager::Play_Normal, mSoundOffset + ); + else + MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget, + mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, + MWBase::SoundManager::Play_Normal, mSoundOffset + ); } } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 82c7fb80e4..84805c70e1 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -19,7 +19,7 @@ namespace MWWorld getTarget().getContainerStore()->remove(getTarget(), 1, actor); // apply to actor - std::string id = getTarget().getClass().getId (getTarget()); + std::string id = getTarget().getCellRef().getRefId(); if (actor.getClass().apply (actor, id, actor) && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 147f229638..a6d6eef2b1 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -52,7 +52,12 @@ namespace MWWorld } } - assert(it != invStore.end()); + if (it == invStore.end()) + { + std::stringstream error; + error << "ActionEquip can't find item " << object.getCellRef().getRefId(); + throw std::runtime_error(error.str()); + } // equip the item in the first free slot std::vector::const_iterator slot=slots_.first.begin(); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index cd6698c985..031f07258c 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -3,6 +3,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" + +#include "../mwworld/class.hpp" + #include "player.hpp" namespace @@ -40,6 +43,11 @@ namespace MWWorld for(std::set::iterator it = followers.begin();it != followers.end();++it) { MWWorld::Ptr follower = *it; + + std::string script = follower.getClass().getScript(follower); + if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) + continue; + if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() <= 800*800) teleport(*it); diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 49197d1671..4d503dcc8d 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,17 +24,6 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); - LiveRef *find (const std::string& name) - { - for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (!iter->mData.isDeletedByContentFile() - && (iter->mRef.hasContentFile() || iter->mData.getCount() > 0) - && iter->mRef.getRefId() == name) - return &*iter; - - return 0; - } - LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index b096301fd6..75d908db83 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,5 +1,7 @@ #include "cells.hpp" +#include + #include #include #include @@ -23,7 +25,7 @@ MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) if (result==mInteriors.end()) { - result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).first; } return &result->second; @@ -36,7 +38,7 @@ MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) if (result==mExteriors.end()) { result = mExteriors.insert (std::make_pair ( - std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell))).first; + std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell, mStore, mReader))).first; } @@ -70,7 +72,7 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const { if (cell.getState()!=CellStore::State_Loaded) - cell.load (mStore, mReader); + cell.load (); ESM::CellState cellState; @@ -114,13 +116,12 @@ MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) } result = mExteriors.insert (std::make_pair ( - std::make_pair (x, y), CellStore (cell))).first; + std::make_pair (x, y), CellStore (cell, mStore, mReader))).first; } if (result->second.getState()!=CellStore::State_Loaded) { - // Multiple plugin support for landscape data is much easier than for references. The last plugin wins. - result->second.load (mStore, mReader); + result->second.load (); } return &result->second; @@ -135,12 +136,12 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) { const ESM::Cell *cell = mStore.get().find(lowerName); - result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).first; } if (result->second.getState()!=CellStore::State_Loaded) { - result->second.load (mStore, mReader); + result->second.load (); } return &result->second; @@ -158,13 +159,13 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, bool searchInContainers) { if (cell.getState()==CellStore::State_Unloaded) - cell.preload (mStore, mReader); + cell.preload (); if (cell.getState()==CellStore::State_Preloaded) { if (cell.hasId (name)) { - cell.load (mStore, mReader); + cell.load (); } else return Ptr(); @@ -172,7 +173,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, Ptr ptr = cell.search (name); - if (!ptr.isEmpty()) + if (!ptr.isEmpty() && MWWorld::CellStore::isAccessible(ptr.getRefData(), ptr.getCellRef())) return ptr; if (searchInContainers) @@ -304,6 +305,29 @@ void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) } } +struct GetCellStoreCallback : public MWWorld::CellStore::GetCellStoreCallback +{ +public: + GetCellStoreCallback(MWWorld::Cells& cells) + : mCells(cells) + { + } + + MWWorld::Cells& mCells; + + virtual MWWorld::CellStore* getCellStore(const ESM::CellId& cellId) + { + try + { + return mCells.getCell(cellId); + } + catch (...) + { + return NULL; + } + } +}; + bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { @@ -321,9 +345,9 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, catch (...) { // silently drop cells that don't exist anymore + std::cerr << "Dropping state for cell " << state.mId.mWorldspace << " (cell no longer exists)" << std::endl; reader.skipRecord(); return true; - /// \todo log } state.load (reader); @@ -333,9 +357,11 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, cellStore->readFog(reader); if (cellStore->getState()!=CellStore::State_Loaded) - cellStore->load (mStore, mReader); + cellStore->load (); + + GetCellStoreCallback callback(*this); - cellStore->readReferences (reader, contentFileMap); + cellStore->readReferences (reader, contentFileMap, &callback); return true; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b33a6f8db8..08f272ea2a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -47,13 +47,16 @@ namespace template MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, - MWWorld::CellStore *cell) + MWWorld::CellStore *cell, const std::map& toIgnore) { for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); iter!=actorList.mList.end(); ++iter) { MWWorld::Ptr actor (&*iter, cell); + if (toIgnore.find(&*iter) != toIgnore.end()) + continue; + if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) return actor; } @@ -141,6 +144,28 @@ namespace ref.load (state); collection.mList.push_back (ref); } + + struct SearchByRefNumVisitor + { + MWWorld::LiveCellRefBase* mFound; + ESM::RefNum mRefNumToFind; + + SearchByRefNumVisitor(const ESM::RefNum& toFind) + : mFound(NULL) + , mRefNumToFind(toFind) + { + } + + bool operator()(const MWWorld::Ptr& ptr) + { + if (ptr.getCellRef().getRefNum() == mRefNumToFind) + { + mFound = ptr.getBase(); + return false; + } + return true; + } + }; } namespace MWWorld @@ -159,7 +184,7 @@ namespace MWWorld LiveRef liveCellRef (ref, ptr); if (deleted) - liveCellRef.mData.setDeleted(true); + liveCellRef.mData.setDeletedByContentFile(true); if (iter != mList.end()) *iter = liveCellRef; @@ -179,8 +204,121 @@ namespace MWWorld return (ref.mRef.mRefnum == pRefnum); } - CellStore::CellStore (const ESM::Cell *cell) - : mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) + void CellStore::moveFrom(const Ptr &object, CellStore *from) + { + if (mState != State_Loaded) + load(); + + mHasState = true; + MovedRefTracker::iterator found = mMovedToAnotherCell.find(object.getBase()); + if (found != mMovedToAnotherCell.end()) + { + // A cell we had previously moved an object to is returning it to us. + assert (found->second == from); + mMovedToAnotherCell.erase(found); + } + else + { + mMovedHere.insert(std::make_pair(object.getBase(), from)); + } + updateMergedRefs(); + } + + MWWorld::Ptr CellStore::moveTo(const Ptr &object, CellStore *cellToMoveTo) + { + if (cellToMoveTo == this) + throw std::runtime_error("moveTo: object is already in this cell"); + + // We assume that *this is in State_Loaded since we could hardly have reference to a live object otherwise. + if (mState != State_Loaded) + throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); + + // Ensure that the object actually exists in the cell + SearchByRefNumVisitor searchVisitor(object.getCellRef().getRefNum()); + forEach(searchVisitor); + if (!searchVisitor.mFound) + throw std::runtime_error("moveTo: object is not in this cell"); + + + // Objects with no refnum can't be handled correctly in the merging process that happens + // on a save/load, so do a simple copy & delete for these objects. + if (!object.getCellRef().getRefNum().hasContentFile()) + { + MWWorld::Ptr copied = object.getClass().copyToCell(object, *cellToMoveTo, object.getRefData().getCount()); + object.getRefData().setCount(0); + object.getRefData().setBaseNode(NULL); + return copied; + } + + MovedRefTracker::iterator found = mMovedHere.find(object.getBase()); + if (found != mMovedHere.end()) + { + // Special case - object didn't originate in this cell + // Move it back to its original cell first + CellStore* originalCell = found->second; + assert (originalCell != this); + originalCell->moveFrom(object, this); + + mMovedHere.erase(found); + + // Now that object is back to its rightful owner, we can move it + if (cellToMoveTo != originalCell) + { + originalCell->moveTo(object, cellToMoveTo); + } + + updateMergedRefs(); + return MWWorld::Ptr(object.getBase(), cellToMoveTo); + } + + cellToMoveTo->moveFrom(object, this); + mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); + + updateMergedRefs(); + return MWWorld::Ptr(object.getBase(), cellToMoveTo); + } + + struct MergeVisitor + { + MergeVisitor(std::vector& mergeTo, const std::map& movedHere, + const std::map& movedToAnotherCell) + : mMergeTo(mergeTo) + , mMovedHere(movedHere) + , mMovedToAnotherCell(movedToAnotherCell) + { + } + + bool operator() (const MWWorld::Ptr& ptr) + { + if (mMovedToAnotherCell.find(ptr.getBase()) != mMovedToAnotherCell.end()) + return true; + mMergeTo.push_back(ptr.getBase()); + return true; + } + + void merge() + { + for (std::map::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + mMergeTo.push_back(it->first); + } + + private: + std::vector& mMergeTo; + + const std::map& mMovedHere; + const std::map& mMovedToAnotherCell; + }; + + void CellStore::updateMergedRefs() + { + mMergedRefs.clear(); + MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); + forEachInternal(visitor); + visitor.merge(); + } + + CellStore::CellStore (const ESM::Cell *cell, const MWWorld::ESMStore& esmStore, std::vector& readerList) + : mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) { mWaterLevel = cell->mWater; } @@ -208,94 +346,65 @@ namespace MWWorld if (mState==State_Preloaded) return std::binary_search (mIds.begin(), mIds.end(), id); - /// \todo address const-issues - return const_cast (this)->search (id).isEmpty(); + return searchConst (id).isEmpty(); } - Ptr CellStore::search (const std::string& id) + template + struct SearchVisitor { - bool oldState = mHasState; - - mHasState = true; - - if (LiveCellRef *ref = mActivators.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mPotions.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mAppas.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mArmors.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mBooks.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mClothes.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mContainers.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mCreatures.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mDoors.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mIngreds.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mCreatureLists.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mItemLists.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mLights.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mLockpicks.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mMiscItems.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mNpcs.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mProbes.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mRepairs.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mStatics.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mWeapons.find (id)) - return Ptr (ref, this); + PtrType mFound; + std::string mIdToFind; + bool operator()(const PtrType& ptr) + { + if (ptr.getCellRef().getRefId() == mIdToFind) + { + mFound = ptr; + return false; + } + return true; + } + }; - mHasState = oldState; + Ptr CellStore::search (const std::string& id) + { + SearchVisitor searchVisitor; + searchVisitor.mIdToFind = id; + forEach(searchVisitor); + return searchVisitor.mFound; + } - return Ptr(); + ConstPtr CellStore::searchConst (const std::string& id) const + { + SearchVisitor searchVisitor; + searchVisitor.mIdToFind = id; + forEachConst(searchVisitor); + return searchVisitor.mFound; } Ptr CellStore::searchViaActorId (int id) { - if (Ptr ptr = ::searchViaActorId (mNpcs, id, this)) + if (Ptr ptr = ::searchViaActorId (mNpcs, id, this, mMovedToAnotherCell)) return ptr; - if (Ptr ptr = ::searchViaActorId (mCreatures, id, this)) + if (Ptr ptr = ::searchViaActorId (mCreatures, id, this, mMovedToAnotherCell)) return ptr; + for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + { + MWWorld::Ptr actor (it->first, this); + if (!actor.getClass().isActor()) + continue; + if (actor.getClass().getCreatureStats (actor).matchesActorId (id) && actor.getRefData().getCount() > 0) + return actor; + } + return Ptr(); } float CellStore::getWaterLevel() const { + if (isExterior()) + return -1; return mWaterLevel; } @@ -307,37 +416,17 @@ namespace MWWorld int CellStore::count() const { - return - mActivators.mList.size() - + mPotions.mList.size() - + mAppas.mList.size() - + mArmors.mList.size() - + mBooks.mList.size() - + mClothes.mList.size() - + mContainers.mList.size() - + mDoors.mList.size() - + mIngreds.mList.size() - + mCreatureLists.mList.size() - + mItemLists.mList.size() - + mLights.mList.size() - + mLockpicks.mList.size() - + mMiscItems.mList.size() - + mProbes.mList.size() - + mRepairs.mList.size() - + mStatics.mList.size() - + mWeapons.mList.size() - + mCreatures.mList.size() - + mNpcs.mList.size(); - } - - void CellStore::load (const MWWorld::ESMStore &store, std::vector &esm) + return mMergedRefs.size(); + } + + void CellStore::load () { if (mState!=State_Loaded) { if (mState==State_Preloaded) mIds.clear(); - loadRefs (store, esm); + loadRefs (); mState = State_Loaded; @@ -347,18 +436,20 @@ namespace MWWorld } } - void CellStore::preload (const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::preload () { if (mState==State_Unloaded) { - listRefs (store, esm); + listRefs (); mState = State_Preloaded; } } - void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::listRefs() { + std::vector& esm = mReader; + assert (mCell); if (mCell->mContextList.empty()) @@ -402,8 +493,10 @@ namespace MWWorld std::sort (mIds.begin(), mIds.end()); } - void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::loadRefs() { + std::vector& esm = mReader; + assert (mCell); if (mCell->mContextList.empty()) @@ -430,7 +523,7 @@ namespace MWWorld continue; } - loadRef (ref, deleted, store); + loadRef (ref, deleted); } } @@ -439,8 +532,10 @@ namespace MWWorld { ESM::CellRef &ref = const_cast(*it); - loadRef (ref, false, store); + loadRef (ref, false); } + + updateMergedRefs(); } bool CellStore::isExterior() const @@ -468,14 +563,16 @@ namespace MWWorld return Ptr(); } - void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) + void CellStore::loadRef (ESM::CellRef& ref, bool deleted) { - Misc::StringUtils::toLower (ref.mRefID); + Misc::StringUtils::lowerCaseInPlace (ref.mRefID); + + const MWWorld::ESMStore& store = mStore; switch (store.find (ref.mRefID)) { case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; - case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; + case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break; case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; @@ -562,10 +659,19 @@ namespace MWWorld writeReferenceCollection (writer, mRepairs); writeReferenceCollection (writer, mStatics); writeReferenceCollection (writer, mWeapons); + + for (MovedRefTracker::const_iterator it = mMovedToAnotherCell.begin(); it != mMovedToAnotherCell.end(); ++it) + { + LiveCellRefBase* base = it->first; + ESM::RefNum refNum = base->mRef.getRefNum(); + ESM::CellId movedTo = it->second->getCell()->getCellId(); + + refNum.save(writer, true, "MVRF"); + movedTo.save(writer); + } } - void CellStore::readReferences (ESM::ESMReader& reader, - const std::map& contentFileMap) + void CellStore::readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback) { mHasState = true; @@ -693,6 +799,50 @@ namespace MWWorld throw std::runtime_error ("unknown type in cell reference section"); } } + + while (reader.isNextSub("MVRF")) + { + reader.cacheSubName(); + ESM::RefNum refnum; + ESM::CellId movedTo; + refnum.load(reader, true, "MVRF"); + movedTo.load(reader); + + // Search for the reference. It might no longer exist if its content file was removed. + SearchByRefNumVisitor visitor(refnum); + forEachInternal(visitor); + + if (!visitor.mFound) + { + std::cerr << "Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)" << std::endl; + continue; + } + + MWWorld::LiveCellRefBase* movedRef = visitor.mFound; + + CellStore* otherCell = callback->getCellStore(movedTo); + + if (otherCell == NULL) + { + std::cerr << "Dropping moved ref tag for " << movedRef->mRef.getRefId() + << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location." << std::endl; + // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. + // Restore original coordinates: + movedRef->mData.setPosition(movedRef->mRef.getPosition()); + continue; + } + + if (otherCell == this) + { + // Should never happen unless someone's tampering with files. + std::cerr << "Found invalid moved ref, ignoring" << std::endl; + continue; + } + + moveTo(MWWorld::Ptr(movedRef, this), otherCell); + } + + updateMergedRefs(); } bool operator== (const CellStore& left, const CellStore& right) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index f88bf09587..27fe9ec036 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -35,19 +35,19 @@ #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld #include "timestamp.hpp" +#include "ptr.hpp" namespace ESM { struct CellState; struct FogState; + struct CellId; } namespace MWWorld { - class Ptr; class ESMStore; - /// \brief Mutable state of a cell class CellStore { @@ -60,6 +60,9 @@ namespace MWWorld private: + const MWWorld::ESMStore& mStore; + std::vector& mReader; + // Even though fog actually belongs to the player and not cells, // it makes sense to store it here since we need it once for each cell. // Note this is NULL until the cell is explored to save some memory @@ -73,6 +76,7 @@ namespace MWWorld MWWorld::TimeStamp mLastRespawn; + // List of refs owned by this cell CellRefList mActivators; CellRefList mPotions; CellRefList mAppas; @@ -94,9 +98,104 @@ namespace MWWorld CellRefList mStatics; CellRefList mWeapons; + typedef std::map MovedRefTracker; + // References owned by a different cell that have been moved here. + // + MovedRefTracker mMovedHere; + // References owned by this cell that have been moved to another cell. + // + MovedRefTracker mMovedToAnotherCell; + + // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from mMovedToAnotherCell + std::vector mMergedRefs; + + /// Moves object from the given cell to this cell. + void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from); + + /// Repopulate mMergedRefs. + void updateMergedRefs(); + + // helper function for forEachInternal + template + bool forEachImp (Visitor& visitor, List& list) + { + for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); + ++iter) + { + if (!isAccessible(iter->mData, iter->mRef)) + continue; + if (!visitor (MWWorld::Ptr(&*iter, this))) + return false; + } + return true; + } + + // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved objects are accounted for. + template + bool forEachInternal (Visitor& visitor) + { + return + forEachImp (visitor, mActivators) && + forEachImp (visitor, mPotions) && + forEachImp (visitor, mAppas) && + forEachImp (visitor, mArmors) && + forEachImp (visitor, mBooks) && + forEachImp (visitor, mClothes) && + forEachImp (visitor, mContainers) && + forEachImp (visitor, mDoors) && + forEachImp (visitor, mIngreds) && + forEachImp (visitor, mItemLists) && + forEachImp (visitor, mLights) && + forEachImp (visitor, mLockpicks) && + forEachImp (visitor, mMiscItems) && + forEachImp (visitor, mProbes) && + forEachImp (visitor, mRepairs) && + forEachImp (visitor, mStatics) && + forEachImp (visitor, mWeapons) && + forEachImp (visitor, mCreatures) && + forEachImp (visitor, mNpcs) && + forEachImp (visitor, mCreatureLists); + } + + /// @note If you get a linker error here, this means the given type can not be stored in a cell. The supported types are + /// defined at the bottom of this file. + template + CellRefList& get(); + public: - CellStore (const ESM::Cell *cell_); + /// Should this reference be accessible to the outside world (i.e. to scripts / game logic)? + /// Determined based on the deletion flags. By default, objects deleted by content files are never accessible; + /// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla + /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). + static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) + { + return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); + } + + /// Moves object from this cell to the given cell. + /// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...) + /// @note throws exception if cellToMoveTo == this + /// @return updated MWWorld::Ptr with the new CellStore pointer set. + MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); + + /// Make a copy of the given object and insert it into this cell. + /// @note If you get a linker error here, this means the given type can not be inserted into a cell. + /// The supported types are defined at the bottom of this file. + template + LiveCellRefBase* insert(const LiveCellRef* ref) + { + mHasState = true; + CellRefList& list = get(); + LiveCellRefBase* ret = &list.insert(*ref); + updateMergedRefs(); + return ret; + } + + /// @param readerList The readers to use for loading of the cell on-demand. + CellStore (const ESM::Cell *cell_, + const MWWorld::ESMStore& store, + std::vector& readerList); const ESM::Cell *getCell() const; @@ -108,10 +207,17 @@ namespace MWWorld bool hasId (const std::string& id) const; ///< May return true for deleted IDs when in preload state. Will return false, if cell is /// unloaded. + /// @note Will not account for moved references which may exist in Loaded state. Use search() instead if the cell is loaded. Ptr search (const std::string& id); ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. + /// @note Triggers CellStore hasState flag. + + ConstPtr searchConst (const std::string& id) const; + ///< Will return an empty Ptr if cell is not loaded. Does not check references in + /// containers. + /// @note Does not trigger CellStore hasState flag. Ptr searchViaActorId (int id); ///< Will return an empty Ptr if cell is not loaded. @@ -128,55 +234,102 @@ namespace MWWorld int count() const; ///< Return total number of references, including deleted ones. - void load (const MWWorld::ESMStore &store, std::vector &esm); + void load (); ///< Load references from content file. - void preload (const MWWorld::ESMStore &store, std::vector &esm); + void preload (); ///< Build ID list from content file. - /// Call functor (ref) for each reference. functor must return a bool. Returning + /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. + /// \note Prefer using forEachConst when possible. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? - /// - /// \note Creatures and NPCs are handled last. - template - bool forEach (Functor& functor) + template + bool forEach (Visitor& visitor) { + if (mState != State_Loaded) + return false; + mHasState = true; - return - forEachImp (functor, mActivators) && - forEachImp (functor, mPotions) && - forEachImp (functor, mAppas) && - forEachImp (functor, mArmors) && - forEachImp (functor, mBooks) && - forEachImp (functor, mClothes) && - forEachImp (functor, mContainers) && - forEachImp (functor, mDoors) && - forEachImp (functor, mIngreds) && - forEachImp (functor, mItemLists) && - forEachImp (functor, mLights) && - forEachImp (functor, mLockpicks) && - forEachImp (functor, mMiscItems) && - forEachImp (functor, mProbes) && - forEachImp (functor, mRepairs) && - forEachImp (functor, mStatics) && - forEachImp (functor, mWeapons) && - forEachImp (functor, mCreatures) && - forEachImp (functor, mNpcs) && - forEachImp (functor, mCreatureLists); + for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) + continue; + + if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) + return false; + } + return true; } - template - bool forEachContainer (Functor& functor) + /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning + /// false will abort the iteration. + /// \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? + template + bool forEachConst (Visitor& visitor) const { + if (mState != State_Loaded) + return false; + + for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) + continue; + + if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) + return false; + } + return true; + } + + + /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning + /// false will abort the iteration. + /// \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? + template + bool forEachType(Visitor& visitor) + { + if (mState != State_Loaded) + return false; + mHasState = true; - return - forEachImp (functor, mContainers) && - forEachImp (functor, mCreatures) && - forEachImp (functor, mNpcs); + CellRefList& list = get(); + + for (typename CellRefList::List::iterator it (list.mList.begin()); it!=list.mList.end(); ++it) + { + LiveCellRefBase* base = &*it; + if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) + continue; + if (!isAccessible(base->mData, base->mRef)) + continue; + if (!visitor(MWWorld::Ptr(base, this))) + return false; + } + + for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + { + LiveCellRefBase* base = it->first; + if (dynamic_cast*>(base)) + if (!visitor(MWWorld::Ptr(base, this))) + return false; + } + return true; + } + + // NOTE: does not account for moved references + // Should be phased out when we have const version of forEach + inline const CellRefList& getReadOnlyDoors() const + { + return mDoors; + } + inline const CellRefList& getReadOnlyStatics() const + { + return mStatics; } bool isExterior() const; @@ -193,47 +346,31 @@ namespace MWWorld void writeReferences (ESM::ESMWriter& writer) const; - void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap); + struct GetCellStoreCallback + { + public: + ///@note must return NULL if the cell is not found + virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0; + }; + + /// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved references) + void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback); void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - template - CellRefList& get() { - throw std::runtime_error ("Storage for type " + std::string(typeid(T).name())+ " does not exist in cells"); - } - - template - const CellRefList& getReadOnly() { - throw std::runtime_error ("Read Only CellRefList access not available for type " + std::string(typeid(T).name()) ); - } - bool isPointConnected(const int start, const int end) const; std::list aStarSearch(const int start, const int end) const; private: - template - bool forEachImp (Functor& functor, List& list) - { - for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); - ++iter) - { - if (iter->mData.isDeletedByContentFile()) - continue; - if (!functor (MWWorld::Ptr(&*iter, this))) - return false; - } - return true; - } - /// Run through references and store IDs - void listRefs(const MWWorld::ESMStore &store, std::vector &esm); + void listRefs(); - void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); + void loadRefs(); - void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); + void loadRef (ESM::CellRef& ref, bool deleted); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. @@ -381,12 +518,6 @@ namespace MWWorld return mWeapons; } - template<> - inline const CellRefList& CellStore::getReadOnly() - { - return mDoors; - } - bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } diff --git a/apps/openmw/mwworld/cellfunctors.hpp b/apps/openmw/mwworld/cellvisitors.hpp similarity index 78% rename from apps/openmw/mwworld/cellfunctors.hpp rename to apps/openmw/mwworld/cellvisitors.hpp index c7fdc793cc..cfb07f7492 100644 --- a/apps/openmw/mwworld/cellfunctors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -1,5 +1,5 @@ -#ifndef GAME_MWWORLD_CELLFUNCTORS_H -#define GAME_MWWORLD_CELLFUNCTORS_H +#ifndef GAME_MWWORLD_CELLVISITORS_H +#define GAME_MWWORLD_CELLVISITORS_H #include #include @@ -9,7 +9,7 @@ namespace MWWorld { - struct ListAndResetObjects + struct ListAndResetObjectsVisitor { std::vector mObjects; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 4c73c603bc..7f2d759b95 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -30,11 +30,6 @@ namespace MWWorld Class::~Class() {} - std::string Class::getId (const Ptr& ptr) const - { - throw std::runtime_error ("class does not support ID retrieval"); - } - void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { @@ -55,12 +50,12 @@ namespace MWWorld throw std::runtime_error ("class does not represent an actor"); } - bool Class::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Class::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return false; } - int Class::getServices(const Ptr &actor) const + int Class::getServices(const ConstPtr &actor) const { throw std::runtime_error ("class does not have services"); } @@ -75,12 +70,12 @@ namespace MWWorld throw std::runtime_error ("class does not have NPC stats"); } - bool Class::hasItemHealth (const Ptr& ptr) const + bool Class::hasItemHealth (const ConstPtr& ptr) const { return false; } - int Class::getItemHealth(const Ptr &ptr) const + int Class::getItemHealth(const ConstPtr &ptr) const { if (ptr.getCellRef().getCharge() == -1) return getItemMaxHealth(ptr); @@ -88,7 +83,7 @@ namespace MWWorld return ptr.getCellRef().getCharge(); } - int Class::getItemMaxHealth (const Ptr& ptr) const + int Class::getItemMaxHealth (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have item health"); } @@ -143,7 +138,7 @@ namespace MWWorld throw std::runtime_error ("class does not support unlocking"); } - bool Class::canLock(const Ptr &ptr) const + bool Class::canLock(const ConstPtr &ptr) const { return false; } @@ -153,12 +148,12 @@ namespace MWWorld throw std::runtime_error ("class does not support time-based uses"); } - float Class::getRemainingUsageTime (const Ptr& ptr) const + float Class::getRemainingUsageTime (const ConstPtr& ptr) const { return -1; } - std::string Class::getScript (const Ptr& ptr) const + std::string Class::getScript (const ConstPtr& ptr) const { return ""; } @@ -173,7 +168,7 @@ namespace MWWorld return 0; } - int Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Class::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error ("class does not support enchanting"); } @@ -188,17 +183,17 @@ namespace MWWorld return osg::Vec3f (0, 0, 0); } - std::pair, bool> Class::getEquipmentSlots (const Ptr& ptr) const + std::pair, bool> Class::getEquipmentSlots (const ConstPtr& ptr) const { return std::make_pair (std::vector(), false); } - int Class::getEquipmentSkill (const Ptr& ptr) const + int Class::getEquipmentSkill (const ConstPtr& ptr) const { return -1; } - int Class::getValue (const Ptr& ptr) const + int Class::getValue (const ConstPtr& ptr) const { throw std::logic_error ("value not supported by this class"); } @@ -208,7 +203,7 @@ namespace MWWorld throw std::runtime_error ("capacity not supported by this class"); } - float Class::getWeight(const Ptr &ptr) const + float Class::getWeight(const ConstPtr &ptr) const { throw std::runtime_error ("weight not supported by this class"); } @@ -218,7 +213,7 @@ namespace MWWorld throw std::runtime_error ("encumbrance not supported by class"); } - bool Class::isEssential (const MWWorld::Ptr& ptr) const + bool Class::isEssential (const MWWorld::ConstPtr& ptr) const { return false; } @@ -241,7 +236,7 @@ namespace MWWorld return *iter->second; } - bool Class::isPersistent(const Ptr &ptr) const + bool Class::isPersistent(const ConstPtr &ptr) const { throw std::runtime_error ("class does not support persistence"); } @@ -252,12 +247,12 @@ namespace MWWorld sClasses.insert(std::make_pair(key, instance)); } - std::string Class::getUpSoundId (const Ptr& ptr) const + std::string Class::getUpSoundId (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have an up sound"); } - std::string Class::getDownSoundId (const Ptr& ptr) const + std::string Class::getDownSoundId (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have an down sound"); } @@ -267,41 +262,41 @@ namespace MWWorld throw std::runtime_error("class does not support soundgen look up"); } - std::string Class::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Class::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error ("class does not have any inventory icon"); } - MWGui::ToolTipInfo Class::getToolTipInfo (const Ptr& ptr) const + MWGui::ToolTipInfo Class::getToolTipInfo (const ConstPtr& ptr, int count) const { throw std::runtime_error ("class does not have a tool tip"); } - bool Class::hasToolTip (const Ptr& ptr) const + bool Class::hasToolTip (const ConstPtr& ptr) const { return false; } - std::string Class::getEnchantment (const Ptr& ptr) const + std::string Class::getEnchantment (const ConstPtr& ptr) const { return ""; } - void Class::adjustScale(const MWWorld::Ptr& ptr, osg::Vec3f& scale) const + void Class::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const { } - std::string Class::getModel(const MWWorld::Ptr &ptr) const + std::string Class::getModel(const MWWorld::ConstPtr &ptr) const { return ""; } - std::string Class::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Class::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { throw std::runtime_error ("class can't be enchanted"); } - std::pair Class::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Class::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { return std::make_pair (1, ""); } @@ -333,44 +328,45 @@ namespace MWWorld } MWWorld::Ptr - Class::copyToCellImpl(const Ptr &ptr, CellStore &cell) const + Class::copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const { - throw std::runtime_error("unable to move class to cell"); + throw std::runtime_error("unable to copy class to cell"); } MWWorld::Ptr - Class::copyToCell(const Ptr &ptr, CellStore &cell) const + Class::copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const { Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference + newPtr.getRefData().setCount(count); return newPtr; } MWWorld::Ptr - Class::copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const + Class::copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const { - Ptr newPtr = copyToCell(ptr, cell); + Ptr newPtr = copyToCell(ptr, cell, count); newPtr.getRefData().setPosition(pos); return newPtr; } - bool Class::isBipedal(const Ptr &ptr) const + bool Class::isBipedal(const ConstPtr &ptr) const { return false; } - bool Class::canFly(const Ptr &ptr) const + bool Class::canFly(const ConstPtr &ptr) const { return false; } - bool Class::canSwim(const Ptr &ptr) const + bool Class::canSwim(const ConstPtr &ptr) const { return false; } - bool Class::canWalk(const Ptr &ptr) const + bool Class::canWalk(const ConstPtr &ptr) const { return false; } @@ -390,26 +386,26 @@ namespace MWWorld throw std::runtime_error("class does not support skills"); } - int Class::getBloodTexture (const MWWorld::Ptr& ptr) const + int Class::getBloodTexture (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support gore"); } void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} - void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} + void Class::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {} - int Class::getBaseGold(const MWWorld::Ptr& ptr) const + int Class::getBaseGold(const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support base gold"); } - bool Class::isClass(const MWWorld::Ptr& ptr, const std::string &className) const + bool Class::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const { return false; } - int Class::getDoorState (const MWWorld::Ptr &ptr) const + int Class::getDoorState (const MWWorld::ConstPtr &ptr) const { throw std::runtime_error("this is not a door"); } @@ -428,26 +424,26 @@ namespace MWWorld return getEncumbrance(ptr) / capacity; } - std::string Class::getSound(const MWWorld::Ptr&) const + std::string Class::getSound(const MWWorld::ConstPtr&) const { return std::string(); } - int Class::getBaseFightRating(const Ptr &ptr) const + int Class::getBaseFightRating(const ConstPtr &ptr) const { throw std::runtime_error("class does not support fight rating"); } - std::string Class::getPrimaryFaction (const MWWorld::Ptr& ptr) const + std::string Class::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const { return std::string(); } - int Class::getPrimaryFactionRank (const MWWorld::Ptr& ptr) const + int Class::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { return -1; } - int Class::getEffectiveArmorRating(const Ptr &ptr, const Ptr &actor) const + int Class::getEffectiveArmorRating(const ConstPtr &armor, const Ptr &actor) const { throw std::runtime_error("class does not support armor ratings"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 1157db6704..85cd9ff7c5 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -66,7 +66,7 @@ namespace MWWorld boost::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items - virtual Ptr copyToCellImpl(const Ptr &ptr, CellStore &cell) const; + virtual Ptr copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const; public: @@ -76,17 +76,11 @@ namespace MWWorld return mTypeName; } - virtual std::string getId (const Ptr& ptr) const; - ///< Return ID of \a ptr or throw an exception, if class does not support ID retrieval - /// (default implementation: throw an exception) - /// @note This function is currently redundant; the same ID can be retrieved by CellRef::getRefId. - /// Leaving it here for now in case we want to optimize later. - virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). - virtual std::string getName (const Ptr& ptr) const = 0; + virtual std::string getName (const ConstPtr& ptr) const = 0; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -98,23 +92,23 @@ namespace MWWorld ///< Return creature stats or throw an exception, if class does not have creature stats /// (default implementation: throw an exception) - virtual bool hasToolTip (const Ptr& ptr) const; + virtual bool hasToolTip (const ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWMechanics::NpcStats& getNpcStats (const Ptr& ptr) const; ///< Return NPC stats or throw an exception, if class does not have NPC stats /// (default implementation: throw an exception) - virtual bool hasItemHealth (const Ptr& ptr) const; + virtual bool hasItemHealth (const ConstPtr& ptr) const; ///< \return Item health data available? (default implementation: false) - virtual int getItemHealth (const Ptr& ptr) const; + virtual int getItemHealth (const ConstPtr& ptr) const; ///< Return current item health or throw an exception if class does not have item health - virtual int getItemMaxHealth (const Ptr& ptr) const; + virtual int getItemMaxHealth (const ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) @@ -161,17 +155,17 @@ namespace MWWorld virtual void unlock (const Ptr& ptr) const; ///< Unlock object (default implementation: throw an exception) - virtual bool canLock (const Ptr& ptr) const; + virtual bool canLock (const ConstPtr& ptr) const; virtual void setRemainingUsageTime (const Ptr& ptr, float duration) const; ///< Sets the remaining duration of the object, such as an equippable light /// source. (default implementation: throw an exception) - virtual float getRemainingUsageTime (const Ptr& ptr) const; + virtual float getRemainingUsageTime (const ConstPtr& ptr) const; ///< Returns the remaining duration of the object, such as an equippable light /// source. (default implementation: -1, i.e. infinite) - virtual std::string getScript (const Ptr& ptr) const; + virtual std::string getScript (const ConstPtr& ptr) const; ///< Return name of the script attached to ptr (default implementation: return an empty /// string). @@ -187,19 +181,19 @@ namespace MWWorld virtual osg::Vec3f getRotationVector (const Ptr& ptr) const; ///< Return desired rotations, as euler angles. - virtual std::pair, bool> getEquipmentSlots (const Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? /// /// Default implementation: return (empty vector, false). - virtual int getEquipmentSkill (const Ptr& ptr) + virtual int getEquipmentSkill (const ConstPtr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. /// (default implementation: return -1) - virtual int getValue (const Ptr& ptr) const; + virtual int getValue (const ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. /// (default implementation: throws an exception) @@ -229,16 +223,16 @@ namespace MWWorld /// /// (default implementations: throws an exception) - virtual bool isEssential (const MWWorld::Ptr& ptr) const; + virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) /// /// (default implementation: return false) - virtual std::string getUpSoundId (const Ptr& ptr) const; + virtual std::string getUpSoundId (const ConstPtr& ptr) const; ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) - virtual std::string getDownSoundId (const Ptr& ptr) const; + virtual std::string getDownSoundId (const ConstPtr& ptr) const; ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) @@ -248,48 +242,47 @@ namespace MWWorld virtual float getArmorRating (const MWWorld::Ptr& ptr) const; ///< @return combined armor rating of this actor - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; ///< @return the number of enchantment points available for possible enchanting - virtual void adjustScale(const MWWorld::Ptr& ptr, osg::Vec3f& scale) const; + virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices - virtual int getServices (const MWWorld::Ptr& actor) const; + virtual int getServices (const MWWorld::ConstPtr& actor) const; - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; - virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; } + virtual bool isKey (const MWWorld::ConstPtr& ptr) const { return false; } - virtual bool isGold(const MWWorld::Ptr& ptr) const { return false; }; + virtual bool isGold(const MWWorld::ConstPtr& ptr) const { return false; } /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; - virtual Ptr - copyToCell(const Ptr &ptr, CellStore &cell) const; + virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const; - virtual Ptr - copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const; + virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const; virtual bool isActor() const { return false; @@ -299,10 +292,10 @@ namespace MWWorld return false; } - virtual bool isBipedal(const MWWorld::Ptr& ptr) const; - virtual bool canFly(const MWWorld::Ptr& ptr) const; - virtual bool canSwim(const MWWorld::Ptr& ptr) const; - virtual bool canWalk(const MWWorld::Ptr& ptr) const; + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; + virtual bool canFly(const MWWorld::ConstPtr& ptr) const; + virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; + virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; bool isPureWaterCreature(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const; @@ -312,7 +305,7 @@ namespace MWWorld const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. @@ -321,12 +314,12 @@ namespace MWWorld static void registerClass (const std::string& key, boost::shared_ptr instance); - virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; - virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; /// 0 = nothing, 1 = opening, 2 = closing - virtual int getDoorState (const MWWorld::Ptr &ptr) const; + virtual int getDoorState (const MWWorld::ConstPtr &ptr) const; /// This does not actually cause the door to move. Use World::activateDoor instead. virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; @@ -335,15 +328,15 @@ namespace MWWorld virtual void restock (const MWWorld::Ptr& ptr) const {} /// Returns sound id - virtual std::string getSound(const MWWorld::Ptr& ptr) const; + virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; - virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; + virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; - virtual std::string getPrimaryFaction (const MWWorld::Ptr& ptr) const; - virtual int getPrimaryFactionRank (const MWWorld::Ptr& ptr) const; + virtual std::string getPrimaryFaction (const MWWorld::ConstPtr& ptr) const; + virtual int getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const; /// Get the effective armor rating, factoring in the actor's skills, for the given armor. - virtual int getEffectiveArmorRating(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; + virtual int getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index bcaaeff94a..ab9fa46110 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -94,7 +94,7 @@ void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::Object } template -void MWWorld::ContainerStore::storeStates (CellRefList& collection, +void MWWorld::ContainerStore::storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); @@ -176,7 +176,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: return retval; } -bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) { const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); @@ -336,7 +336,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, return addNewStack(ptr, count); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr, int count) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const ConstPtr& ptr, int count) { ContainerStoreIterator it = begin(); @@ -415,6 +415,7 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std:: void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel, const std::string& levItem) { + if (count == 0) return; //Don't restock with nothing. try { ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); @@ -442,9 +443,11 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks if (!levItem.empty() && count < 0) { - if (mLevelledItemMap.find(id) == mLevelledItemMap.end()) - mLevelledItemMap[id] = 0; - mLevelledItemMap[id] += std::abs(count); + //If there is no item in map, insert it + std::map, int>::iterator itemInMap = + mLevelledItemMap.insert(std::make_pair(std::make_pair(id, levItem), 0)).first; + //Update spawned count + itemInMap->second += std::abs(count); } count = std::abs(count); @@ -461,30 +464,84 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner) { - // Remove the items already spawned by levelled items that will restock - for (std::map::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) + //allowedForReplace - Holds information about how many items from the list were not sold; + // Hence, tells us how many items we don't need to restock. + //allowedForReplace[list] <- How many items we should generate(how many of these were sold) + std::map allowedForReplace; + + //Check which lists need restocking: + for (std::map, int>::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end();) { - if (count(it->first) >= it->second) - remove(it->first, it->second, ptr); + int spawnedCount = it->second; //How many items should be in shop originally + int itemCount = count(it->first.first); //How many items are there in shop now + //If something was not sold + if(itemCount >= spawnedCount) + { + const std::string& parent = it->first.second; + // Security check for old saves: + //If item is imported from old save(doesn't have an parent) and wasn't sold + if(parent == "") + { + //Remove it, from shop, + remove(it->first.first, itemCount, ptr);//ptr is the NPC + //And remove it from map, so that when we restock, the new item will have proper parent. + mLevelledItemMap.erase(it++); + continue; + } + //Create the entry if it does not exist yet + std::map::iterator listInMap = allowedForReplace.insert( + std::make_pair(it->first.second, 0)).first; + //And signal that we don't need to restock item from this list + listInMap->second += std::abs(itemCount); + } + //If every of the item was sold + else if (itemCount == 0) + { + mLevelledItemMap.erase(it++); + continue; + } + //If some was sold, but some remain + else + { + //Create entry if it does not exist yet + std::map::iterator listInMap = allowedForReplace.insert( + std::make_pair(it->first.second, 0)).first; + //And signal that we don't need to restock all items from this list + listInMap->second += std::abs(itemCount); + //And update itemCount so we don't mistake it next time. + it->second = itemCount; + } + ++it; } - mLevelledItemMap.clear(); + //Restock: + //For every item that NPC could have for (std::vector::const_iterator it = items.mList.begin(); it != items.mList.end(); ++it) { + //If he shouldn't have it restocked, don't restock it. if (it->mCount >= 0) continue; - std::string item = Misc::StringUtils::lowerCase(it->mItem.toString()); + std::string itemOrList = Misc::StringUtils::lowerCase(it->mItem.toString()); + //If it's levelled list, restock if there's need to do so. if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem.toString())) { - addInitialItem(item, owner, it->mCount, true); + std::map::iterator listInMap = allowedForReplace.find(itemOrList); + + int restockNum = it->mCount; + //If we know we must restock less, take it into account + if(listInMap != allowedForReplace.end()) + restockNum += listInMap->second;//We add, because list items have negative count + //restock + addInitialItem(itemOrList, owner, restockNum, true); } else { - int currentCount = count(item); + //Restocking static item - just restock to the max count + int currentCount = count(itemOrList); if (currentCount < std::abs(it->mCount)) - addInitialItem(item, owner, std::abs(it->mCount) - currentCount, true); + addInitialItem(itemOrList, owner, std::abs(it->mCount) - currentCount, true); } } flagAsModified(); @@ -528,7 +585,7 @@ float MWWorld::ContainerStore::getWeight() const return mCachedWeight; } -int MWWorld::ContainerStore::getType (const Ptr& ptr) +int MWWorld::ContainerStore::getType (const ConstPtr& ptr) { if (ptr.isEmpty()) throw std::runtime_error ("can't put a non-existent object into a container"); @@ -650,7 +707,7 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) return Ptr(); } -void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) +void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const { state.mItems.clear(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index e9750a6228..b7ec4bb74e 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -68,9 +69,9 @@ namespace MWWorld MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; - std::map mLevelledItemMap; - ///< Stores result of levelled item spawns. - /// This is used to remove the spawned item(s) if the levelled item is restocked. + std::map, int> mLevelledItemMap; + ///< Stores result of levelled item spawns. <(refId, spawningGroup), count> + /// This is used to restock levelled items(s) if the old item was sold. mutable float mCachedWeight; mutable bool mWeightUpToDate; @@ -85,7 +86,7 @@ namespace MWWorld void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; template - void storeStates (CellRefList& collection, + void storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; @@ -141,14 +142,14 @@ namespace MWWorld int count (const std::string& id); protected: - ContainerStoreIterator addNewStack (const Ptr& ptr, int count); + ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); ///< Add the item to this container (do not try to stack it onto existing items) virtual void flagAsModified(); public: - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2); ///< @return true if the two specified objects can stack with each other void fill (const ESM::InventoryList& items, const std::string& owner); @@ -162,14 +163,13 @@ namespace MWWorld float getWeight() const; ///< Return total weight of the items contained in *this. - static int getType (const Ptr& ptr); + static int getType (const ConstPtr& ptr); ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. Ptr search (const std::string& id); - /// \todo make this method const once const-correct ContainerStoreIterators are available - virtual void writeState (ESM::InventoryState& state); + virtual void writeState (ESM::InventoryState& state) const; virtual void readState (const ESM::InventoryState& state); diff --git a/apps/openmw/mwworld/customdata.cpp b/apps/openmw/mwworld/customdata.cpp new file mode 100644 index 0000000000..a63123bcf5 --- /dev/null +++ b/apps/openmw/mwworld/customdata.cpp @@ -0,0 +1,74 @@ +#include "customdata.hpp" + +#include +#include +#include + +namespace MWWorld +{ + +MWClass::CreatureCustomData &CustomData::asCreatureCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::CreatureCustomData &CustomData::asCreatureCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::NpcCustomData &CustomData::asNpcCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::NpcCustomData &CustomData::asNpcCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::ContainerCustomData &CustomData::asContainerCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::DoorCustomData &CustomData::asDoorCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::DoorCustomData &CustomData::asDoorCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; + throw std::logic_error(error.str()); +} + + +} diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index 588991fe40..11932e690f 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -1,6 +1,15 @@ #ifndef GAME_MWWORLD_CUSTOMDATA_H #define GAME_MWWORLD_CUSTOMDATA_H +namespace MWClass +{ + class CreatureCustomData; + class NpcCustomData; + class ContainerCustomData; + class DoorCustomData; + class CreatureLevListCustomData; +} + namespace MWWorld { /// \brief Base class for the MW-class-specific part of RefData @@ -11,6 +20,22 @@ namespace MWWorld virtual ~CustomData() {} virtual CustomData *clone() const = 0; + + // Fast version of dynamic_cast. Needs to be overridden in the respective class. + + virtual MWClass::CreatureCustomData& asCreatureCustomData(); + virtual const MWClass::CreatureCustomData& asCreatureCustomData() const; + + virtual MWClass::NpcCustomData& asNpcCustomData(); + virtual const MWClass::NpcCustomData& asNpcCustomData() const; + + virtual MWClass::ContainerCustomData& asContainerCustomData(); + + virtual MWClass::DoorCustomData& asDoorCustomData(); + virtual const MWClass::DoorCustomData& asDoorCustomData() const; + + virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); + virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; }; } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index dea468d22b..1882c6e1aa 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -32,6 +32,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) ESM::Dialogue *dialogue = 0; + // Land texture loading needs to use a separate internal store for each plugin. + // We set the number of plugins here to avoid continual resizes during loading, + // and so we can properly verify if valid plugin indices are being passed to the + // LandTexture Store retrieval methods. + mLandTextures.resize(esm.getGlobalReaderList()->size()); + /// \todo Move this to somewhere else. ESMReader? // Cache parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This will greaty accelerate @@ -96,34 +102,18 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) throw std::runtime_error(error.str()); } } else { - // Load it - std::string id = esm.getHNOString("NAME"); - // ... unless it got deleted! This means that the following record - // has been deleted, and trying to load it using standard assumptions - // on the structure will (probably) fail. - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; - } - it->second->load(esm, id); - - // DELE can also occur after the usual subrecords - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; + RecordId id = it->second->load(esm); + if (id.mIsDeleted) + { + it->second->eraseStatic(id.mId); + continue; } if (n.val==ESM::REC_DIAL) { - dialogue = const_cast(mDialogs.find(id)); + dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = 0; } - // Insert the reference into the global lookup - if (!id.empty() && isCacheableRecord(n.val)) { - mIds[Misc::StringUtils::lowerCase (id)] = n.val; - } } listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); } @@ -131,9 +121,20 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) void ESMStore::setUp() { - std::map::iterator it = mStores.begin(); - for (; it != mStores.end(); ++it) { - it->second->setUp(); + mIds.clear(); + + std::map::iterator storeIt = mStores.begin(); + for (; storeIt != mStores.end(); ++storeIt) { + storeIt->second->setUp(); + + if (isCacheableRecord(storeIt->first)) + { + std::vector identifiers; + storeIt->second->listIdentifier(identifiers); + + for (std::vector::const_iterator record = identifiers.begin(); record != identifiers.end(); ++record) + mIds[*record] = storeIt->first; + } } mSkills.setUp(); mMagicEffects.setUp(); @@ -195,19 +196,13 @@ void ESMStore::setUp() case ESM::REC_LEVC: { - std::string id = reader.getHNString ("NAME"); - mStores[type]->read (reader, id); - - // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame - // that really should be cleared instead of just overwritten - - mIds[id] = type; + mStores[type]->read (reader); } if (type==ESM::REC_NPC_) { // NPC record will always be last and we know that there can be only one - // dynamic NPC record (player) -> We are done here with dynamic record laoding + // dynamic NPC record (player) -> We are done here with dynamic record loading setUp(); const ESM::NPC *player = mNpcs.find ("player"); diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index dcd7924a22..acc06b28a9 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -13,7 +13,7 @@ namespace MWWorld { Globals::Collection::const_iterator Globals::find (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -23,7 +23,7 @@ namespace MWWorld Globals::Collection::iterator Globals::find (const std::string& name) { - Collection::iterator iter = mVariables.find (name); + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -40,28 +40,28 @@ namespace MWWorld for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); ++iter) { - mVariables.insert (std::make_pair (iter->mId, iter->mValue)); + mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (iter->mId), *iter)); } } const ESM::Variant& Globals::operator[] (const std::string& name) const { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } ESM::Variant& Globals::operator[] (const std::string& name) { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } char Globals::getType (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) return ' '; - switch (iter->second.getType()) + switch (iter->second.mValue.getType()) { case ESM::VT_Short: return 's'; case ESM::VT_Long: return 'l'; @@ -81,8 +81,7 @@ namespace MWWorld for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { writer.startRecord (ESM::REC_GLOB); - writer.writeHNString ("NAME", iter->first); - iter->second.write (writer, ESM::Variant::Format_Global); + iter->second.save (writer); writer.endRecord (ESM::REC_GLOB); } } @@ -91,14 +90,17 @@ namespace MWWorld { if (type==ESM::REC_GLOB) { - std::string id = reader.getHNString ("NAME"); + ESM::Global global; + bool isDeleted = false; - Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id)); + // This readRecord() method is used when reading a saved game. + // Deleted globals can't appear there, so isDeleted will be ignored here. + global.load(reader, isDeleted); + Misc::StringUtils::lowerCaseInPlace(global.mId); + Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) - iter->second.read (reader, ESM::Variant::Format_Global); - else - reader.skipRecord(); + iter->second = global; return true; } diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index bb4ab13d92..3468c2e719 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include namespace ESM { @@ -29,7 +29,7 @@ namespace MWWorld { private: - typedef std::map Collection; + typedef std::map Collection; Collection mVariables; // type, value diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index ebba4ae30d..b82b798d11 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -410,11 +410,6 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // the items should appear as if they'd always been equipped. mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); - - // Apply instant effects - MWMechanics::CastSpell cast(actor, actor); - if (magnitude) - cast.applyInstantEffect(actor, actor, effectIt->mEffectID, magnitude); } if (magnitude) @@ -455,7 +450,7 @@ void MWWorld::InventoryStore::flagAsModified() mRechargingItemsUpToDate = false; } -bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) { bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); if (!canStack) @@ -705,7 +700,7 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sou if (*iter==end()) continue; - if ((*iter)->getClass().getId(**iter) != sourceId) + if ((*iter)->getCellRef().getRefId() != sourceId) continue; std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); @@ -747,7 +742,7 @@ void MWWorld::InventoryStore::clear() ContainerStore::clear(); } -bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) +bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) { for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) { @@ -757,7 +752,7 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) return false; } -void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) +void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const { MWWorld::ContainerStore::writeState(state); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 95c16c115e..28c44bcec1 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -141,7 +141,7 @@ namespace MWWorld void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \warning \a iterator can not be an end()-iterator, use unequip function instead - bool isEquipped(const MWWorld::Ptr& item); + bool isEquipped(const MWWorld::ConstPtr& item); ///< Utility function, returns true if the given item is equipped in any slot void setSelectedEnchantItem(const ContainerStoreIterator& iterator); @@ -167,7 +167,7 @@ namespace MWWorld ///< \attention This function is internal to the world model and should not be called from /// outside. - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2); ///< @return true if the two specified objects can stack with each other virtual int remove(const Ptr& item, int count, const Ptr& actor); @@ -207,7 +207,7 @@ namespace MWWorld virtual void clear(); ///< Empty container. - virtual void writeState (ESM::InventoryState& state); + virtual void writeState (ESM::InventoryState& state) const; virtual void readState (const ESM::InventoryState& state); }; diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index bfc708185d..32830b5fb9 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -11,7 +11,7 @@ #include "class.hpp" #include "esmstore.hpp" -MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef &cref) +MWWorld::LiveCellRefBase::LiveCellRefBase(const std::string& type, const ESM::CellRef &cref) : mClass(&Class::get(type)), mRef(cref), mData(cref) { } @@ -19,7 +19,7 @@ MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef & void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) { mRef = state.mRef; - mData = RefData (state); + mData = RefData (state, mData.isDeletedByContentFile()); Ptr ptr (this); @@ -54,8 +54,7 @@ void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const { mRef.writeState(state); - /// \todo get rid of this cast once const-correct Ptr are available - Ptr ptr (const_cast (this)); + ConstPtr ptr (this); mData.write (state, mClass->getScript (ptr)); diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 3994d8a249..2631f513fb 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -31,7 +31,7 @@ namespace MWWorld /** runtime-data */ RefData mData; - LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef()); + LiveCellRefBase(const std::string& type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index e30246f7cb..46d0b3cc22 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -11,51 +11,59 @@ namespace { - template - void listCellScripts (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) + + struct AddScriptsVisitor { - for (typename MWWorld::CellRefList::List::iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); ++iter) + AddScriptsVisitor(MWWorld::LocalScripts& scripts) + : mScripts(scripts) { - if (!iter->mBase->mScript.empty() && !iter->mData.isDeleted()) - { - localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell)); - } } - } + MWWorld::LocalScripts& mScripts; - // Adds scripts for items in containers (containers/npcs/creatures) - template - void listCellScriptsCont (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) - { - for (typename MWWorld::CellRefList::List::iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); ++iter) + bool operator()(const MWWorld::Ptr& ptr) { + if (ptr.getRefData().isDeleted()) + return true; + + std::string script = ptr.getClass().getScript(ptr); + + if (!script.empty()) + mScripts.add(script, ptr); + + return true; + } + }; - MWWorld::Ptr containerPtr (&*iter, cell); + struct AddContainerItemScriptsVisitor + { + AddContainerItemScriptsVisitor(MWWorld::LocalScripts& scripts) + : mScripts(scripts) + { + } + MWWorld::LocalScripts& mScripts; + bool operator()(const MWWorld::Ptr& containerPtr) + { MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); - for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3) + for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { - std::string script = it3->getClass().getScript(*it3); + std::string script = it->getClass().getScript(*it); if(script != "") { - MWWorld::Ptr item = *it3; - item.mCell = cell; - localScripts.add (script, item); + MWWorld::Ptr item = *it; + item.mCell = containerPtr.getCell(); + mScripts.add (script, item); } } + return true; } - } + }; + } MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {} -void MWWorld::LocalScripts::setIgnore (const Ptr& ptr) +void MWWorld::LocalScripts::setIgnore (const ConstPtr& ptr) { mIgnore = ptr; } @@ -116,26 +124,13 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) void MWWorld::LocalScripts::addCell (CellStore *cell) { - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScriptsCont (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScriptsCont (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScriptsCont (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); + AddScriptsVisitor addScriptsVisitor(*this); + cell->forEach(addScriptsVisitor); + + AddContainerItemScriptsVisitor addContainerItemScriptsVisitor(*this); + cell->forEachType(addContainerItemScriptsVisitor); + cell->forEachType(addContainerItemScriptsVisitor); + cell->forEachType(addContainerItemScriptsVisitor); } void MWWorld::LocalScripts::clear() diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 9d612cb33c..6ef4633a1d 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -17,14 +17,14 @@ namespace MWWorld { std::list > mScripts; std::list >::iterator mIter; - MWWorld::Ptr mIgnore; + MWWorld::ConstPtr mIgnore; const MWWorld::ESMStore& mStore; public: LocalScripts (const MWWorld::ESMStore& store); - void setIgnore (const Ptr& ptr); + void setIgnore (const ConstPtr& ptr); ///< Mark a single reference for ignoring during iteration over local scripts (will revoke /// previous ignores). @@ -52,7 +52,7 @@ namespace MWWorld void remove (RefData *ref); void remove (const Ptr& ptr); - ///< Remove script for given reference (ignored if reference does not have a scirpt listed). + ///< Remove script for given reference (ignored if reference does not have a script listed). }; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 6bfdd29860..19b66c3c93 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -205,6 +205,35 @@ namespace MWWorld return ptr.getClass().getNpcStats(ptr).getDrawState(); } + void Player::activate() + { + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + return; + + MWWorld::Ptr player = getPlayer(); + const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); + if (playerStats.isParalyzed() || playerStats.getKnockedDown()) + return; + + MWWorld::Ptr toActivate = MWBase::Environment::get().getWorld()->getFacedObject(); + + if (toActivate.isEmpty()) + return; + + if (toActivate.getClass().getName(toActivate) == "") // objects without name presented to user can never be activated + return; + + if (toActivate.getClass().isActor()) + { + MWMechanics::CreatureStats &stats = toActivate.getClass().getCreatureStats(toActivate); + + if (stats.getAiSequence().isInCombat() && !stats.isDead()) + return; + } + + MWBase::Environment::get().getWorld()->activate(toActivate, player); + } + bool Player::wasTeleported() const { return mTeleported; diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index f0ae13daa9..595dd89fc6 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -82,6 +82,9 @@ namespace MWWorld void setDrawState (MWMechanics::DrawState_ state); MWMechanics::DrawState_ getDrawState(); /// \todo constness + /// Activate the object under the crosshair, if any + void activate(); + bool getAutoMove() const; void setAutoMove (bool enable); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3e9f172786..439ac19ec9 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -25,6 +27,7 @@ #include "../mwrender/effectmanager.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" +#include "../mwrender/renderingmanager.hpp" #include "../mwsound/sound.hpp" @@ -34,23 +37,69 @@ namespace MWWorld { - ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, MWPhysics::PhysicsSystem* physics) + ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, + MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics) : mParent(parent) , mResourceSystem(resourceSystem) + , mRendering(rendering) , mPhysics(physics) { } - void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient) + /// Rotates an osg::PositionAttitudeTransform over time. + class RotateCallback : public osg::NodeCallback + { + public: + RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) + : mAxis(axis) + , mRotateSpeed(rotateSpeed) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::PositionAttitudeTransform* transform = static_cast(node); + + double time = nv->getFrameStamp()->getSimulationTime(); + + osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); + transform->setAttitude(orient); + + traverse(node, nv); + } + + private: + osg::Vec3f mAxis; + float mRotateSpeed; + }; + + + void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate) { state.mNode = new osg::PositionAttitudeTransform; state.mNode->setNodeMask(MWRender::Mask_Effect); state.mNode->setPosition(pos); state.mNode->setAttitude(orient); - mParent->addChild(state.mNode); - mResourceSystem->getSceneManager()->createInstance(model, state.mNode); + osg::Group* attachTo = state.mNode; + + if (rotate) + { + osg::ref_ptr rotateNode (new osg::PositionAttitudeTransform); + rotateNode->addUpdateCallback(new RotateCallback()); + state.mNode->addChild(rotateNode); + attachTo = rotateNode; + } + + mResourceSystem->getSceneManager()->createInstance(model, attachTo); + + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; + state.mNode->accept(disableFreezeOnCullVisitor); + + state.mNode->addCullCallback(new SceneUtil::LightListCallback); + + mParent->addChild(state.mNode); state.mEffectAnimationTime.reset(new MWRender::EffectAnimationTime); @@ -68,12 +117,7 @@ namespace MWWorld const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const osg::Vec3f& fallbackDirection) { - float height = 0; - - height += mPhysics->getHalfExtents(caster).z() * 2.f * 0.75f; // Spawn at 0.75 * ActorHeight - - osg::Vec3f pos(caster.getRefData().getPosition().asVec3()); - pos.z() += height; + osg::Vec3f pos = mPhysics->getPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible return; @@ -109,15 +153,15 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), model); MWWorld::Ptr ptr = ref.getPtr(); - createModel(state, ptr.getClass().getModel(ptr), pos, orient); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound = sndMgr->playManualSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + state.mSound = sndMgr->playSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); mMagicBolts.push_back(state); } - void ProjectileManager::launchProjectile(Ptr actor, Ptr projectile, const osg::Vec3f &pos, const osg::Quat &orient, Ptr bow, float speed, float attackStrength) + void ProjectileManager::launchProjectile(Ptr actor, ConstPtr projectile, const osg::Vec3f &pos, const osg::Quat &orient, Ptr bow, float speed, float attackStrength) { ProjectileState state; state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); @@ -130,7 +174,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); - createModel(state, ptr.getClass().getModel(ptr), pos, orient); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, false); mProjectiles.push_back(state); } @@ -196,7 +240,6 @@ namespace MWWorld MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, ESM::RT_Target, it->mSpellId, it->mSourceName); MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); - mParent->removeChild(it->mNode); it = mMagicBolts.erase(it); @@ -231,32 +274,38 @@ namespace MWWorld // TODO: use a proper btRigidBody / btGhostObject? MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, 0xff, MWPhysics::CollisionType_Projectile); - if (result.mHit) + bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); + if (result.mHit || underwater) { - 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()) + if (result.mHit) { - 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; + 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 = result.mHitObject; + + MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHitPos, it->mAttackStrength); } - if (caster.isEmpty()) - caster = result.mHitObject; - - MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHitPos, it->mAttackStrength); + if (underwater) + mRendering->emitWaterRipple(newPos); mParent->removeChild(it->mNode); - it = mProjectiles.erase(it); continue; } - else - ++it; + + ++it; } } @@ -345,7 +394,7 @@ namespace MWWorld return true; } - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation)); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false); mProjectiles.push_back(state); return true; @@ -376,11 +425,11 @@ namespace MWWorld return true; } - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation)); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound = sndMgr->playManualSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + state.mSound = sndMgr->playSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); state.mSoundId = esm.mSound; mMagicBolts.push_back(state); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 0aa2efded7..74d4c1dc53 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -35,6 +36,7 @@ namespace Resource namespace MWRender { class EffectAnimationTime; + class RenderingManager; } namespace MWWorld @@ -44,14 +46,14 @@ namespace MWWorld { public: ProjectileManager (osg::Group* parent, Resource::ResourceSystem* resourceSystem, - MWPhysics::PhysicsSystem* physics); + MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); - void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); void update(float dt); @@ -66,6 +68,7 @@ namespace MWWorld private: osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; + MWRender::RenderingManager* mRendering; MWPhysics::PhysicsSystem* mPhysics; struct State @@ -117,8 +120,11 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient); + void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate); void update (State& state, float duration); + + void operator=(const ProjectileManager&); + ProjectileManager(const ProjectileManager&); }; } diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 4d74c3c581..01e7be1d1d 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -52,3 +52,30 @@ MWWorld::Ptr::operator const void *() { return mRef; } + +// ------------------------------------------------------------------------------- + +const std::string &MWWorld::ConstPtr::getTypeName() const +{ + if(mRef != 0) + return mRef->mClass->getTypeName(); + throw std::runtime_error("Can't get type name from an empty object."); +} + +const MWWorld::LiveCellRefBase *MWWorld::ConstPtr::getBase() const +{ + if (!mRef) + throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); + + return mRef; +} + +const MWWorld::ContainerStore *MWWorld::ConstPtr::getContainerStore() const +{ + return mContainerStore; +} + +MWWorld::ConstPtr::operator const void *() +{ + return mRef; +} diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index c97d2e6d1e..d34f516a79 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -85,6 +85,87 @@ namespace MWWorld ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty }; + /// \brief Pointer to a const LiveCellRef + /// @note a Ptr can be implicitely converted to a ConstPtr, but you can not convert a ConstPtr to a Ptr. + class ConstPtr + { + public: + + const MWWorld::LiveCellRefBase *mRef; + const CellStore *mCell; + const ContainerStore *mContainerStore; + + public: + ConstPtr(const MWWorld::LiveCellRefBase *liveCellRef=0, const CellStore *cell=0) + : mRef(liveCellRef), mCell(cell), mContainerStore(0) + { + } + + ConstPtr(const MWWorld::Ptr& ptr) + : mRef(ptr.mRef), mCell(ptr.mCell), mContainerStore(ptr.mContainerStore) + { + } + + bool isEmpty() const + { + return mRef == 0; + } + + const std::string& getTypeName() const; + + const Class& getClass() const + { + if(mRef != 0) + return *(mRef->mClass); + throw std::runtime_error("Cannot get class of an empty object"); + } + + template + const MWWorld::LiveCellRef *get() const + { + const MWWorld::LiveCellRef *ref = dynamic_cast*>(mRef); + if(ref) return ref; + + std::stringstream str; + str<< "Bad LiveCellRef cast to "<mRef; + } + + const RefData& getRefData() const + { + assert(mRef); + return mRef->mData; + } + + const CellStore *getCell() const + { + assert(mCell); + return mCell; + } + + bool isInCell() const + { + return (mContainerStore == 0) && (mCell != 0); + } + + const ContainerStore *getContainerStore() const; + ///< May return a 0-pointer, if reference is not in a container. + + operator const void *(); + ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty + }; + inline bool operator== (const Ptr& left, const Ptr& right) { return left.mRef==right.mRef; @@ -114,6 +195,36 @@ namespace MWWorld { return !(left>right); } + + inline bool operator== (const ConstPtr& left, const ConstPtr& right) + { + return left.mRef==right.mRef; + } + + inline bool operator!= (const ConstPtr& left, const ConstPtr& right) + { + return !(left==right); + } + + inline bool operator< (const ConstPtr& left, const ConstPtr& right) + { + return left.mRef= (const ConstPtr& left, const ConstPtr& right) + { + return !(left (const ConstPtr& left, const ConstPtr& right) + { + return rightright); + } } #endif diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 88a6fa3533..1da6b53fa4 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -17,9 +17,8 @@ namespace MWWorld mEnabled = refData.mEnabled; mCount = refData.mCount; mPosition = refData.mPosition; - mLocalRotation = refData.mLocalRotation; mChanged = refData.mChanged; - mDeleted = refData.mDeleted; + mDeletedByContentFile = refData.mDeletedByContentFile; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -33,37 +32,31 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mDeleted(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) { for (int i=0; i<3; ++i) { - mLocalRotation.rot[i] = 0; mPosition.pos[i] = 0; mPosition.rot[i] = 0; } } RefData::RefData (const ESM::CellRef& cellRef) - : mBaseNode(0), mDeleted(false), mEnabled (true), + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), mChanged(false) // Loading from ESM/ESP files -> assume unchanged { - mLocalRotation.rot[0]=0; - mLocalRotation.rot[1]=0; - mLocalRotation.rot[2]=0; } - RefData::RefData (const ESM::ObjectState& objectState) - : mBaseNode(0), mDeleted(false), + RefData::RefData (const ESM::ObjectState& objectState, bool deletedByContentFile) + : mBaseNode(0), mDeletedByContentFile(deletedByContentFile), mEnabled (objectState.mEnabled != 0), mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), mChanged(true) // Loading from a savegame -> assume changed { - for (int i=0; i<3; ++i) - mLocalRotation.rot[i] = objectState.mLocalRotation[i]; } RefData::RefData (const RefData& refData) @@ -87,9 +80,6 @@ namespace MWWorld objectState.mEnabled = mEnabled; objectState.mCount = mCount; objectState.mPosition = mPosition; - - for (int i=0; i<3; ++i) - objectState.mLocalRotation[i] = mLocalRotation.rot[i]; } RefData& RefData::operator= (const RefData& refData) @@ -118,12 +108,17 @@ namespace MWWorld {} } - void RefData::setBaseNode(osg::PositionAttitudeTransform *base) + void RefData::setBaseNode(SceneUtil::PositionAttitudeTransform *base) { mBaseNode = base; } - osg::PositionAttitudeTransform* RefData::getBaseNode() + SceneUtil::PositionAttitudeTransform* RefData::getBaseNode() + { + return mBaseNode; + } + + const SceneUtil::PositionAttitudeTransform* RefData::getBaseNode() const { return mBaseNode; } @@ -149,19 +144,19 @@ namespace MWWorld mCount = count; } - void RefData::setDeleted(bool deleted) + void RefData::setDeletedByContentFile(bool deleted) { - mDeleted = deleted; + mDeletedByContentFile = deleted; } bool RefData::isDeleted() const { - return mDeleted || mCount == 0; + return mDeletedByContentFile || mCount == 0; } bool RefData::isDeletedByContentFile() const { - return mDeleted; + return mDeletedByContentFile; } MWScript::Locals& RefData::getLocals() @@ -176,14 +171,20 @@ namespace MWWorld void RefData::enable() { - mChanged = !mEnabled; - mEnabled = true; + if (!mEnabled) + { + mChanged = true; + mEnabled = true; + } } void RefData::disable() { - mChanged = mEnabled; - mEnabled = false; + if (mEnabled) + { + mChanged = true; + mEnabled = false; + } } void RefData::setPosition(const ESM::Position& pos) @@ -192,22 +193,11 @@ namespace MWWorld mPosition = pos; } - const ESM::Position& RefData::getPosition() + const ESM::Position& RefData::getPosition() const { return mPosition; } - void RefData::setLocalRotation(const LocalRotation& rot) - { - mChanged = true; - mLocalRotation = rot; - } - - const LocalRotation& RefData::getLocalRotation() - { - return mLocalRotation; - } - void RefData::setCustomData (CustomData *data) { mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed @@ -220,6 +210,11 @@ namespace MWWorld return mCustomData; } + const CustomData *RefData::getCustomData() const + { + return mCustomData; + } + bool RefData::hasChanged() const { return mChanged; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 4075f62ce3..28d2dad4c9 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -7,7 +7,7 @@ #include -namespace osg +namespace SceneUtil { class PositionAttitudeTransform; } @@ -21,27 +21,26 @@ namespace ESM namespace MWWorld { - struct LocalRotation{ - float rot[3]; - }; class CustomData; class RefData { - osg::PositionAttitudeTransform* mBaseNode; + SceneUtil::PositionAttitudeTransform* mBaseNode; MWScript::Locals mLocals; - bool mDeleted; // separate delete flag used for deletion by a content file + /// separate delete flag used for deletion by a content file + /// @note not stored in the save game file. + bool mDeletedByContentFile; + bool mEnabled; - int mCount; // 0: deleted + /// 0: deleted + int mCount; ESM::Position mPosition; - LocalRotation mLocalRotation; - CustomData *mCustomData; void copy (const RefData& refData); @@ -56,10 +55,10 @@ namespace MWWorld /// @param cellRef Used to copy constant data such as position into this class where it can /// be altered without affecting the original data. This makes it possible - /// to reset the position as the orignal data is still held in the CellRef + /// to reset the position as the original data is still held in the CellRef RefData (const ESM::CellRef& cellRef); - RefData (const ESM::ObjectState& objectState); + RefData (const ESM::ObjectState& objectState, bool deletedByContentFile); ///< Ignores local variables and custom data (not enough context available here to /// perform these operations). @@ -74,10 +73,13 @@ namespace MWWorld RefData& operator= (const RefData& refData); /// Return base node (can be a null pointer). - osg::PositionAttitudeTransform* getBaseNode(); + SceneUtil::PositionAttitudeTransform* getBaseNode(); + + /// Return base node (can be a null pointer). + const SceneUtil::PositionAttitudeTransform* getBaseNode() const; /// Set base node (can be a null pointer). - void setBaseNode (osg::PositionAttitudeTransform* base); + void setBaseNode (SceneUtil::PositionAttitudeTransform* base); int getCount() const; @@ -92,7 +94,7 @@ namespace MWWorld /// This flag is only used for content stack loading and will not be stored in the savegame. /// If the object was deleted by gameplay, then use setCount(0) instead. - void setDeleted(bool deleted); + void setDeletedByContentFile(bool deleted); /// Returns true if the object was either deleted by the content file or by gameplay. bool isDeleted() const; @@ -108,10 +110,7 @@ namespace MWWorld void disable(); void setPosition (const ESM::Position& pos); - const ESM::Position& getPosition(); - - void setLocalRotation (const LocalRotation& rotation); - const LocalRotation& getLocalRotation(); + const ESM::Position& getPosition() const; void setCustomData (CustomData *data); ///< Set custom data (potentially replacing old custom data). The ownership of \a data is @@ -120,6 +119,8 @@ namespace MWWorld CustomData *getCustomData(); ///< May return a 0-pointer. The ownership of the return data object is not transferred. + const CustomData *getCustomData() const; + bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? }; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 459b3b6ca0..15b3c057bc 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -22,7 +22,7 @@ #include "localscripts.hpp" #include "esmstore.hpp" #include "class.hpp" -#include "cellfunctors.hpp" +#include "cellvisitors.hpp" #include "cellstore.hpp" namespace @@ -32,7 +32,7 @@ namespace MWRender::RenderingManager& rendering) { std::string model = Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), rendering.getResourceSystem()->getVFS()); - std::string id = ptr.getClass().getId(ptr); + std::string id = ptr.getCellRef().getRefId(); if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player ptr.getClass().insertObjectRendering(ptr, model, rendering); @@ -42,25 +42,24 @@ namespace rendering.addWaterRippleEmitter(ptr); } - void updateObjectLocalRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) + void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, + MWRender::RenderingManager& rendering, bool inverseRotationOrder) { if (ptr.getRefData().getBaseNode() != NULL) { osg::Quat worldRotQuat(ptr.getRefData().getPosition().rot[2], osg::Vec3(0,0,-1)); if (!ptr.getClass().isActor()) - worldRotQuat = worldRotQuat * osg::Quat(ptr.getRefData().getPosition().rot[1], osg::Vec3(0,-1,0)) * - osg::Quat(ptr.getRefData().getPosition().rot[0], osg::Vec3(-1,0,0)); - - float x = ptr.getRefData().getLocalRotation().rot[0]; - float y = ptr.getRefData().getLocalRotation().rot[1]; - float z = ptr.getRefData().getLocalRotation().rot[2]; - - osg::Quat rot(z, osg::Vec3(0,0,-1)); - if (!ptr.getClass().isActor()) - rot = rot * osg::Quat(y, osg::Vec3(0,-1,0)) * osg::Quat(x, osg::Vec3(-1,0,0)); + { + float xr = ptr.getRefData().getPosition().rot[0]; + float yr = ptr.getRefData().getPosition().rot[1]; + if (!inverseRotationOrder) + worldRotQuat = worldRotQuat * osg::Quat(yr, osg::Vec3(0,-1,0)) * + osg::Quat(xr, osg::Vec3(-1,0,0)); + else + worldRotQuat = osg::Quat(xr, osg::Vec3(-1,0,0)) * osg::Quat(yr, osg::Vec3(0,-1,0)) * worldRotQuat; + } - rendering.rotateObject(ptr, rot * worldRotQuat); + rendering.rotateObject(ptr, worldRotQuat); physics.updateRotation(ptr); } } @@ -72,12 +71,14 @@ namespace { float scale = ptr.getCellRef().getScale(); osg::Vec3f scaleVec (scale, scale, scale); - ptr.getClass().adjustScale(ptr, scaleVec); + ptr.getClass().adjustScale(ptr, scaleVec, true); rendering.scaleObject(ptr, scaleVec); + + physics.updateScale(ptr); } } - struct InsertFunctor + struct InsertVisitor { MWWorld::CellStore& mCell; bool mRescale; @@ -85,13 +86,13 @@ namespace MWPhysics::PhysicsSystem& mPhysics; MWRender::RenderingManager& mRendering; - InsertFunctor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, + InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering); bool operator() (const MWWorld::Ptr& ptr); }; - InsertFunctor::InsertFunctor (MWWorld::CellStore& cell, bool rescale, + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) : mCell (cell), mRescale (rescale), mLoadingListener (loadingListener), @@ -99,7 +100,7 @@ namespace mRendering (rendering) {} - bool InsertFunctor::operator() (const MWWorld::Ptr& ptr) + bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) { if (mRescale) { @@ -114,9 +115,7 @@ namespace try { addObject(ptr, mPhysics, mRendering); - updateObjectLocalRotation(ptr, mPhysics, mRendering); - updateObjectScale(ptr, mPhysics, mRendering); - ptr.getClass().adjustPosition (ptr, false); + updateObjectRotation(ptr, mPhysics, mRendering, false); } catch (const std::exception& e) { @@ -129,15 +128,26 @@ namespace return true; } + + struct AdjustPositionVisitor + { + bool operator() (const MWWorld::Ptr& ptr) + { + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + ptr.getClass().adjustPosition (ptr, false); + return true; + } + }; + } namespace MWWorld { - void Scene::updateObjectLocalRotation (const Ptr& ptr) + void Scene::updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder) { - ::updateObjectLocalRotation(ptr, *mPhysics, mRendering); + ::updateObjectRotation(ptr, *mPhysics, mRendering, inverseRotationOrder); } void Scene::updateObjectScale(const Ptr &ptr) @@ -196,11 +206,11 @@ namespace MWWorld void Scene::unloadCell (CellStoreCollection::iterator iter) { std::cout << "Unloading cell\n"; - ListAndResetObjects functor; + ListAndResetObjectsVisitor visitor; - (*iter)->forEach(functor); - for (std::vector::const_iterator iter2 (functor.mObjects.begin()); - iter2!=functor.mObjects.end(); ++iter2) + (*iter)->forEach(visitor); + for (std::vector::const_iterator iter2 (visitor.mObjects.begin()); + iter2!=visitor.mObjects.end(); ++iter2) { mPhysics->remove(*iter2); } @@ -265,7 +275,7 @@ namespace MWWorld mRendering.addCell(cell); bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->isExterior() ? -1.f : cell->getWaterLevel(); + float waterLevel = cell->getWaterLevel(); mRendering.setWaterEnabled(waterEnabled); if (waterEnabled) { @@ -403,6 +413,8 @@ namespace MWWorld // Delay the map update until scripts have been given a chance to run. // If we don't do this, objects that should be disabled will still appear on the map. mNeedMapUpdate = true; + + mRendering.getResourceSystem()->clearCache(); } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) @@ -419,9 +431,9 @@ namespace MWWorld if (adjustPlayerPos) { world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]); - float x = osg::RadiansToDegrees(pos.rot[0]); - float y = osg::RadiansToDegrees(pos.rot[1]); - float z = osg::RadiansToDegrees(pos.rot[2]); + float x = pos.rot[0]; + float y = pos.rot[1]; + float z = pos.rot[2]; world->rotateObject(player, x, y, z); player.getClass().adjustPosition(player, true); @@ -476,9 +488,9 @@ namespace MWWorld MWBase::World *world = MWBase::Environment::get().getWorld(); world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]); - float x = osg::RadiansToDegrees(position.rot[0]); - float y = osg::RadiansToDegrees(position.rot[1]); - float z = osg::RadiansToDegrees(position.rot[2]); + float x = position.rot[0]; + float y = position.rot[1]; + float z = position.rot[2]; world->rotateObject(world->getPlayerPtr(), x, y, z); world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); @@ -518,6 +530,8 @@ namespace MWWorld // Delay the map update until scripts have been given a chance to run. // If we don't do this, objects that should be disabled will still appear on the map. mNeedMapUpdate = true; + + mRendering.getResourceSystem()->clearCache(); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) @@ -547,8 +561,12 @@ namespace MWWorld void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener) { - InsertFunctor functor (cell, rescale, *loadingListener, *mPhysics, mRendering); - cell.forEach (functor); + InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering); + cell.forEach (insertVisitor); + + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + AdjustPositionVisitor adjustPosVisitor; + cell.forEach (adjustPosVisitor); } void Scene::addObjectToScene (const Ptr& ptr) @@ -556,7 +574,7 @@ namespace MWWorld try { addObject(ptr, *mPhysics, mRendering); - MWBase::Environment::get().getWorld()->rotateObject(ptr, 0, 0, 0, true); + updateObjectRotation(ptr, false); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); } catch (std::exception& e) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index ca6ed83b91..439c8d72c3 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -107,7 +107,7 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. - void updateObjectLocalRotation (const Ptr& ptr); + void updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder); void updateObjectScale(const Ptr& ptr); bool isCellActive(const CellStore &cell); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 7a471eaa4b..d1ba0ef0b5 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -42,6 +42,10 @@ namespace namespace MWWorld { + RecordId::RecordId(const std::string &id, bool isDeleted) + : mId(id), mIsDeleted(isDeleted) + {} + template IndexedStore::IndexedStore() { @@ -60,7 +64,9 @@ namespace MWWorld void IndexedStore::load(ESM::ESMReader &esm) { T record; - record.load(esm); + bool isDeleted = false; + + record.load(esm, isDeleted); // Try to overwrite existing record std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); @@ -178,16 +184,21 @@ namespace MWWorld return ptr; } template - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { - std::string idLower = Misc::StringUtils::lowerCase(id); + T record; + bool isDeleted = false; - std::pair inserted = mStatic.insert(std::make_pair(idLower, T())); + record.load(esm, isDeleted); + Misc::StringUtils::lowerCaseInPlace(record.mId); + + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); if (inserted.second) mShared.push_back(&inserted.first->second); + else + inserted.first->second = record; - inserted.first->second.mId = idLower; - inserted.first->second.load(esm); + return RecordId(record.mId, isDeleted); } template void Store::setUp() @@ -309,20 +320,21 @@ namespace MWWorld ++iter) { writer.startRecord (T::sRecordId); - writer.writeHNString ("NAME", iter->second.mId); iter->second.save (writer); writer.endRecord (T::sRecordId); } } template - void Store::read(ESM::ESMReader& reader, const std::string& id) + RecordId Store::read(ESM::ESMReader& reader) { T record; - record.mId = id; - record.load (reader); + bool isDeleted = false; + + record.load (reader, isDeleted); insert (record); - } + return RecordId(record.mId, isDeleted); + } // LandTexture //========================================================================= @@ -339,8 +351,9 @@ namespace MWWorld assert(plugin < mStatic.size()); const LandTextureList <exl = mStatic[plugin]; - assert(index < ltexl.size()); - return <exl.at(index); + if (index >= ltexl.size()) + return NULL; + return <exl[index]; } const ESM::LandTexture *Store::find(size_t index, size_t plugin) const { @@ -361,26 +374,27 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].size(); } - void Store::load(ESM::ESMReader &esm, const std::string &id, size_t plugin) + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) { ESM::LandTexture lt; - lt.load(esm); - lt.mId = id; + bool isDeleted = false; + + lt.load(esm, isDeleted); + + assert(plugin < mStatic.size()); - // Make sure we have room for the structure - if (plugin >= mStatic.size()) { - mStatic.resize(plugin+1); - } LandTextureList <exl = mStatic[plugin]; if(lt.mIndex + 1 > (int)ltexl.size()) ltexl.resize(lt.mIndex+1); // Store it ltexl[lt.mIndex] = lt; + + return RecordId(lt.mId, isDeleted); } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { - load(esm, id, esm.getIndex()); + return load(esm, esm.getIndex()); } Store::iterator Store::begin(size_t plugin) const { @@ -392,7 +406,11 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].end(); } - + void Store::resize(size_t num) + { + if (mStatic.size() < num) + mStatic.resize(num); + } // Land //========================================================================= @@ -440,10 +458,12 @@ namespace MWWorld } return ptr; } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { ESM::Land *ptr = new ESM::Land(); - ptr->load(esm); + bool isDeleted = false; + + ptr->load(esm, isDeleted); // Same area defined in multiple plugins? -> last plugin wins // Can't use search() because we aren't sorted yet - is there any other way to speed this up? @@ -458,6 +478,8 @@ namespace MWWorld } mStatic.push_back(ptr); + + return RecordId("", isDeleted); } void Store::setUp() { @@ -600,7 +622,7 @@ namespace MWWorld mSharedExt.push_back(&(it->second)); } } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, // and we merge all this data into one Cell object. However, we can't simply search for the cell id, @@ -608,13 +630,13 @@ namespace MWWorld // are not available until both cells have been loaded at least partially! // All cells have a name record, even nameless exterior cells. - std::string idLower = Misc::StringUtils::lowerCase(id); ESM::Cell cell; - cell.mName = id; + bool isDeleted = false; - // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // Load the (x,y) coordinates of the cell, if it is an exterior cell, // so we can find the cell we need to merge with - cell.loadData(esm); + cell.loadNameAndData(esm, isDeleted); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); if(cell.mData.mFlags & ESM::Cell::Interior) { @@ -682,6 +704,8 @@ namespace MWWorld mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; } } + + return RecordId(cell.mName, isDeleted); } Store::iterator Store::intBegin() const { @@ -837,10 +861,12 @@ namespace MWWorld { mCells = &cells; } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { ESM::Pathgrid pathgrid; - pathgrid.load(esm); + bool isDeleted = false; + + pathgrid.load(esm, isDeleted); // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. @@ -862,6 +888,8 @@ namespace MWWorld if (!ret.second) ret.first->second = pathgrid; } + + return RecordId("", isDeleted); } size_t Store::getSize() const { @@ -1013,51 +1041,29 @@ namespace MWWorld } template <> - void Store::load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); + inline RecordId Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed + ESM::Dialogue dialogue; + bool isDeleted = false; - std::map::iterator it = mStatic.find(idLower); - if (it == mStatic.end()) { - it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first; - it->second.mId = id; // don't smash case here, as this line is printed - } + dialogue.loadId(esm); - it->second.load(esm); - } - - - // Script - //========================================================================= - - template <> - void Store::load(ESM::ESMReader &esm, const std::string &id) { - ESM::Script scpt; - scpt.load(esm); - Misc::StringUtils::toLower(scpt.mId); - - std::pair inserted = mStatic.insert(std::make_pair(scpt.mId, scpt)); - if (inserted.second) - mShared.push_back(&inserted.first->second); + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + dialogue.loadData(esm, isDeleted); + mStatic.insert(std::make_pair(idLower, dialogue)); + } else - inserted.first->second = scpt; - } - - - // StartScript - //========================================================================= + { + found->second.loadData(esm, isDeleted); + dialogue = found->second; + } - template <> - void Store::load(ESM::ESMReader &esm, const std::string &id) - { - ESM::StartScript s; - s.load(esm); - s.mId = Misc::StringUtils::toLower(s.mId); - std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); - if (inserted.second) - mShared.push_back(&inserted.first->second); - else - inserted.first->second = s; + return RecordId(dialogue.mId, isDeleted); } + } template class MWWorld::Store; @@ -1082,7 +1088,7 @@ template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 47c87f742d..617c3552af 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -19,23 +19,34 @@ namespace Loading namespace MWWorld { - struct StoreBase + struct RecordId { + std::string mId; + bool mIsDeleted; + + RecordId(const std::string &id = "", bool isDeleted = false); + }; + + class StoreBase + { + public: virtual ~StoreBase() {} virtual void setUp() {} + + /// List identifiers of records contained in this Store (case-smashed). No-op for Stores that don't use string IDs. virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + virtual RecordId load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual void read (ESM::ESMReader& reader, const std::string& id) {} + virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } ///< Read into dynamic storage }; @@ -180,9 +191,9 @@ namespace MWWorld bool erase(const std::string &id); bool erase(const T &item); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; - void read(ESM::ESMReader& reader, const std::string& id); + RecordId read(ESM::ESMReader& reader); }; template <> @@ -202,11 +213,14 @@ namespace MWWorld const ESM::LandTexture *search(size_t index, size_t plugin) const; const ESM::LandTexture *find(size_t index, size_t plugin) const; + /// Resize the internal store to hold at least \a num plugins. + void resize(size_t num); + size_t getSize() const; size_t getSize(size_t plugin) const; - void load(ESM::ESMReader &esm, const std::string &id, size_t plugin); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm, size_t plugin); + RecordId load(ESM::ESMReader &esm); iterator begin(size_t plugin) const; iterator end(size_t plugin) const; @@ -231,7 +245,7 @@ namespace MWWorld ESM::Land *search(int x, int y) const; ESM::Land *find(int x, int y) const; - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); void setUp(); }; @@ -281,7 +295,7 @@ namespace MWWorld void setUp(); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); iterator intBegin() const; iterator intEnd() const; @@ -323,7 +337,7 @@ namespace MWWorld Store(); void setCells(Store& cells); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); size_t getSize() const; void setUp(); @@ -338,14 +352,16 @@ namespace MWWorld template <> - struct Store : public IndexedStore + class Store : public IndexedStore { + public: Store(); }; template <> - struct Store : public IndexedStore + class Store : public IndexedStore { + public: Store(); }; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5008d8fbfc..5ce3d5c2fe 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -31,47 +31,104 @@ namespace { static const int invalidWeatherID = -1; + // linear interpolate between x and y based on factor. float lerp (float x, float y, float factor) { return x * (1-factor) + y * factor; } - + // linear interpolate between x and y based on factor. osg::Vec4f lerp (const osg::Vec4f& x, const osg::Vec4f& y, float factor) { return x * (1-factor) + y * factor; } } +template +T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const +{ + // TODO: use pre/post sunset/sunrise time values in [Weather] section + + // night + if (gameHour <= timeSettings.mNightEnd || gameHour >= timeSettings.mNightStart + 1) + return mNightValue; + // sunrise + else if (gameHour >= timeSettings.mNightEnd && gameHour <= timeSettings.mDayStart + 1) + { + if (gameHour <= timeSettings.mSunriseTime) + { + // fade in + float advance = timeSettings.mSunriseTime - gameHour; + float factor = advance / 0.5f; + return lerp(mSunriseValue, mNightValue, factor); + } + else + { + // fade out + float advance = gameHour - timeSettings.mSunriseTime; + float factor = advance / 3.f; + return lerp(mSunriseValue, mDayValue, factor); + } + } + // day + else if (gameHour >= timeSettings.mDayStart + 1 && gameHour <= timeSettings.mDayEnd - 1) + return mDayValue; + // sunset + else if (gameHour >= timeSettings.mDayEnd - 1 && gameHour <= timeSettings.mNightStart + 1) + { + if (gameHour <= timeSettings.mDayEnd + 1) + { + // fade in + float advance = (timeSettings.mDayEnd + 1) - gameHour; + float factor = (advance / 2); + return lerp(mSunsetValue, mDayValue, factor); + } + else + { + // fade out + float advance = gameHour - (timeSettings.mDayEnd + 1); + float factor = advance / 2.f; + return lerp(mSunsetValue, mNightValue, factor); + } + } + // shut up compiler + return T(); +} + + + +template class TimeOfDayInterpolator; +template class TimeOfDayInterpolator; + Weather::Weather(const std::string& name, const MWWorld::Fallback& fallback, float stormWindSpeed, float rainSpeed, - const std::string& ambientLoopSoundID, const std::string& particleEffect) : mCloudTexture(fallback.getFallbackString("Weather_" + name + "_Cloud_Texture")) - , mSkySunriseColor(fallback.getFallbackColour("Weather_" + name +"_Sky_Sunrise_Color")) - , mSkyDayColor(fallback.getFallbackColour("Weather_" + name + "_Sky_Day_Color")) - , mSkySunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sky_Sunset_Color")) - , mSkyNightColor(fallback.getFallbackColour("Weather_" + name + "_Sky_Night_Color")) - , mFogSunriseColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Sunrise_Color")) - , mFogDayColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Day_Color")) - , mFogSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Sunset_Color")) - , mFogNightColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Night_Color")) - , mAmbientSunriseColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunrise_Color")) - , mAmbientDayColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Day_Color")) - , mAmbientSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunset_Color")) - , mAmbientNightColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Night_Color")) - , mSunSunriseColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Sunrise_Color")) - , mSunDayColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Day_Color")) - , mSunSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Sunset_Color")) - , mSunNightColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Night_Color")) - , mLandFogDayDepth(fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth")) - , mLandFogNightDepth(fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Night_Depth")) + , mSkyColor(fallback.getFallbackColour("Weather_" + name +"_Sky_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sky_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sky_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sky_Night_Color")) + , mFogColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Fog_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Fog_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Fog_Night_Color")) + , mAmbientColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Ambient_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Ambient_Night_Color")) + , mSunColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sun_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sun_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sun_Night_Color")) + , mLandFogDepth(fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Night_Depth")) , mSunDiscSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) , mWindSpeed(fallback.getFallbackFloat("Weather_" + name + "_Wind_Speed")) , mCloudSpeed(fallback.getFallbackFloat("Weather_" + name + "_Cloud_Speed")) , mGlareView(fallback.getFallbackFloat("Weather_" + name + "_Glare_View")) - , mAmbientLoopSoundID(ambientLoopSoundID) , mIsStorm(mWindSpeed > stormWindSpeed) , mRainSpeed(rainSpeed) , mRainFrequency(fallback.getFallbackFloat("Weather_" + name + "_Rain_Entrance_Speed")) @@ -89,6 +146,21 @@ Weather::Weather(const std::string& name, mThunderSoundID[1] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_1"); mThunderSoundID[2] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_2"); mThunderSoundID[3] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_3"); + + // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time. + + if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect + { + mAmbientLoopSoundID = fallback.getFallbackString("Weather_" + name + "_Rain_Loop_Sound_ID"); + if (mAmbientLoopSoundID.empty()) // default to "rain" if not set + mAmbientLoopSoundID = "rain"; + } + else + mAmbientLoopSoundID = fallback.getFallbackString("Weather_" + name + "_Ambient_Loop_Sound_ID"); + + if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) + mAmbientLoopSoundID.clear(); + /* Unhandled: Rain Diameter=600 ? @@ -432,12 +504,13 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWo , mSunriseDuration(fallback.getFallbackFloat("Weather_Sunrise_Duration")) , mSunsetDuration(fallback.getFallbackFloat("Weather_Sunset_Duration")) , mSunPreSunsetTime(fallback.getFallbackFloat("Weather_Sun_Pre-Sunset_Time")) - , mNightStart(mSunsetTime + mSunsetDuration) - , mNightEnd(mSunriseTime - 0.5f) - , mDayStart(mSunriseTime + mSunriseDuration) - , mDayEnd(mSunsetTime) + , mNightFade(0, 0, 0, 1) , mHoursBetweenWeatherChanges(fallback.getFallbackFloat("Weather_Hours_Between_Weather_Changes")) , mRainSpeed(fallback.getFallbackFloat("Weather_Precip_Gravity")) + , mUnderwaterFog(fallback.getFallbackFloat("Water_UnderwaterSunriseFog"), + fallback.getFallbackFloat("Water_UnderwaterDayFog"), + fallback.getFallbackFloat("Water_UnderwaterSunsetFog"), + fallback.getFallbackFloat("Water_UnderwaterNightFog")) , mWeatherSettings() , mMasser("Masser", fallback) , mSecunda("Secunda", fallback) @@ -457,17 +530,23 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWo , mAmbientSound() , mPlayingSoundID() { + mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; + mTimeSettings.mNightEnd = mSunriseTime - 0.5f; + mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; + mTimeSettings.mDayEnd = mSunsetTime; + mTimeSettings.mSunriseTime = mSunriseTime; + mWeatherSettings.reserve(10); addWeather("Clear", fallback); // 0 addWeather("Cloudy", fallback); // 1 addWeather("Foggy", fallback); // 2 addWeather("Overcast", fallback); // 3 - addWeather("Rain", fallback, "rain"); // 4 - addWeather("Thunderstorm", fallback, "rain heavy"); // 5 - addWeather("Ashstorm", fallback, "ashstorm", "meshes\\ashcloud.nif"); // 6 - addWeather("Blight", fallback, "blight", "meshes\\blightcloud.nif"); // 7 - addWeather("Snow", fallback, "", "meshes\\snow.nif"); // 8 - addWeather("Blizzard", fallback, "BM Blizzard", "meshes\\blizzard.nif"); // 9 + addWeather("Rain", fallback); // 4 + addWeather("Thunderstorm", fallback); // 5 + addWeather("Ashstorm", fallback, "meshes\\ashcloud.nif"); // 6 + addWeather("Blight", fallback, "meshes\\blightcloud.nif"); // 7 + addWeather("Snow", fallback, "meshes\\snow.nif"); // 8 + addWeather("Blizzard", fallback, "meshes\\blizzard.nif"); // 9 Store::iterator it = store.get().begin(); for(; it != store.get().end(); ++it) @@ -544,11 +623,11 @@ void WeatherManager::playerTeleported() void WeatherManager::update(float duration, bool paused) { - MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::ConstPtr player = MWMechanics::getPlayer(); MWBase::World& world = *MWBase::Environment::get().getWorld(); TimeStamp time = world.getTimeStamp(); - if(!paused) + if(!paused || mFastForward) { // Add new transitions when either the player's current external region changes. std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); @@ -589,7 +668,7 @@ void WeatherManager::update(float duration, bool paused) } // disable sun during night - if (time.getHour() >= mNightStart || time.getHour() <= mSunriseTime) + if (time.getHour() >= mTimeSettings.mNightStart || time.getHour() <= mSunriseTime) mRendering.getSkyManager()->sunDisable(); else mRendering.getSkyManager()->sunEnable(); @@ -600,10 +679,10 @@ void WeatherManager::update(float duration, bool paused) { // Shift times into a 24-hour window beginning at mSunriseTime... float adjustedHour = time.getHour(); - float adjustedNightStart = mNightStart; + float adjustedNightStart = mTimeSettings.mNightStart; if ( time.getHour() < mSunriseTime ) adjustedHour += 24.f; - if ( mNightStart < mSunriseTime ) + if ( mTimeSettings.mNightStart < mSunriseTime ) adjustedNightStart += 24.f; const bool is_night = adjustedHour >= adjustedNightStart; @@ -614,7 +693,7 @@ void WeatherManager::update(float duration, bool paused) if ( !is_night ) { theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration; } else { - theta = M_PI * (adjustedHour - adjustedNightStart) / nightDuration; + theta = M_PI * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); } osg::Vec3f final( @@ -624,6 +703,8 @@ void WeatherManager::update(float duration, bool paused) mRendering.setSunDirection( final * -1 ); } + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings); + float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) mRendering.getSkyManager()->setGlareTimeOfDayFade(0); @@ -635,7 +716,7 @@ void WeatherManager::update(float duration, bool paused) mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); - mRendering.configureFog(mResult.mFogDepth, mResult.mFogColor); + mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); mRendering.setSunColour(mResult.mSunColor); @@ -657,11 +738,9 @@ void WeatherManager::update(float duration, bool paused) void WeatherManager::stopSounds() { if (mAmbientSound.get()) - { MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound.reset(); - mPlayingSoundID.clear(); - } + mAmbientSound.reset(); + mPlayingSoundID.clear(); } float WeatherManager::getWindSpeed() const @@ -697,7 +776,7 @@ bool WeatherManager::isDark() const TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); - return exterior && (time.getHour() < mSunriseTime || time.getHour() > mNightStart - 1); + return exterior && (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart - 1); } void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) @@ -784,12 +863,11 @@ void WeatherManager::clear() inline void WeatherManager::addWeather(const std::string& name, const MWWorld::Fallback& fallback, - const std::string& ambientLoopSoundID, const std::string& particleEffect) { static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); - Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, ambientLoopSoundID, particleEffect); + Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, particleEffect); mWeatherSettings.push_back(weather); } @@ -807,7 +885,7 @@ inline void WeatherManager::importRegions() inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region) { // If the region is current, then add a weather transition for it. - MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::ConstPtr player = MWMechanics::getPlayer(); if(player.isInCell()) { std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); @@ -975,81 +1053,14 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mNight = (gameHour < mSunriseTime || gameHour > mNightStart - 1); - - mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; - - // TODO: use pre/post sunset/sunrise time values in [Weather] section - // night - if (gameHour <= mNightEnd || gameHour >= mNightStart + 1) - { - mResult.mFogColor = current.mFogNightColor; - mResult.mAmbientColor = current.mAmbientNightColor; - mResult.mSunColor = current.mSunNightColor; - mResult.mSkyColor = current.mSkyNightColor; - mResult.mNightFade = 1.f; - } + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart - 1); - // sunrise - else if (gameHour >= mNightEnd && gameHour <= mDayStart + 1) - { - if (gameHour <= mSunriseTime) - { - // fade in - float advance = mSunriseTime - gameHour; - float factor = advance / 0.5f; - mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor); - mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunNightColor, factor); - mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor); - mResult.mNightFade = factor; - } - else //if (gameHour >= 6) - { - // fade out - float advance = gameHour - mSunriseTime; - float factor = advance / 3.f; - mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor); - mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunDayColor, factor); - mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyDayColor, factor); - } - } - - // day - else if (gameHour >= mDayStart + 1 && gameHour <= mDayEnd - 1) - { - mResult.mFogColor = current.mFogDayColor; - mResult.mAmbientColor = current.mAmbientDayColor; - mResult.mSunColor = current.mSunDayColor; - mResult.mSkyColor = current.mSkyDayColor; - } - - // sunset - else if (gameHour >= mDayEnd - 1 && gameHour <= mNightStart + 1) - { - if (gameHour <= mDayEnd + 1) - { - // fade in - float advance = (mDayEnd + 1) - gameHour; - float factor = (advance / 2); - mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor); - mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor); - mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor); - } - else //if (gameHour >= 19) - { - // fade out - float advance = gameHour - (mDayEnd + 1); - float factor = advance / 2.f; - mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor); - mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunNightColor, factor); - mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyNightColor, factor); - mResult.mNightFade = factor; - } - } + mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings); + mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings); + mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings); + mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings); + mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings); + mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings); if (gameHour >= mSunsetTime - mSunPreSunsetTime) { diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 7ce7c1bf84..a5627a507c 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -34,6 +34,33 @@ namespace MWWorld class Fallback; class TimeStamp; + + struct TimeOfDaySettings + { + float mNightStart; + float mNightEnd; + float mDayStart; + float mDayEnd; + float mSunriseTime; + }; + + /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. + /// The template value could be a floating point number, or a color. + template + class TimeOfDayInterpolator + { + public: + TimeOfDayInterpolator(const T& sunrise, const T& day, const T& sunset, const T& night) + : mSunriseValue(sunrise), mDayValue(day), mSunsetValue(sunset), mNightValue(night) + { + } + + T getValue (const float gameHour, const TimeOfDaySettings& timeSettings) const; + + private: + T mSunriseValue, mDayValue, mSunsetValue, mNightValue; + }; + /// Defines a single weather setting (according to INI) class Weather { @@ -42,38 +69,21 @@ namespace MWWorld const MWWorld::Fallback& fallback, float stormWindSpeed, float rainSpeed, - const std::string& ambientLoopSoundID, const std::string& particleEffect); std::string mCloudTexture; - // Sky (atmosphere) colors - osg::Vec4f mSkySunriseColor; - osg::Vec4f mSkyDayColor; - osg::Vec4f mSkySunsetColor; - osg::Vec4f mSkyNightColor; - - // Fog colors - osg::Vec4f mFogSunriseColor; - osg::Vec4f mFogDayColor; - osg::Vec4f mFogSunsetColor; - osg::Vec4f mFogNightColor; - - // Ambient lighting colors - osg::Vec4f mAmbientSunriseColor; - osg::Vec4f mAmbientDayColor; - osg::Vec4f mAmbientSunsetColor; - osg::Vec4f mAmbientNightColor; - - // Sun (directional) lighting colors - osg::Vec4f mSunSunriseColor; - osg::Vec4f mSunDayColor; - osg::Vec4f mSunSunsetColor; - osg::Vec4f mSunNightColor; + // Sky (atmosphere) color + TimeOfDayInterpolator mSkyColor; + // Fog color + TimeOfDayInterpolator mFogColor; + // Ambient lighting color + TimeOfDayInterpolator mAmbientColor; + // Sun (directional) lighting color + TimeOfDayInterpolator mSunColor; // Fog depth/density - float mLandFogDayDepth; - float mLandFogNightDepth; + TimeOfDayInterpolator mLandFogDepth; // Color modulation for the sun itself during sunset osg::Vec4f mSunDiscSunsetColor; @@ -243,12 +253,18 @@ namespace MWWorld float mSunriseDuration; float mSunsetDuration; float mSunPreSunsetTime; - float mNightStart; - float mNightEnd; - float mDayStart; - float mDayEnd; + + TimeOfDaySettings mTimeSettings; + + // fading of night skydome + TimeOfDayInterpolator mNightFade; + float mHoursBetweenWeatherChanges; float mRainSpeed; + + // underwater fog not really related to weather, but we handle it here because it's convenient + TimeOfDayInterpolator mUnderwaterFog; + std::vector mWeatherSettings; MoonModel mMasser; MoonModel mSecunda; @@ -273,7 +289,6 @@ namespace MWWorld void addWeather(const std::string& name, const MWWorld::Fallback& fallback, - const std::string& ambientLoopSoundID = "", const std::string& particleEffect = ""); void importRegions(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d994a35ee7..c2c8e58334 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -22,6 +21,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -53,7 +54,6 @@ #include "player.hpp" #include "manualref.hpp" #include "cellstore.hpp" -#include "cellfunctors.hpp" #include "containerstore.hpp" #include "inventorystore.hpp" #include "actionteleport.hpp" @@ -137,8 +137,7 @@ namespace MWWorld { if (mSky && (isCellExterior() || isCellQuasiExterior())) { - mRendering->skySetDate (mGlobalVariables["day"].getInteger(), - mGlobalVariables["month"].getInteger()); + mRendering->skySetDate (mDay->getInteger(), mMonth->getInteger()); mRendering->setSkyEnabled(true); } @@ -153,7 +152,8 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell, const std::string& startupScript) + int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, + const std::string& resourcePath) : mResourceSystem(resourceSystem), mFallback(fallbackMap), mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), @@ -162,8 +162,8 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0) { mPhysics = new MWPhysics::PhysicsSystem(resourceSystem, rootNode); - mProjectileManager.reset(new ProjectileManager(rootNode, resourceSystem, mPhysics)); - mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem, &mFallback); + mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem, &mFallback, resourcePath); + mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering, mPhysics)); mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); @@ -186,18 +186,30 @@ namespace MWWorld if (mEsm[0].getFormat() == 0) ensureNeededRecords(); + fillGlobalVariables(); + mStore.setUp(); mStore.movePlayerRecord(); mSwimHeightScale = mStore.get().find("fSwimHeightScale")->getFloat(); - mGlobalVariables.fill (mStore); - mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); mWorldScene = new Scene(*mRendering, mPhysics); } + void World::fillGlobalVariables() + { + mGlobalVariables.fill (mStore); + + mGameHour = &mGlobalVariables["gamehour"]; + mDaysPassed = &mGlobalVariables["dayspassed"]; + mDay = &mGlobalVariables["day"]; + mMonth = &mGlobalVariables["month"]; + mYear = &mGlobalVariables["year"]; + mTimeScale = &mGlobalVariables["timescale"]; + } + void World::startNewGame (bool bypass) { mGoToJail = false; @@ -304,7 +316,7 @@ namespace MWWorld mTeleportEnabled = true; mLevitationEnabled = true; - mGlobalVariables.fill (mStore); + fillGlobalVariables(); } int World::countSavedGameRecords() const @@ -677,12 +689,12 @@ namespace MWWorld return mWorldScene->searchPtrViaActorId (actorId); } - struct FindContainerFunctor + struct FindContainerVisitor { - Ptr mContainedPtr; + ConstPtr mContainedPtr; Ptr mResult; - FindContainerFunctor(const Ptr& containedPtr) : mContainedPtr(containedPtr) {} + FindContainerVisitor(const ConstPtr& containedPtr) : mContainedPtr(containedPtr) {} bool operator() (Ptr ptr) { @@ -696,7 +708,7 @@ namespace MWWorld } }; - Ptr World::findContainer(const Ptr& ptr) + Ptr World::findContainer(const ConstPtr& ptr) { if (ptr.isInCell()) return Ptr(); @@ -708,11 +720,15 @@ namespace MWWorld const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) { - FindContainerFunctor functor(ptr); - (*cellIt)->forEachContainer(functor); + FindContainerVisitor visitor(ptr); + (*cellIt)->forEachType(visitor); + if (visitor.mResult.isEmpty()) + (*cellIt)->forEachType(visitor); + if (visitor.mResult.isEmpty()) + (*cellIt)->forEachType(visitor); - if (!functor.mResult.isEmpty()) - return functor.mResult; + if (!visitor.mResult.isEmpty()) + return visitor.mResult; } return Ptr(); @@ -780,6 +796,9 @@ namespace MWWorld if (reference.getRefData().isEnabled()) { + if (reference == getPlayerPtr()) + throw std::runtime_error("can not disable player object"); + reference.getRefData().disable(); if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) @@ -793,15 +812,15 @@ namespace MWWorld mWeatherManager->advanceTime (hours, incremental); - hours += mGlobalVariables["gamehour"].getFloat(); + hours += mGameHour->getFloat(); setHour (hours); int days = static_cast(hours / 24); if (days>0) - mGlobalVariables["dayspassed"].setInteger ( - days + mGlobalVariables["dayspassed"].getInteger()); + mDaysPassed->setInteger ( + days + mDaysPassed->getInteger()); } void World::setHour (double hour) @@ -813,10 +832,10 @@ namespace MWWorld hour = std::fmod (hour, 24); - mGlobalVariables["gamehour"].setFloat(static_cast(hour)); + mGameHour->setFloat(static_cast(hour)); if (days>0) - setDay (days + mGlobalVariables["day"].getInteger()); + setDay (days + mDay->getInteger()); } void World::setDay (int day) @@ -824,7 +843,7 @@ namespace MWWorld if (day<1) day = 1; - int month = mGlobalVariables["month"].getInteger(); + int month = mMonth->getInteger(); while (true) { @@ -839,14 +858,14 @@ namespace MWWorld else { month = 0; - mGlobalVariables["year"].setInteger (mGlobalVariables["year"].getInteger()+1); + mYear->setInteger(mYear->getInteger()+1); } day -= days; } - mGlobalVariables["day"].setInteger (day); - mGlobalVariables["month"].setInteger (month); + mDay->setInteger(day); + mMonth->setInteger(month); mRendering->skySetDate(day, month); } @@ -861,30 +880,30 @@ namespace MWWorld int days = getDaysPerMonth (month); - if (mGlobalVariables["day"].getInteger()>days) - mGlobalVariables["day"].setInteger (days); + if (mDay->getInteger()>days) + mDay->setInteger (days); - mGlobalVariables["month"].setInteger (month); + mMonth->setInteger (month); if (years>0) - mGlobalVariables["year"].setInteger (years+mGlobalVariables["year"].getInteger()); + mYear->setInteger (years+mYear->getInteger()); - mRendering->skySetDate (mGlobalVariables["day"].getInteger(), month); + mRendering->skySetDate (mDay->getInteger(), month); } int World::getDay() const { - return mGlobalVariables["day"].getInteger(); + return mDay->getInteger(); } int World::getMonth() const { - return mGlobalVariables["month"].getInteger(); + return mMonth->getInteger(); } int World::getYear() const { - return mGlobalVariables["year"].getInteger(); + return mYear->getInteger(); } std::string World::getMonthName (int month) const @@ -909,8 +928,7 @@ namespace MWWorld TimeStamp World::getTimeStamp() const { - return TimeStamp (mGlobalVariables["gamehour"].getFloat(), - mGlobalVariables["dayspassed"].getInteger()); + return TimeStamp (mGameHour->getFloat(), mDaysPassed->getInteger()); } bool World::toggleSky() @@ -937,7 +955,7 @@ namespace MWWorld float World::getTimeScaleFactor() const { - return mGlobalVariables["timescale"].getFloat(); + return mTimeScale->getFloat(); } void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) @@ -994,7 +1012,8 @@ namespace MWWorld if (mActivationDistanceOverride >= 0) return static_cast(mActivationDistanceOverride); - return getStore().get().find("iMaxActivateDist")->getFloat() * 5 / 4; + static const int iMaxActivateDist = getStore().get().find("iMaxActivateDist")->getInt(); + return iMaxActivateDist * 5.f / 4.f; } MWWorld::Ptr World::getFacedObject() @@ -1019,32 +1038,30 @@ namespace MWWorld return facedObject; } - osg::Vec3f getActorHeadPosition(const MWWorld::Ptr& actor, MWRender::RenderingManager* rendering) + osg::Matrixf World::getActorHeadTransform(const MWWorld::ConstPtr& actor) const { - osg::Vec3f origin(actor.getRefData().getPosition().asVec3()); - - MWRender::Animation* anim = rendering->getAnimation(actor); - if (anim != NULL) + const MWRender::Animation *anim = mRendering->getAnimation(actor); + if(anim) { - const osg::Node* node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if (node != NULL) + const osg::Node *node = anim->getNode("Head"); + if(!node) node = anim->getNode("Bip01 Head"); + if(node) { osg::MatrixList mats = node->getWorldMatrices(); - if (mats.size()) - origin = mats[0].getTrans(); + if(!mats.empty()) + return mats[0]; } } - return origin; + return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); } - std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) + + std::pair World::getHitContact(const MWWorld::ConstPtr &ptr, float distance) { const ESM::Position &posdata = ptr.getRefData().getPosition(); osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f pos = getActorHeadPosition(ptr, mRendering); + osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance); if(result.first.isEmpty()) @@ -1057,6 +1074,9 @@ namespace MWWorld { if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == NULL) { + if (ptr == getPlayerPtr()) + throw std::runtime_error("can not delete player object"); + ptr.getRefData().setCount(0); if (ptr.isInCell() @@ -1130,7 +1150,7 @@ namespace MWWorld bool newCellActive = mWorldScene->isCellActive(*newCell); if (!currCellActive && newCellActive) { - newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); + newPtr = currCell->moveTo(ptr, newCell); mWorldScene->addObjectToScene(newPtr); std::string script = newPtr.getClass().getScript(newPtr); @@ -1146,17 +1166,16 @@ namespace MWWorld removeContainerScripts (ptr); haveToMove = false; - newPtr = ptr.getClass().copyToCell(ptr, *newCell); + newPtr = currCell->moveTo(ptr, newCell); newPtr.getRefData().setBaseNode(0); } else if (!currCellActive && !newCellActive) - newPtr = ptr.getClass().copyToCell(ptr, *newCell); + newPtr = currCell->moveTo(ptr, newCell); else // both cells active { - newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); + newPtr = currCell->moveTo(ptr, newCell); mRendering->updatePtr(ptr, newPtr); - ptr.getRefData().setBaseNode(NULL); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, newPtr); mPhysics->updatePtr(ptr, newPtr); @@ -1173,7 +1192,6 @@ namespace MWWorld addContainerScripts (newPtr, newCell); } } - ptr.getRefData().setCount(0); } } if (haveToMove && newPtr.getRefData().getBaseNode()) @@ -1243,38 +1261,15 @@ namespace MWWorld if(objRot[0] < -half_pi) objRot[0] = -half_pi; else if(objRot[0] > half_pi) objRot[0] = half_pi; - } - else - { - wrap(objRot[0]); - } - wrap(objRot[1]); - wrap(objRot[2]); + wrap(objRot[1]); + wrap(objRot[2]); + } ptr.getRefData().setPosition(pos); if(ptr.getRefData().getBaseNode() != 0) - mWorldScene->updateObjectLocalRotation(ptr); - } - - void World::localRotateObject (const Ptr& ptr, float x, float y, float z) - { - LocalRotation rot = ptr.getRefData().getLocalRotation(); - rot.rot[0]=osg::DegreesToRadians(x); - rot.rot[1]=osg::DegreesToRadians(y); - rot.rot[2]=osg::DegreesToRadians(z); - - wrap(rot.rot[0]); - wrap(rot.rot[1]); - wrap(rot.rot[2]); - - ptr.getRefData().setLocalRotation(rot); - - if (ptr.getRefData().getBaseNode() != 0) - { - mWorldScene->updateObjectLocalRotation(ptr); - } + mWorldScene->updateObjectRotation(ptr, true); } void World::adjustPosition(const Ptr &ptr, bool force) @@ -1321,15 +1316,12 @@ namespace MWWorld void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) { - rotateObjectImp(ptr, osg::Vec3f(osg::DegreesToRadians(x), - osg::DegreesToRadians(y), - osg::DegreesToRadians(z)), - adjust); + rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) + MWWorld::Ptr World::safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { - return copyObjectToCell(ptr,cell,pos,false); + return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1388,7 +1380,7 @@ namespace MWWorld { osg::Vec3f a(x1,y1,z1); osg::Vec3f b(x2,y2,z2); - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), MWPhysics::CollisionType_World); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), MWPhysics::CollisionType_World|MWPhysics::CollisionType_Door); return result.mHit; } @@ -1406,15 +1398,22 @@ namespace MWWorld } else { - float oldRot = osg::RadiansToDegrees(it->first.getRefData().getLocalRotation().rot[2]); - float diff = duration * 90.f; - float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second == 1 ? 1 : -1)), 90.f); - localRotateObject(it->first, 0, 0, targetRot); + const ESM::Position& objPos = it->first.getRefData().getPosition(); + float oldRot = objPos.rot[2]; + + float minRot = it->first.getCellRef().getPosition().rot[2]; + float maxRot = minRot + osg::DegreesToRadians(90.f); - bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; + float diff = duration * osg::DegreesToRadians(90.f); + float targetRot = std::min(std::max(minRot, oldRot + diff * (it->second == 1 ? 1 : -1)), maxRot); + rotateObject(it->first, objPos.rot[0], objPos.rot[1], targetRot); + // the rotation order we want to use + mWorldScene->updateObjectRotation(it->first, false); + + bool reached = (targetRot == maxRot && it->second) || targetRot == minRot; /// \todo should use convexSweepTest here - std::vector collisions = mPhysics->getCollisions(it->first, MWPhysics::CollisionType_Actor, MWPhysics::CollisionType_Actor); + std::vector collisions = mPhysics->getCollisions(it->first, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { MWWorld::Ptr ptr = *cit; @@ -1428,7 +1427,7 @@ namespace MWWorld } // we need to undo the rotation - localRotateObject(it->first, 0, 0, oldRot); + rotateObject(it->first, objPos.rot[0], objPos.rot[1], oldRot); reached = false; } } @@ -1568,8 +1567,20 @@ namespace MWWorld mPlayer->setLastKnownExteriorPosition(pos.asVec3()); } - if (player.getClass().getNpcStats(player).isWerewolf()) - MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(mRendering->getCamera()->isFirstPerson()); + bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf(); + bool isFirstPerson = mRendering->getCamera()->isFirstPerson(); + if (isWerewolf && isFirstPerson) + { + float werewolfFov = mFallback.getFallbackFloat("General_Werewolf_FOV"); + if (werewolfFov != 0) + mRendering->overrideFieldOfView(werewolfFov); + MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(true); + } + else + { + mRendering->resetFieldOfView(); + MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(false); + } // Sink the camera while sneaking bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak); @@ -1604,18 +1615,23 @@ namespace MWWorld void World::updateSoundListener() { const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition(); - osg::Vec3f playerPos = refpos.asVec3(); + osg::Vec3f listenerPos; - playerPos.z() += 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z(); + if (isFirstPerson()) + listenerPos = mRendering->getCameraPosition(); + else + listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z()); - osg::Quat playerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0,-1,0)) * + osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0,-1,0)) * osg::Quat(refpos.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f forward = playerOrient * osg::Vec3f(0,1,0); - osg::Vec3f up = playerOrient * osg::Vec3f(0,0,1); + osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0); + osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1); - MWBase::Environment::get().getSoundManager()->setListenerPosDir(playerPos, forward, up); + bool underwater = isUnderwater(getPlayerPtr().getCell(), listenerPos); + + MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } void World::updateWindowManager () @@ -1691,27 +1707,32 @@ namespace MWWorld osg::Vec2f World::getNorthVector (CellStore* cell) { - MWWorld::CellRefList& statics = cell->get(); - MWWorld::LiveCellRef* ref = statics.find("northmarker"); - if (!ref) + MWWorld::Ptr northmarker = cell->search("northmarker"); + + if (northmarker.isEmpty()) return osg::Vec2f(0, 1); - osg::Quat orient (-ref->mData.getPosition().rot[2], osg::Vec3f(0,0,1)); + osg::Quat orient (-northmarker.getRefData().getPosition().rot[2], osg::Vec3f(0,0,1)); osg::Vec3f dir = orient * osg::Vec3f(0,1,0); osg::Vec2f d (dir.x(), dir.y()); return d; } - void World::getDoorMarkers (CellStore* cell, std::vector& out) + struct GetDoorMarkerVisitor { - MWWorld::CellRefList& doors = cell->get(); - CellRefList::List& refList = doors.mList; - for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) + GetDoorMarkerVisitor(std::vector& out) + : mOut(out) + { + } + + std::vector& mOut; + + bool operator()(const MWWorld::Ptr& ptr) { - MWWorld::LiveCellRef& ref = *it; + MWWorld::LiveCellRef& ref = *static_cast* >(ptr.getBase()); - if (!ref.mData.isEnabled()) - continue; + if (!ref.mData.isEnabled() || ref.mData.isDeleted()) + return true; if (ref.mRef.getTeleport()) { @@ -1727,7 +1748,7 @@ namespace MWWorld else { cellid.mPaged = true; - positionToIndex( + MWBase::Environment::get().getWorld()->positionToIndex( ref.mRef.getDoorDest().pos[0], ref.mRef.getDoorDest().pos[1], cellid.mIndex.mX, @@ -1739,9 +1760,16 @@ namespace MWWorld newMarker.x = pos.pos[0]; newMarker.y = pos.pos[1]; - out.push_back(newMarker); + mOut.push_back(newMarker); } + return true; } + }; + + void World::getDoorMarkers (CellStore* cell, std::vector& out) + { + GetDoorMarkerVisitor visitor(out); + cell->forEachType(visitor); } void World::setWaterHeight(const float height) @@ -1769,7 +1797,7 @@ namespace MWWorld item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } - MWWorld::Ptr World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) + MWWorld::Ptr World::placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) { const float maxDist = 200.f; @@ -1789,10 +1817,7 @@ namespace MWWorld pos.rot[1] = 0; // copy the object and set its count - int origCount = object.getRefData().getCount(); - object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, cell, pos, true); - object.getRefData().setCount(origCount); + Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); // only the player place items in the world, so no need to check actor PCDropped(dropped); @@ -1818,7 +1843,7 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore* cell, ESM::Position pos, bool adjustPos) + Ptr World::copyObjectToCell(const ConstPtr &object, CellStore* cell, ESM::Position pos, int count, bool adjustPos) { if (cell->isExterior()) { @@ -1828,14 +1853,9 @@ namespace MWWorld } MWWorld::Ptr dropped = - object.getClass().copyToCell(object, *cell, pos); + object.getClass().copyToCell(object, *cell, pos, count); // Reset some position values that could be uninitialized if this item came from a container - LocalRotation localRotation; - localRotation.rot[0] = 0; - localRotation.rot[1] = 0; - localRotation.rot[2] = 0; - dropped.getRefData().setLocalRotation(localRotation); dropped.getCellRef().setPosition(pos); dropped.getCellRef().unsetRefNum(); @@ -1876,7 +1896,7 @@ namespace MWWorld return dropped; } - MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) + MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const ConstPtr& object, int amount) { MWWorld::CellStore* cell = actor.getCell(); @@ -1890,17 +1910,14 @@ namespace MWWorld orig.z() += 20; osg::Vec3f dir (0, 0, -1); - float len = 100.0; + float len = 1000000.0; MWRender::RenderingManager::RayResult result = mRendering->castRay(orig, orig+dir*len, true, true); if (result.mHit) pos.pos[2] = result.mHitPointWorld.z(); // copy the object and set its count - int origCount = object.getRefData().getCount(); - object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, cell, pos); - object.getRefData().setCount(origCount); + Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); if(actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); @@ -1948,27 +1965,27 @@ namespace MWWorld return false; } - bool World::isSubmerged(const MWWorld::Ptr &object) const + bool World::isSubmerged(const MWWorld::ConstPtr &object) const { return isUnderwater(object, 1.0f/mSwimHeightScale); } - bool World::isSwimming(const MWWorld::Ptr &object) const + bool World::isSwimming(const MWWorld::ConstPtr &object) const { return isUnderwater(object, mSwimHeightScale); } - bool World::isWading(const MWWorld::Ptr &object) const + bool World::isWading(const MWWorld::ConstPtr &object) const { const float kneeDeep = 0.25f; return isUnderwater(object, kneeDeep); } - bool World::isUnderwater(const MWWorld::Ptr &object, const float heightRatio) const + bool World::isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const { osg::Vec3f pos (object.getRefData().getPosition().asVec3()); - pos.z() += heightRatio*2*mPhysics->getHalfExtents(object).z(); + pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z(); return isUnderwater(object.getCell(), pos); } @@ -2079,11 +2096,10 @@ namespace MWWorld if (!actor) throw std::runtime_error("can't find player"); - if((!actor->getOnGround()&&actor->getCollisionMode()) || isUnderwater(currentCell, playerPos) || isWalkingOnWater(player)) + if ((actor->getCollisionMode() && !mPhysics->isOnSolidGround(player)) || isUnderwater(currentCell, playerPos)) return 2; - if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || - player.getClass().getNpcStats(player).isWerewolf()) + if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || player.getClass().getNpcStats(player).isWerewolf()) return 1; return 0; @@ -2091,8 +2107,11 @@ namespace MWWorld MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr) { - if (ptr == getPlayerPtr()) - return mRendering->getPlayerAnimation(); + return mRendering->getAnimation(ptr); + } + + const MWRender::Animation* World::getAnimation(const MWWorld::ConstPtr &ptr) const + { return mRendering->getAnimation(ptr); } @@ -2107,7 +2126,7 @@ namespace MWWorld switch (state) { case 0: - if (door.getRefData().getLocalRotation().rot[2] == 0) + if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2]) state = 1; // if closed, then open else state = 2; // if open, then close @@ -2132,33 +2151,33 @@ namespace MWWorld mDoorStates.erase(door); } - bool World::getPlayerStandingOn (const MWWorld::Ptr& object) + bool World::getPlayerStandingOn (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isActorStandingOn(player, object); } - bool World::getActorStandingOn (const MWWorld::Ptr& object) + bool World::getActorStandingOn (const MWWorld::ConstPtr& object) { std::vector actors; mPhysics->getActorsStandingOn(object, actors); return !actors.empty(); } - bool World::getPlayerCollidingWith (const MWWorld::Ptr& object) + bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isActorCollidingWith(player, object); } - bool World::getActorCollidingWith (const MWWorld::Ptr& object) + bool World::getActorCollidingWith (const MWWorld::ConstPtr& object) { std::vector actors; mPhysics->getActorsCollidingWith(object, actors); return !actors.empty(); } - void World::hurtStandingActors(const Ptr &object, float healthPerSecond) + void World::hurtStandingActors(const ConstPtr &object, float healthPerSecond) { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; @@ -2175,6 +2194,8 @@ namespace MWWorld health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); stats.setHealth(health); + mPhysics->markAsNonSolid (object); + if (healthPerSecond > 0.0f) { if (actor == getPlayerPtr()) @@ -2186,7 +2207,7 @@ namespace MWWorld } } - void World::hurtCollidingActors(const Ptr &object, float healthPerSecond) + void World::hurtCollidingActors(const ConstPtr &object, float healthPerSecond) { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; @@ -2203,6 +2224,8 @@ namespace MWWorld health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); stats.setHealth(health); + mPhysics->markAsNonSolid (object); + if (healthPerSecond > 0.0f) { if (actor == getPlayerPtr()) @@ -2238,25 +2261,40 @@ namespace MWWorld return osg::Vec3f(0,1,0); } - void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) + struct GetContainersOwnedByVisitor + { + GetContainersOwnedByVisitor(const MWWorld::ConstPtr& owner, std::vector& out) + : mOwner(owner) + , mOut(out) + { + } + + MWWorld::ConstPtr mOwner; + std::vector& mOut; + + bool operator()(const MWWorld::Ptr& ptr) + { + if (ptr.getRefData().isDeleted()) + return true; + + if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId())) + mOut.push_back(ptr); + + return true; + } + }; + + void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector& out) { const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) { - MWWorld::CellRefList& containers = (*cellIt)->get(); - CellRefList::List& refList = containers.mList; - for (CellRefList::List::iterator container = refList.begin(); container != refList.end(); ++container) - { - MWWorld::Ptr ptr (&*container, *cellIt); - if (ptr.getRefData().isDeleted()) - continue; - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId())) - out.push_back(ptr); - } + GetContainersOwnedByVisitor visitor (owner, out); + (*cellIt)->forEachType(visitor); } } - struct ListObjectsFunctor + struct ListObjectsVisitor { std::vector mObjects; @@ -2268,25 +2306,25 @@ namespace MWWorld } }; - void World::getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) + void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) { const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) { - ListObjectsFunctor functor; - (*cellIt)->forEach(functor); + ListObjectsVisitor visitor; + (*cellIt)->forEach(visitor); - for (std::vector::iterator it = functor.mObjects.begin(); it != functor.mObjects.end(); ++it) + for (std::vector::iterator it = visitor.mObjects.begin(); it != visitor.mObjects.end(); ++it) if (Misc::StringUtils::ciEqual(it->getCellRef().getOwner(), npc.getCellRef().getRefId())) out.push_back(*it); } } - bool World::getLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor) + bool World::getLOS(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& targetActor) { if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled()) return false; // cannot get LOS unless both NPC's are enabled - if (!targetActor.getRefData().getBaseNode() || !targetActor.getRefData().getBaseNode()) + if (!targetActor.getRefData().getBaseNode() || !actor.getRefData().getBaseNode()) return false; // not in active cell return mPhysics->getLineOfSight(actor, targetActor); @@ -2299,7 +2337,7 @@ namespace MWWorld to = from + (to * maxDist); MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), - MWPhysics::CollisionType_World|MWPhysics::CollisionType_HeightMap); + MWPhysics::CollisionType_World|MWPhysics::CollisionType_HeightMap|MWPhysics::CollisionType_Door); if (!result.mHit) return maxDist; @@ -2327,7 +2365,8 @@ namespace MWWorld if (0 == cellStore) { return false; } - const DoorList &doors = cellStore->get().mList; + + const DoorList &doors = cellStore->getReadOnlyDoors().mList; for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) { if (!it->mRef.getTeleport()) { continue; @@ -2349,7 +2388,7 @@ namespace MWWorld if (0 != source) { // Find door leading to our current teleport door // and use it destination to position inside cell. - const DoorList &doors = source->get().mList; + const DoorList &doors = source->getReadOnlyDoors().mList; for (DoorList::const_iterator jt = doors.begin(); jt != doors.end(); ++jt) { if (it->mRef.getTeleport() && Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell())) @@ -2363,7 +2402,7 @@ namespace MWWorld } } // Fall back to the first static location. - const StaticList &statics = cellStore->get().mList; + const StaticList &statics = cellStore->getReadOnlyStatics().mList; if ( statics.begin() != statics.end() ) { pos = statics.begin()->mRef.getPosition(); return true; @@ -2587,7 +2626,7 @@ namespace MWWorld } // If this is a power, check if it was already used in the last 24h - if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell->mId)) + if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell)) { message = "#{sPowerAlreadyUsed}"; fail = true; @@ -2615,7 +2654,7 @@ namespace MWWorld MWWorld::Ptr target; float distance = 192.f; // ?? osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - osg::Vec3f origin = getActorHeadPosition(actor, mRendering); + osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); @@ -2670,7 +2709,7 @@ namespace MWWorld } } - void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + void World::launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) { mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); @@ -2732,7 +2771,7 @@ namespace MWWorld MWWorld::CellStore *next = getInterior( *i ); if ( !next ) continue; - const MWWorld::CellRefList& doors = next->getReadOnly(); + const MWWorld::CellRefList& doors = next->getReadOnlyDoors(); const CellRefList::List& refList = doors.mList; // Check if any door in the cell leads to an exterior directly @@ -2763,7 +2802,7 @@ namespace MWWorld return false; } - MWWorld::Ptr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ) + MWWorld::ConstPtr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ) { if ( ptr.getCell()->isExterior() ) { return getClosestMarkerFromExteriorPosition(mPlayer->getLastKnownExteriorPosition(), id); @@ -2775,7 +2814,7 @@ namespace MWWorld std::set< std::string >checkedCells; std::set< std::string >currentCells; std::set< std::string >nextCells; - MWWorld::Ptr closestMarker; + MWWorld::ConstPtr closestMarker; nextCells.insert( ptr.getCell()->getCell()->mName ); while ( !nextCells.empty() ) { @@ -2786,13 +2825,13 @@ namespace MWWorld checkedCells.insert( *i ); if ( !next ) continue; - closestMarker = next->search( id ); + closestMarker = next->searchConst( id ); if ( !closestMarker.isEmpty() ) { return closestMarker; } - const MWWorld::CellRefList& doors = next->getReadOnly(); + const MWWorld::CellRefList& doors = next->getReadOnlyDoors(); const CellRefList::List& doorList = doors.mList; // Check if any door in the cell leads to an exterior directly @@ -2816,12 +2855,11 @@ namespace MWWorld } } } - return MWWorld::Ptr(); } - MWWorld::Ptr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) { - MWWorld::Ptr closestMarker; + MWWorld::ConstPtr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) { + MWWorld::ConstPtr closestMarker; float closestDistance = std::numeric_limits::max(); std::vector markers; @@ -2846,7 +2884,7 @@ namespace MWWorld void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, const std::string& id) { - MWWorld::Ptr closestMarker = getClosestMarker( ptr, id ); + MWWorld::ConstPtr closestMarker = getClosestMarker( ptr, id ); if ( closestMarker.isEmpty() ) { @@ -2873,9 +2911,9 @@ namespace MWWorld mWeatherManager->update(duration, paused); } - struct AddDetectedReference + struct AddDetectedReferenceVisitor { - AddDetectedReference(std::vector& out, Ptr detector, World::DetectionType type, float squaredDist) + AddDetectedReferenceVisitor(std::vector& out, Ptr detector, World::DetectionType type, float squaredDist) : mOut(out), mDetector(detector), mSquaredDist(squaredDist), mType(type) { } @@ -2884,7 +2922,7 @@ namespace MWWorld Ptr mDetector; float mSquaredDist; World::DetectionType mType; - bool operator() (MWWorld::Ptr ptr) + bool operator() (const MWWorld::Ptr& ptr) { if ((ptr.getRefData().getPosition().asVec3() - mDetector.getRefData().getPosition().asVec3()).length2() >= mSquaredDist) return true; @@ -2915,7 +2953,7 @@ namespace MWWorld return true; } - bool needToAdd (MWWorld::Ptr ptr, MWWorld::Ptr detector) + bool needToAdd (const MWWorld::Ptr& ptr, const MWWorld::Ptr& detector) { if (mType == World::Detect_Creature) { @@ -2955,13 +2993,13 @@ namespace MWWorld dist = feetToGameUnits(dist); - AddDetectedReference functor (out, ptr, type, dist*dist); + AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist); const Scene::CellStoreCollection& active = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it) { MWWorld::CellStore* cellStore = *it; - cellStore->forEach(functor); + cellStore->forEach(visitor); } } @@ -3006,13 +3044,13 @@ namespace MWWorld void World::confiscateStolenItems(const Ptr &ptr) { - MWWorld::Ptr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); + MWWorld::ConstPtr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); if ( prisonMarker.isEmpty() ) { std::cerr << "Failed to confiscate items: no closest prison marker found." << std::endl; return; } - std::string prisonName = prisonMarker.mRef->mRef.getDestCell(); + std::string prisonName = prisonMarker.getCellRef().getDestCell(); if ( prisonName.empty() ) { std::cerr << "Failed to confiscate items: prison marker not linked to prison interior" << std::endl; @@ -3159,9 +3197,9 @@ namespace MWWorld { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mAreaSound.empty()) - sndMgr->playManualSound3D(origin, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + sndMgr->playSound3D(origin, effect->mAreaSound, 1.0f, 1.0f); else - sndMgr->playManualSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + sndMgr->playSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f); } // Get the actors in range of the effect std::vector objects; @@ -3217,7 +3255,7 @@ namespace MWWorld interpreterContext.executeActivation(object, actor); } - struct ResetActorsFunctor + struct ResetActorsVisitor { bool operator() (Ptr ptr) { @@ -3239,24 +3277,30 @@ namespace MWWorld iter!=mWorldScene->getActiveCells().end(); ++iter) { CellStore* cellstore = *iter; - ResetActorsFunctor functor; - cellstore->forEach(functor); + ResetActorsVisitor visitor; + cellstore->forEach(visitor); } } - bool World::isWalkingOnWater(const Ptr &actor) + bool World::isWalkingOnWater(const ConstPtr &actor) { - MWPhysics::Actor* physicActor = mPhysics->getActor(actor); + const MWPhysics::Actor* physicActor = mPhysics->getActor(actor); if (physicActor && physicActor->isWalkingOnWater()) return true; return false; } - osg::Vec3f World::aimToTarget(const Ptr &actor, const MWWorld::Ptr& target) + osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { - osg::Vec3f weaponPos = getActorHeadPosition(actor, mRendering); - osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); - targetPos.z() += mPhysics->getHalfExtents(target).z(); + osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); + osg::Vec3f targetPos = mPhysics->getPosition(target); return (targetPos - weaponPos); } + + float World::getHitDistance(const ConstPtr &actor, const ConstPtr &target) + { + osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); + return mPhysics->getHitDistance(weaponPos, target); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 26153086a3..ceda1321a9 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -85,6 +85,13 @@ namespace MWWorld MWPhysics::PhysicsSystem *mPhysics; bool mSky; + ESM::Variant* mGameHour; + ESM::Variant* mDaysPassed; + ESM::Variant* mDay; + ESM::Variant* mMonth; + ESM::Variant* mYear; + ESM::Variant* mTimeScale; + Cells mCells; std::string mCurrentWorldSpace; @@ -116,14 +123,15 @@ namespace MWWorld Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return an updated Ptr in case the Ptr's cell changes - Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); + Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); void updateWindowManager (); void updatePlayer(bool paused); MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true); - + public: // FIXME void removeContainerScripts(const Ptr& reference); + private: void addContainerScripts(const Ptr& reference, CellStore* cell); void PCDropped (const Ptr& item); @@ -135,6 +143,8 @@ namespace MWWorld void ensureNeededRecords(); + void fillGlobalVariables(); + /** * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) * @param fileCollections- Container which holds content file names and their paths @@ -145,7 +155,7 @@ namespace MWWorld const std::vector& content, ContentLoader& contentLoader); float mSwimHeightScale; - bool isUnderwater(const MWWorld::Ptr &object, const float heightRatio) const; + bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const; ///< helper function for implementing isSwimming(), isSubmerged(), isWading() bool mTeleportEnabled; @@ -155,8 +165,8 @@ namespace MWWorld float feetToGameUnits(float feet); - MWWorld::Ptr getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ); - MWWorld::Ptr getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ); + MWWorld::ConstPtr getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ); + MWWorld::ConstPtr getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ); public: @@ -167,7 +177,7 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell, const std::string& startupScript); + int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath); virtual ~World(); @@ -259,7 +269,7 @@ namespace MWWorld virtual Ptr searchPtrViaActorId (int actorId); ///< Search is limited to the active cells. - virtual MWWorld::Ptr findContainer (const MWWorld::Ptr& ptr); + virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr); ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. @@ -334,7 +344,7 @@ namespace MWWorld /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance); + virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance); /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const Ptr& ptr); @@ -349,14 +359,13 @@ namespace MWWorld virtual void scaleObject (const Ptr& ptr, float scale); - /// World rotates object, uses degrees + /// World rotates object, uses radians + /// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This + /// could be considered a bug, but is needed for MW compatibility. /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); - /// Local rotates object, uses degrees - virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); + virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual float getMaxActivationDistance(); @@ -434,14 +443,14 @@ namespace MWWorld virtual void update (float duration, bool paused); - virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); + virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount); ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount); + virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount); ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object @@ -455,12 +464,14 @@ namespace MWWorld virtual bool isFlying(const MWWorld::Ptr &ptr) const; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const; ///Is the head of the creature underwater? - virtual bool isSubmerged(const MWWorld::Ptr &object) const; - virtual bool isSwimming(const MWWorld::Ptr &object) const; + virtual bool isSubmerged(const MWWorld::ConstPtr &object) const; + virtual bool isSwimming(const MWWorld::ConstPtr &object) const; virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const; - virtual bool isWading(const MWWorld::Ptr &object) const; + virtual bool isWading(const MWWorld::ConstPtr &object) const; virtual bool isOnGround(const MWWorld::Ptr &ptr) const; + virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const; + virtual void togglePOV(); virtual bool isFirstPerson() const; @@ -489,25 +500,25 @@ namespace MWWorld /// @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); + virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object); ///< @return true if the player is standing on \a object + virtual bool getActorStandingOn (const MWWorld::ConstPtr& object); ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object); ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::ConstPtr& object); ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::ConstPtr& 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); + virtual void hurtCollidingActors (const MWWorld::ConstPtr& 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); + virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out); ///< get all containers in active cells owned by this Npc - virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); + virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out); ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor); + virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor); ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist); @@ -523,6 +534,7 @@ namespace MWWorld /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr); + virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const; virtual void reattachPlayerCamera(); /// \todo this does not belong here @@ -575,7 +587,7 @@ namespace MWWorld virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); - virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); @@ -629,11 +641,14 @@ namespace MWWorld /// Resets all actors in the current active cells to their original location within that cell. virtual void resetActors(); - virtual bool isWalkingOnWater (const MWWorld::Ptr& actor); + virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor); /// Return a vector aiming the actor's weapon towards a target. /// @note The length of the vector is the distance between actor and target. - virtual osg::Vec3f aimToTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); + + /// Return the distance between actor's weapon and target's collision box. + virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2ffb7ffa0e..2300f97a36 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -4,8 +4,11 @@ if (GTEST_FOUND) include_directories(${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES - components/misc/test_*.cpp - mwdialogue/test_*.cpp + ../openmw/mwworld/store.cpp + ../openmw/mwworld/esmstore.cpp + mwworld/test_store.cpp + + mwdialogue/test_keywordsearch.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp deleted file mode 100644 index 55fe0e0c27..0000000000 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include "components/misc/stringops.hpp" - -struct StringOpsTest : public ::testing::Test -{ - protected: - virtual void SetUp() - { - } - - virtual void TearDown() - { - } -}; diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp new file mode 100644 index 0000000000..ac21470ded --- /dev/null +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -0,0 +1,315 @@ +#include + +#include + +#include +#include +#include +#include + +#include "apps/openmw/mwworld/esmstore.hpp" + +static Loading::Listener dummyListener; + +/// Base class for tests of ESMStore that rely on external content files to produce the test results +struct ContentFileTest : public ::testing::Test +{ + protected: + + virtual void SetUp() + { + readContentFiles(); + + // load the content files + std::vector readerList; + readerList.resize(mContentFiles.size()); + + int index=0; + for (std::vector::const_iterator it = mContentFiles.begin(); it != mContentFiles.end(); ++it) + { + ESM::ESMReader lEsm; + lEsm.setEncoder(NULL); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&readerList); + lEsm.open(it->string()); + readerList[index] = lEsm; + mEsmStore.load(readerList[index], &dummyListener); + + ++index; + } + + mEsmStore.setUp(); + } + + virtual void TearDown() + { + } + + // read absolute path to content files from openmw.cfg + void readContentFiles() + { + boost::program_options::variables_map variables; + + boost::program_options::options_description desc("Allowed options"); + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value >()->default_value(std::vector(), "") + ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") + ("data-local", boost::program_options::value()->default_value("")); + + boost::program_options::notify(variables); + + mConfigurationManager.readConfiguration(variables, desc, true); + + Files::PathContainer dataDirs, dataLocal; + if (!variables["data"].empty()) { + dataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + dataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mConfigurationManager.processPaths (dataDirs); + mConfigurationManager.processPaths (dataLocal, true); + + if (!dataLocal.empty()) + dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + + Files::Collections collections (dataDirs, true); + + std::vector contentFiles = variables["content"].as >(); + for (std::vector::iterator it = contentFiles.begin(); it != contentFiles.end(); ++it) + mContentFiles.push_back(collections.getPath(*it)); + } + +protected: + Files::ConfigurationManager mConfigurationManager; + MWWorld::ESMStore mEsmStore; + std::vector mContentFiles; +}; + +/// Print results of the dialogue merging process, i.e. the resulting linked list. +TEST_F(ContentFileTest, dialogue_merging_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_dialogue_merging.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + const MWWorld::Store& dialStore = mEsmStore.get(); + for (MWWorld::Store::iterator it = dialStore.begin(); it != dialStore.end(); ++it) + { + const ESM::Dialogue& dial = *it; + stream << "Dialogue: " << dial.mId << std::endl; + + for (ESM::Dialogue::InfoContainer::const_iterator infoIt = dial.mInfo.begin(); infoIt != dial.mInfo.end(); ++infoIt) + { + const ESM::DialInfo& info = *infoIt; + stream << info.mId << std::endl; + } + stream << std::endl; + } + + std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl; +} + +// Note: here we don't test records that don't use string names (e.g. Land, Pathgrid, Cell) +#define RUN_TEST_FOR_TYPES(func, arg1, arg2) \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); + +template +void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream) +{ + const MWWorld::Store& store = esmStore.get(); + outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl; + + for (typename MWWorld::Store::iterator it = store.begin(); it != store.end(); ++it) + { + const T& record = *it; + outStream << record.mId << std::endl; + } + + outStream << std::endl; +} + +/// Print some basic diagnostics about the loaded content files, e.g. number of records and names of those records +/// Also used to test the iteration order of records +TEST_F(ContentFileTest, content_diagnostics_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_content_diagnostics.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + RUN_TEST_FOR_TYPES(printRecords, mEsmStore, stream); + + std::cout << "diagnostics_test successful, results printed to " << file << std::endl; +} + +// TODO: +/// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on +/// - even incorrect rounding modes can completely change the resulting spell lists. +/* +TEST_F(ContentFileTest, autocalc_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + +} +*/ + +/// Base class for tests of ESMStore that do not rely on external content files +struct StoreTest : public ::testing::Test +{ +protected: + MWWorld::ESMStore mEsmStore; +}; + + +/// Create an ESM file in-memory containing the specified record. +/// @param deleted Write record with deleted flag? +template +Files::IStreamPtr getEsmFile(T record, bool deleted) +{ + ESM::ESMWriter writer; + std::stringstream* stream = new std::stringstream; + writer.setFormat(0); + writer.save(*stream); + writer.startRecord(T::sRecordId); + record.save(writer, deleted); + writer.endRecord(T::sRecordId); + + return Files::IStreamPtr(stream); +} + +/// Tests deletion of records. +TEST_F(StoreTest, delete_test) +{ + const std::string recordId = "foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); + + // now a plugin deletes it + file = getEsmFile(record, true); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 0); + + // now another plugin inserts it again + // expected behaviour is the record to reappear rather than staying deleted + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); +} + +/// Tests overwriting of records. +TEST_F(StoreTest, overwrite_test) +{ + const std::string recordId = "foobar"; + const std::string recordIdUpper = "Foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // now a plugin overwrites it with changed data + record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it + record.mModel = "the_new_model"; + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // verify that changes were actually applied + const RecordType* overwrittenRec = mEsmStore.get().search(recordId); + + ASSERT_TRUE (overwrittenRec != NULL); + + ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); +} diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index dc2674680e..2f0af88c94 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -28,12 +28,6 @@ Wizard::InstallationPage::InstallationPage(QWidget *parent) : connect(mUnshield, SIGNAL(finished()), mThread, SLOT(quit())); - connect(mUnshield, SIGNAL(finished()), - mUnshield, SLOT(deleteLater())); - - connect(mUnshield, SIGNAL(finished()), - mThread, SLOT(deleteLater()));; - connect(mUnshield, SIGNAL(finished()), this, SLOT(installationFinished()), Qt::QueuedConnection); @@ -60,6 +54,7 @@ Wizard::InstallationPage::~InstallationPage() { if (mThread->isRunning()) { mUnshield->stopWorker(); + mThread->quit(); mThread->wait(); } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 7538511fe9..19cdf9535d 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -138,7 +138,7 @@ void Wizard::MainWizard::setupGameSettings() QString path(userPath + QLatin1String("openmw.cfg")); QFile file(path); - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -163,7 +163,7 @@ void Wizard::MainWizard::setupGameSettings() paths.append(globalPath + QLatin1String("openmw.cfg")); foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { @@ -197,7 +197,7 @@ void Wizard::MainWizard::setupLauncherSettings() QFile file(path); - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 9daea2b71a..c8f7ca677c 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -45,9 +45,7 @@ Wizard::UnshieldWorker::~UnshieldWorker() void Wizard::UnshieldWorker::stopWorker() { - mMutex.lock(); mStopped = true; - mMutex.unlock(); } void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index c1d3cfff1f..3f922ad78a 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -104,7 +104,6 @@ namespace Wizard QTextCodec *mIniCodec; QWaitCondition mWait; - QMutex mMutex; QReadWriteLock mLock; diff --git a/cmake/BundleUtilitiesWithRPath.cmake b/cmake/BundleUtilitiesWithRPath.cmake deleted file mode 100644 index 5254e1a529..0000000000 --- a/cmake/BundleUtilitiesWithRPath.cmake +++ /dev/null @@ -1,961 +0,0 @@ -#.rst: -# BundleUtilities -# --------------- -# -# Functions to help assemble a standalone bundle application. -# -# A collection of CMake utility functions useful for dealing with .app -# bundles on the Mac and bundle-like directories on any OS. -# -# The following functions are provided by this module: -# -# :: -# -# fixup_bundle -# copy_and_fixup_bundle -# verify_app -# get_bundle_main_executable -# get_dotapp_dir -# get_bundle_and_executable -# get_bundle_all_executables -# get_item_key -# clear_bundle_keys -# set_bundle_key_values -# get_bundle_keys -# copy_resolved_item_into_bundle -# copy_resolved_framework_into_bundle -# fixup_bundle_item -# verify_bundle_prerequisites -# verify_bundle_symlinks -# -# Requires CMake 2.6 or greater because it uses function, break and -# PARENT_SCOPE. Also depends on GetPrerequisites.cmake. -# -# :: -# -# FIXUP_BUNDLE( ) -# -# Fix up a bundle in-place and make it standalone, such that it can be -# drag-n-drop copied to another machine and run on that machine as long -# as all of the system libraries are compatible. -# -# If you pass plugins to fixup_bundle as the libs parameter, you should -# install them or copy them into the bundle before calling fixup_bundle. -# The "libs" parameter is a list of libraries that must be fixed up, but -# that cannot be determined by otool output analysis. (i.e., plugins) -# -# Gather all the keys for all the executables and libraries in a bundle, -# and then, for each key, copy each prerequisite into the bundle. Then -# fix each one up according to its own list of prerequisites. -# -# Then clear all the keys and call verify_app on the final bundle to -# ensure that it is truly standalone. -# -# :: -# -# COPY_AND_FIXUP_BUNDLE( ) -# -# Makes a copy of the bundle at location and then fixes up -# the new copied bundle in-place at ... -# -# :: -# -# VERIFY_APP() -# -# Verifies that an application appears valid based on running -# analysis tools on it. Calls "message(FATAL_ERROR" if the application -# is not verified. -# -# :: -# -# GET_BUNDLE_MAIN_EXECUTABLE( ) -# -# The result will be the full path name of the bundle's main executable -# file or an "error:" prefixed string if it could not be determined. -# -# :: -# -# GET_DOTAPP_DIR( ) -# -# Returns the nearest parent dir whose name ends with ".app" given the -# full path to an executable. If there is no such parent dir, then -# simply return the dir containing the executable. -# -# The returned directory may or may not exist. -# -# :: -# -# GET_BUNDLE_AND_EXECUTABLE( ) -# -# Takes either a ".app" directory name or the name of an executable -# nested inside a ".app" directory and returns the path to the ".app" -# directory in and the path to its main executable in -# -# -# :: -# -# GET_BUNDLE_ALL_EXECUTABLES( ) -# -# Scans the given bundle recursively for all executable files and -# accumulates them into a variable. -# -# :: -# -# GET_ITEM_KEY( ) -# -# Given a file (item) name, generate a key that should be unique -# considering the set of libraries that need copying or fixing up to -# make a bundle standalone. This is essentially the file name including -# extension with "." replaced by "_" -# -# This key is used as a prefix for CMake variables so that we can -# associate a set of variables with a given item based on its key. -# -# :: -# -# CLEAR_BUNDLE_KEYS() -# -# Loop over the list of keys, clearing all the variables associated with -# each key. After the loop, clear the list of keys itself. -# -# Caller of get_bundle_keys should call clear_bundle_keys when done with -# list of keys. -# -# :: -# -# SET_BUNDLE_KEY_VALUES( -# ) -# -# Add a key to the list (if necessary) for the given item. If added, -# also set all the variables associated with that key. -# -# :: -# -# GET_BUNDLE_KEYS( ) -# -# Loop over all the executable and library files within the bundle (and -# given as extra ) and accumulate a list of keys representing -# them. Set values associated with each key such that we can loop over -# all of them and copy prerequisite libs into the bundle and then do -# appropriate install_name_tool fixups. -# -# :: -# -# COPY_RESOLVED_ITEM_INTO_BUNDLE( ) -# -# Copy a resolved item into the bundle if necessary. Copy is not -# necessary if the resolved_item is "the same as" the -# resolved_embedded_item. -# -# :: -# -# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE( ) -# -# Copy a resolved framework into the bundle if necessary. Copy is not -# necessary if the resolved_item is "the same as" the -# resolved_embedded_item. -# -# By default, BU_COPY_FULL_FRAMEWORK_CONTENTS is not set. If you want -# full frameworks embedded in your bundles, set -# BU_COPY_FULL_FRAMEWORK_CONTENTS to ON before calling fixup_bundle. By -# default, COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE copies the framework -# dylib itself plus the framework Resources directory. -# -# :: -# -# IS_RESOLVED_ITEM_EMBEDDED( ) -# -# Set variable to True if the resolved item is -# embedded into the bundle. The function does NOT check for the existence of the -# item, instead if checks if the provided path would correspond to an embeddable -# item. If is True, extra information will be displayed in case the item -# is not embedded. -# -# :: -# -# FIXUP_BUNDLE_ITEM( ) -# -# Get the direct/non-system prerequisites of the resolved embedded item. -# For each prerequisite, change the way it is referenced to the value of -# the _EMBEDDED_ITEM keyed variable for that prerequisite. (Most likely -# changing to an "@executable_path" style reference.) -# -# This function requires that the resolved_embedded_item be "inside" the -# bundle already. In other words, if you pass plugins to fixup_bundle -# as the libs parameter, you should install them or copy them into the -# bundle before calling fixup_bundle. The "libs" parameter is a list of -# libraries that must be fixed up, but that cannot be determined by -# otool output analysis. (i.e., plugins) -# -# Also, change the id of the item being fixed up to its own -# _EMBEDDED_ITEM value. -# -# Accumulate changes in a local variable and make *one* call to -# install_name_tool at the end of the function with all the changes at -# once. -# -# If the BU_CHMOD_BUNDLE_ITEMS variable is set then bundle items will be -# marked writable before install_name_tool tries to change them. -# -# :: -# -# VERIFY_BUNDLE_PREREQUISITES( ) -# -# Verifies that the sum of all prerequisites of all files inside the -# bundle are contained within the bundle or are "system" libraries, -# presumed to exist everywhere. -# -# :: -# -# VERIFY_BUNDLE_SYMLINKS( ) -# -# Verifies that any symlinks found in the bundle point to other files -# that are already also in the bundle... Anything that points to an -# external file causes this function to fail the verification. - -#============================================================================= -# Copyright 2008-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# Copyright (c) 2015 BWH and 3D Slicer contributors, http://slicer.org -# -# Redistribution AND use is allowed according to the terms of the -# BSD-style license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -# The functions defined in this file depend on the get_prerequisites function -# (and possibly others) found in: -# -get_filename_component(BundleUtilities_cmake_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) -include("${BundleUtilities_cmake_dir}/GetPrerequisitesWithRPath.cmake") - - -function(get_bundle_main_executable bundle result_var) - set(result "error: '${bundle}/Contents/Info.plist' file does not exist") - - if(EXISTS "${bundle}/Contents/Info.plist") - set(result "error: no CFBundleExecutable in '${bundle}/Contents/Info.plist' file") - set(line_is_main_executable 0) - set(bundle_executable "") - - # Read Info.plist as a list of lines: - # - set(eol_char "E") - file(READ "${bundle}/Contents/Info.plist" info_plist) - string(REPLACE ";" "\\;" info_plist "${info_plist}") - string(REPLACE "\n" "${eol_char};" info_plist "${info_plist}") - string(REPLACE "\r" "${eol_char};" info_plist "${info_plist}") - - # Scan the lines for "CFBundleExecutable" - the line after that - # is the name of the main executable. - # - foreach(line ${info_plist}) - if(line_is_main_executable) - string(REGEX REPLACE "^.*(.*).*$" "\\1" bundle_executable "${line}") - break() - endif() - - if(line MATCHES "CFBundleExecutable") - set(line_is_main_executable 1) - endif() - endforeach() - - if(NOT "${bundle_executable}" STREQUAL "") - if(EXISTS "${bundle}/Contents/MacOS/${bundle_executable}") - set(result "${bundle}/Contents/MacOS/${bundle_executable}") - else() - - # Ultimate goal: - # If not in "Contents/MacOS" then scan the bundle for matching files. If - # there is only one executable file that matches, then use it, otherwise - # it's an error... - # - #file(GLOB_RECURSE file_list "${bundle}/${bundle_executable}") - - # But for now, pragmatically, it's an error. Expect the main executable - # for the bundle to be in Contents/MacOS, it's an error if it's not: - # - set(result "error: '${bundle}/Contents/MacOS/${bundle_executable}' does not exist") - endif() - endif() - else() - # - # More inclusive technique... (This one would work on Windows and Linux - # too, if a developer followed the typical Mac bundle naming convention...) - # - # If there is no Info.plist file, try to find an executable with the same - # base name as the .app directory: - # - endif() - - set(${result_var} "${result}" PARENT_SCOPE) -endfunction() - - -function(get_dotapp_dir exe dotapp_dir_var) - set(s "${exe}") - - if(s MATCHES "/.*\\.app/") - # If there is a ".app" parent directory, - # ascend until we hit it: - # (typical of a Mac bundle executable) - # - set(done 0) - while(NOT ${done}) - get_filename_component(snamewe "${s}" NAME_WE) - get_filename_component(sname "${s}" NAME) - get_filename_component(sdir "${s}" PATH) - set(s "${sdir}") - if(sname MATCHES "\\.app$") - set(done 1) - set(dotapp_dir "${sdir}/${sname}") - endif() - endwhile() - else() - # Otherwise use a directory containing the exe - # (typical of a non-bundle executable on Mac, Windows or Linux) - # - is_file_executable("${s}" is_executable) - if(is_executable) - get_filename_component(sdir "${s}" PATH) - set(dotapp_dir "${sdir}") - else() - set(dotapp_dir "${s}") - endif() - endif() - - - set(${dotapp_dir_var} "${dotapp_dir}" PARENT_SCOPE) -endfunction() - - -function(get_bundle_and_executable app bundle_var executable_var valid_var) - set(valid 0) - - if(EXISTS "${app}") - # Is it a directory ending in .app? - if(IS_DIRECTORY "${app}") - if(app MATCHES "\\.app$") - get_bundle_main_executable("${app}" executable) - if(EXISTS "${app}" AND EXISTS "${executable}") - set(${bundle_var} "${app}" PARENT_SCOPE) - set(${executable_var} "${executable}" PARENT_SCOPE) - set(valid 1) - #message(STATUS "info: handled .app directory case...") - else() - message(STATUS "warning: *NOT* handled - .app directory case...") - endif() - else() - message(STATUS "warning: *NOT* handled - directory but not .app case...") - endif() - else() - # Is it an executable file? - is_file_executable("${app}" is_executable) - if(is_executable) - get_dotapp_dir("${app}" dotapp_dir) - if(EXISTS "${dotapp_dir}") - set(${bundle_var} "${dotapp_dir}" PARENT_SCOPE) - set(${executable_var} "${app}" PARENT_SCOPE) - set(valid 1) - #message(STATUS "info: handled executable file in .app dir case...") - else() - get_filename_component(app_dir "${app}" PATH) - set(${bundle_var} "${app_dir}" PARENT_SCOPE) - set(${executable_var} "${app}" PARENT_SCOPE) - set(valid 1) - #message(STATUS "info: handled executable file in any dir case...") - endif() - else() - message(STATUS "warning: *NOT* handled - not .app dir, not executable file...") - endif() - endif() - else() - message(STATUS "warning: *NOT* handled - directory/file does not exist...") - endif() - - if(NOT valid) - set(${bundle_var} "error: not a bundle" PARENT_SCOPE) - set(${executable_var} "error: not a bundle" PARENT_SCOPE) - endif() - - set(${valid_var} ${valid} PARENT_SCOPE) -endfunction() - - -function(get_bundle_all_executables bundle exes_var) - set(exes "") - - file(GLOB_RECURSE file_list "${bundle}/*") - if(UNIX) - find_program(find_cmd "find") - mark_as_advanced(find_cmd) - endif() - - # find command is much quicker than checking every file one by one on Unix - # which can take long time for large bundles, and since anyway we expect - # executable to have execute flag set we can narrow the list much quicker. - if(find_cmd) - execute_process(COMMAND "${find_cmd}" "${bundle}" - -type f \( -perm -0100 -o -perm -0010 -o -perm -0001 \) - OUTPUT_VARIABLE file_list - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - string(REPLACE "\n" ";" file_list "${file_list}") - else() - file(GLOB_RECURSE file_list "${bundle}/*") - endif() - - foreach(f ${file_list}) - is_file_executable("${f}" is_executable) - if(is_executable) - set(exes ${exes} "${f}") - endif() - endforeach() - - set(${exes_var} "${exes}" PARENT_SCOPE) -endfunction() - - -function(get_item_key item key_var) - get_filename_component(item_name "${item}" NAME) - if(WIN32) - string(TOLOWER "${item_name}" item_name) - endif() - string(REPLACE "." "_" ${key_var} "${item_name}") - set(${key_var} ${${key_var}} PARENT_SCOPE) -endfunction() - - -function(clear_bundle_keys keys_var) - foreach(key ${${keys_var}}) - set(${key}_ITEM PARENT_SCOPE) - set(${key}_RESOLVED_ITEM PARENT_SCOPE) - set(${key}_DEFAULT_EMBEDDED_PATH PARENT_SCOPE) - set(${key}_EMBEDDED_ITEM PARENT_SCOPE) - set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE) - set(${key}_COPYFLAG PARENT_SCOPE) - endforeach() - set(${keys_var} PARENT_SCOPE) -endfunction() - - -function(set_bundle_key_values keys_var context item exepath dirs copyflag) - get_filename_component(item_name "${item}" NAME) - - get_item_key("${item}" key) - - list(LENGTH ${keys_var} length_before) - gp_append_unique(${keys_var} "${key}") - list(LENGTH ${keys_var} length_after) - - if(NOT length_before EQUAL length_after) - gp_resolve_item("${context}" "${item}" "${exepath}" "${dirs}" resolved_item) - - gp_item_default_embedded_path("${item}" default_embedded_path) - - if(item MATCHES "[^/]+\\.framework/") - # For frameworks, construct the name under the embedded path from the - # opening "${item_name}.framework/" to the closing "/${item_name}": - # - string(REGEX REPLACE "^.*(${item_name}.framework/.*/?${item_name}).*$" "${default_embedded_path}/\\1" embedded_item "${item}") - else() - # For other items, just use the same name as the original, but in the - # embedded path: - # - set(embedded_item "${default_embedded_path}/${item_name}") - - if(APPLE) - # For executables inside the bundle, extract the expected path. - # This remove the hack introduced in commit 6f8bdd27 consisting in - # reseting the value of 'resolved_embedded_item' with 'resolved_item'. - get_dotapp_dir("${exepath}" exe_dotapp_dir) - if(NOT DEFINED gp_bundle_executables) - get_bundle_all_executables("${exe_dotapp_dir}" gp_bundle_executables) - endif() - foreach(exe ${gp_bundle_executables}) - get_item_key("${exe}" exe_key) - list(APPEND exe_keys ${exe_key}) - endforeach() - list(FIND exe_keys ${key} is_executable) - if(NOT is_executable EQUAL "-1") - get_filename_component(resolved_item_path ${resolved_item} PATH) - file(RELATIVE_PATH exe_relative_path_from_dir ${exe_dotapp_dir} ${resolved_item_path}) - # For example, if input variables are: - # resolved_item: /path/to/MyApp.app/Contents/bin/myapp - # exe_dotapp_dir: /path/to/MyApp.app - # Computed variables will be: - # resolved_item_path: /path/to/MyApp.app/Contents/bin - # exe_relative_path_from_dir: Contents/bin - set(embedded_item "@executable_path/../../${exe_relative_path_from_dir}/${item_name}") - set(show_status 0) - if(show_status) - message(STATUS "resolved_item='${resolved_item}'") - message(STATUS "exe_dotapp_dir='${exe_dotapp_dir}'") - message(STATUS "exe_relative_path_from_dir='${exe_relative_path_from_dir}'") - message(STATUS "item_name='${item_name}'") - message(STATUS "embedded_item='${embedded_item}'") - message(STATUS "") - endif() - endif() - endif() - endif() - - gp_resolve_embedded_item("${context}" "${embedded_item}" "${exepath}" resolved_embedded_item) - get_filename_component(resolved_embedded_item "${resolved_embedded_item}" ABSOLUTE) - - # Do not copy already embedded item - set(verbose 0) - is_resolved_item_embedded("${resolved_embedded_item}" "${exepath}" "${verbose}" is_embedded) - if(EXISTS "${resolved_embedded_item}" AND is_embedded) - set(copyflag 0) - set(resolved_item "${resolved_embedded_item}") - endif() - - set(${keys_var} ${${keys_var}} PARENT_SCOPE) - set(${key}_ITEM "${item}" PARENT_SCOPE) - set(${key}_RESOLVED_ITEM "${resolved_item}" PARENT_SCOPE) - set(${key}_DEFAULT_EMBEDDED_PATH "${default_embedded_path}" PARENT_SCOPE) - set(${key}_EMBEDDED_ITEM "${embedded_item}" PARENT_SCOPE) - set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE) - set(${key}_COPYFLAG "${copyflag}" PARENT_SCOPE) - else() - #message("warning: item key '${key}' already in the list, subsequent references assumed identical to first") - endif() -endfunction() - - -function(get_bundle_keys app libs dirs keys_var) - set(${keys_var} PARENT_SCOPE) - - get_bundle_and_executable("${app}" bundle executable valid) - if(valid) - # Always use the exepath of the main bundle executable for @executable_path - # replacements: - # - get_filename_component(exepath "${executable}" PATH) - - # But do fixups on all executables in the bundle: - # - get_bundle_all_executables("${bundle}" gp_bundle_executables) - - # For each extra lib, accumulate a key as well and then also accumulate - # any of its prerequisites. (Extra libs are typically dynamically loaded - # plugins: libraries that are prerequisites for full runtime functionality - # but that do not show up in otool -L output...) - # - foreach(lib ${libs}) - set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${exepath}" "${dirs}" 0) - - set(prereqs "") - get_prerequisites("${lib}" prereqs 1 1 "${exepath}" "${dirs}") - foreach(pr ${prereqs}) - set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${exepath}" "${dirs}" 1) - endforeach() - endforeach() - - # For each executable found in the bundle, accumulate keys as we go. - # The list of keys should be complete when all prerequisites of all - # binaries in the bundle have been analyzed. - # - foreach(exe ${gp_bundle_executables}) - # Add the exe itself to the keys: - # - set_bundle_key_values(${keys_var} "${exe}" "${exe}" "${exepath}" "${dirs}" 0) - - # Add each prerequisite to the keys: - # - set(prereqs "") - get_prerequisites("${exe}" prereqs 1 1 "${exepath}" "${dirs}") - foreach(pr ${prereqs}) - set_bundle_key_values(${keys_var} "${exe}" "${pr}" "${exepath}" "${dirs}" 1) - endforeach() - endforeach() - - # Propagate values to caller's scope: - # - set(${keys_var} ${${keys_var}} PARENT_SCOPE) - foreach(key ${${keys_var}}) - set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE) - set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE) - set(${key}_DEFAULT_EMBEDDED_PATH "${${key}_DEFAULT_EMBEDDED_PATH}" PARENT_SCOPE) - set(${key}_EMBEDDED_ITEM "${${key}_EMBEDDED_ITEM}" PARENT_SCOPE) - set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE) - set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE) - endforeach() - endif() -endfunction() - - -function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) - if(WIN32) - # ignore case on Windows - string(TOLOWER "${resolved_item}" resolved_item_compare) - string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) - else() - set(resolved_item_compare "${resolved_item}") - set(resolved_embedded_item_compare "${resolved_embedded_item}") - endif() - - if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") - message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...") - else() - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") - endif() - endif() - -endfunction() - - -function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_item) - if(WIN32) - # ignore case on Windows - string(TOLOWER "${resolved_item}" resolved_item_compare) - string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) - else() - set(resolved_item_compare "${resolved_item}") - set(resolved_embedded_item_compare "${resolved_embedded_item}") - endif() - - if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") - message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...") - else() - if(BU_COPY_FULL_FRAMEWORK_CONTENTS) - # Full Framework (everything): - get_filename_component(resolved_dir "${resolved_item}" PATH) - get_filename_component(resolved_dir "${resolved_dir}/../.." ABSOLUTE) - get_filename_component(resolved_embedded_dir "${resolved_embedded_item}" PATH) - get_filename_component(resolved_embedded_dir "${resolved_embedded_dir}/../.." ABSOLUTE) - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_dir}' '${resolved_embedded_dir}'") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_dir}" "${resolved_embedded_dir}") - else() - # Framework lib itself: - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - - # Plus Resources, if they exist: - string(REGEX REPLACE "^(.*)/[^/]+$" "\\1/Resources" resolved_resources "${resolved_item}") - string(REGEX REPLACE "^(.*)/[^/]+$" "\\1/Resources" resolved_embedded_resources "${resolved_embedded_item}") - if(EXISTS "${resolved_resources}") - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_resources}' '${resolved_embedded_resources}'") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_resources}" "${resolved_embedded_resources}") - endif() - - # Some frameworks e.g. Qt put Info.plist in wrong place, so when it is - # missing in resources, copy it from other well known incorrect locations: - if(NOT EXISTS "${resolved_resources}/Info.plist") - # Check for Contents/Info.plist in framework root (older Qt SDK): - string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Contents/Info.plist" resolved_info_plist "${resolved_item}") - string(REGEX REPLACE "^(.*)/[^/]+$" "\\1/Resources/Info.plist" resolved_embedded_info_plist "${resolved_embedded_item}") - if(EXISTS "${resolved_info_plist}") - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_info_plist}' '${resolved_embedded_info_plist}'") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_info_plist}" "${resolved_embedded_info_plist}") - endif() - endif() - - # Check if framework is versioned and fix it layout - string(REGEX REPLACE "^.*/([^/]+)/[^/]+$" "\\1" resolved_embedded_version "${resolved_embedded_item}") - string(REGEX REPLACE "^(.*)/[^/]+/[^/]+$" "\\1" resolved_embedded_versions "${resolved_embedded_item}") - string(REGEX REPLACE "^.*/([^/]+)/[^/]+/[^/]+$" "\\1" resolved_embedded_versions_basename "${resolved_embedded_item}") - if(resolved_embedded_versions_basename STREQUAL "Versions") - # Ensure Current symlink points to the framework version - if(NOT EXISTS "${resolved_embedded_versions}/Current") - execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "${resolved_embedded_version}" "${resolved_embedded_versions}/Current") - endif() - # Restore symlinks in framework root pointing to current framework - # binary and resources: - string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1" resolved_embedded_root "${resolved_embedded_item}") - string(REGEX REPLACE "^.*/([^/]+)$" "\\1" resolved_embedded_item_basename "${resolved_embedded_item}") - if(NOT EXISTS "${resolved_embedded_root}/${resolved_embedded_item_basename}") - execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "Versions/Current/${resolved_embedded_item_basename}" "${resolved_embedded_root}/${resolved_embedded_item_basename}") - endif() - if(NOT EXISTS "${resolved_embedded_root}/Resources") - execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "Versions/Current/Resources" "${resolved_embedded_root}/Resources") - endif() - endif() - endif() - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") - endif() - endif() - -endfunction() - -function(is_resolved_item_embedded resolved_item exepath verbose is_embedded_var) - get_dotapp_dir("${exepath}" exe_dotapp_dir) - string(LENGTH "${exe_dotapp_dir}/" exe_dotapp_dir_length) - string(LENGTH "${resolved_item}" resolved_item_length) - set(path_too_short 0) - set(is_embedded 0) - if(${resolved_item_length} LESS ${exe_dotapp_dir_length}) - set(path_too_short 1) - endif() - if(NOT path_too_short) - string(SUBSTRING "${resolved_item}" 0 ${exe_dotapp_dir_length} item_substring) - if("${exe_dotapp_dir}/" STREQUAL "${item_substring}") - set(is_embedded 1) - endif() - endif() - if(verbose AND NOT is_embedded) - message(" exe_dotapp_dir/='${exe_dotapp_dir}/'") - message(" item_substring='${item_substring}'") - message(" resolved_item='${resolved_item}'") - message("") - endif() - set(${is_embedded_var} ${is_embedded} PARENT_SCOPE) -endfunction() - -function(fixup_bundle_item resolved_embedded_item exepath dirs) - # This item's key is "ikey": - # - get_item_key("${resolved_embedded_item}" ikey) - - # Ensure the item is "inside the .app bundle" -- it should not be fixed up if - # it is not in the .app bundle... Otherwise, we'll modify files in the build - # tree, or in other varied locations around the file system, with our call to - # install_name_tool. Make sure that doesn't happen here: - # - set(verbose 1) - is_resolved_item_embedded("${resolved_embedded_item}" "${exepath}" "${verbose}" is_embedded) - if(NOT is_embedded) - message("Install or copy the item into the bundle before calling fixup_bundle.") - message("Or maybe there's a typo or incorrect path in one of the args to fixup_bundle?") - message("") - message(FATAL_ERROR "cannot fixup an item that is not in the bundle...") - endif() - - set(prereqs "") - get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${exepath}" "${dirs}") - - set(changes "") - - foreach(pr ${prereqs}) - # Each referenced item's key is "rkey" in the loop: - # - get_item_key("${pr}" rkey) - - if(NOT "${${rkey}_EMBEDDED_ITEM}" STREQUAL "") - set(changes ${changes} "-change" "${pr}" "${${rkey}_EMBEDDED_ITEM}") - else() - message("warning: unexpected reference to '${pr}'") - endif() - endforeach() - - if(BU_CHMOD_BUNDLE_ITEMS) - execute_process(COMMAND chmod u+w "${resolved_embedded_item}") - endif() - - # Change this item's id and all of its references in one call - # to install_name_tool: - # - execute_process(COMMAND install_name_tool - ${changes} -id "${${ikey}_EMBEDDED_ITEM}" "${resolved_embedded_item}" - ) -endfunction() - - -function(fixup_bundle app libs dirs) - message(STATUS "fixup_bundle") - message(STATUS " app='${app}'") - message(STATUS " libs='${libs}'") - message(STATUS " dirs='${dirs}'") - - get_bundle_and_executable("${app}" bundle executable valid) - message(STATUS " bundle='${bundle}'") - message(STATUS " executable='${executable}'") - if(valid) - get_filename_component(exepath "${executable}" PATH) - - # TODO: Extract list of rpath dirs automatically. On MacOSX, the following could be - # done: otool -l path/to/executable | grep -A 3 LC_RPATH | grep path - # See http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html#comment-87ea054b4839586412727dcfc94c79d2 - set(GP_RPATH_DIR ${bundle}/Contents) - message(STATUS " GP_RPATH_DIR='${GP_RPATH_DIR}'") - - message(STATUS "fixup_bundle: preparing...") - get_bundle_keys("${app}" "${libs}" "${dirs}" keys) - - message(STATUS "fixup_bundle: copying...") - list(LENGTH keys n) - math(EXPR n ${n}*2) - - set(i 0) - foreach(key ${keys}) - math(EXPR i ${i}+1) - if(${${key}_COPYFLAG}) - message(STATUS "${i}/${n}: copying '${${key}_RESOLVED_ITEM}'") - else() - message(STATUS "${i}/${n}: *NOT* copying '${${key}_RESOLVED_ITEM}'") - endif() - - set(show_status 0) - if(show_status) - message(STATUS "key='${key}'") - message(STATUS "item='${${key}_ITEM}'") - message(STATUS "resolved_item='${${key}_RESOLVED_ITEM}'") - message(STATUS "default_embedded_path='${${key}_DEFAULT_EMBEDDED_PATH}'") - message(STATUS "embedded_item='${${key}_EMBEDDED_ITEM}'") - message(STATUS "resolved_embedded_item='${${key}_RESOLVED_EMBEDDED_ITEM}'") - message(STATUS "copyflag='${${key}_COPYFLAG}'") - message(STATUS "") - endif() - - if(${${key}_COPYFLAG}) - set(item "${${key}_ITEM}") - if(item MATCHES "[^/]+\\.framework/") - copy_resolved_framework_into_bundle("${${key}_RESOLVED_ITEM}" - "${${key}_RESOLVED_EMBEDDED_ITEM}") - else() - copy_resolved_item_into_bundle("${${key}_RESOLVED_ITEM}" - "${${key}_RESOLVED_EMBEDDED_ITEM}") - endif() - endif() - endforeach() - - message(STATUS "fixup_bundle: fixing...") - foreach(key ${keys}) - math(EXPR i ${i}+1) - if(APPLE) - message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'") - fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}") - else() - message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'") - endif() - endforeach() - - message(STATUS "fixup_bundle: cleaning up...") - clear_bundle_keys(keys) - - message(STATUS "fixup_bundle: verifying...") - verify_app("${app}") - else() - message(SEND_ERROR "error: fixup_bundle: not a valid bundle") - endif() - - message(STATUS "fixup_bundle: done") -endfunction() - - -function(copy_and_fixup_bundle src dst libs dirs) - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${src}" "${dst}") - fixup_bundle("${dst}" "${libs}" "${dirs}") -endfunction() - - -function(verify_bundle_prerequisites bundle result_var info_var) - set(result 1) - set(info "") - set(count 0) - - get_bundle_main_executable("${bundle}" main_bundle_exe) - - file(GLOB_RECURSE file_list "${bundle}/*") - foreach(f ${file_list}) - is_file_executable("${f}" is_executable) - if(is_executable) - get_filename_component(exepath "${f}" PATH) - math(EXPR count "${count} + 1") - - message(STATUS "executable file ${count}: ${f}") - - set(prereqs "") - get_prerequisites("${f}" prereqs 1 1 "${exepath}" "") - - # On the Mac, - # "embedded" and "system" prerequisites are fine... anything else means - # the bundle's prerequisites are not verified (i.e., the bundle is not - # really "standalone") - # - # On Windows (and others? Linux/Unix/...?) - # "local" and "system" prereqs are fine... - # - set(external_prereqs "") - - foreach(p ${prereqs}) - set(p_type "") - gp_file_type("${f}" "${p}" p_type) - - if(APPLE) - if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system") - set(external_prereqs ${external_prereqs} "${p}") - endif() - else() - if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system") - set(external_prereqs ${external_prereqs} "${p}") - endif() - endif() - endforeach() - - if(external_prereqs) - # Found non-system/somehow-unacceptable prerequisites: - set(result 0) - set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n") - endif() - endif() - endforeach() - - if(result) - set(info "Verified ${count} executable files in '${bundle}'") - endif() - - set(${result_var} "${result}" PARENT_SCOPE) - set(${info_var} "${info}" PARENT_SCOPE) -endfunction() - - -function(verify_bundle_symlinks bundle result_var info_var) - set(result 1) - set(info "") - set(count 0) - - # TODO: implement this function for real... - # Right now, it is just a stub that verifies unconditionally... - - set(${result_var} "${result}" PARENT_SCOPE) - set(${info_var} "${info}" PARENT_SCOPE) -endfunction() - - -function(verify_app app) - set(verified 0) - set(info "") - - get_bundle_and_executable("${app}" bundle executable valid) - - message(STATUS "===========================================================================") - message(STATUS "Analyzing app='${app}'") - message(STATUS "bundle='${bundle}'") - message(STATUS "executable='${executable}'") - message(STATUS "valid='${valid}'") - - # Verify that the bundle does not have any "external" prerequisites: - # - verify_bundle_prerequisites("${bundle}" verified info) - message(STATUS "verified='${verified}'") - message(STATUS "info='${info}'") - message(STATUS "") - - if(verified) - # Verify that the bundle does not have any symlinks to external files: - # - verify_bundle_symlinks("${bundle}" verified info) - message(STATUS "verified='${verified}'") - message(STATUS "info='${info}'") - message(STATUS "") - endif() - - if(NOT verified) - message(FATAL_ERROR "error: verify_app failed") - endif() -endfunction() diff --git a/cmake/COPYING-CMAKE-SCRIPTS b/cmake/COPYING-CMAKE-SCRIPTS index 21f1dbf7d1..d33c6f33bd 100644 --- a/cmake/COPYING-CMAKE-SCRIPTS +++ b/cmake/COPYING-CMAKE-SCRIPTS @@ -25,269 +25,3 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The following files are derived from the Slicer project -(https://github.com/Slicer/Slicer), which in turn derived from CMake project (http://cmake.org) and are covered under the licenses below. - -BundleUtilitiesWithRPath.cmake, GetPrerequisitesWithRPath.cmake - -# Slicer - -For more information, please see: - - http://www.slicer.org - -The 3D Slicer license below is a BSD style license, with extensions -to cover contributions and other issues specific to 3D Slicer. - - -3D Slicer Contribution and Software License Agreement ("Agreement") -Version 1.0 (December 20, 2005) - -This Agreement covers contributions to and downloads from the 3D -Slicer project ("Slicer") maintained by The Brigham and Women's -Hospital, Inc. ("Brigham"). Part A of this Agreement applies to -contributions of software and/or data to Slicer (including making -revisions of or additions to code and/or data already in Slicer). Part -B of this Agreement applies to downloads of software and/or data from -Slicer. Part C of this Agreement applies to all transactions with -Slicer. If you distribute Software (as defined below) downloaded from -Slicer, all of the paragraphs of Part B of this Agreement must be -included with and apply to such Software. - -Your contribution of software and/or data to Slicer (including prior -to the date of the first publication of this Agreement, each a -"Contribution") and/or downloading, copying, modifying, displaying, -distributing or use of any software and/or data from Slicer -(collectively, the "Software") constitutes acceptance of all of the -terms and conditions of this Agreement. If you do not agree to such -terms and conditions, you have no right to contribute your -Contribution, or to download, copy, modify, display, distribute or use -the Software. - -PART A. CONTRIBUTION AGREEMENT - License to Brigham with Right to -Sublicense ("Contribution Agreement"). - -1. As used in this Contribution Agreement, "you" means the individual - contributing the Contribution to Slicer and the institution or - entity which employs or is otherwise affiliated with such - individual in connection with such Contribution. - -2. This Contribution Agreement applies to all Contributions made to - Slicer, including without limitation Contributions made prior to - the date of first publication of this Agreement. If at any time you - make a Contribution to Slicer, you represent that (i) you are - legally authorized and entitled to make such Contribution and to - grant all licenses granted in this Contribution Agreement with - respect to such Contribution; (ii) if your Contribution includes - any patient data, all such data is de-identified in accordance with - U.S. confidentiality and security laws and requirements, including - but not limited to the Health Insurance Portability and - Accountability Act (HIPAA) and its regulations, and your disclosure - of such data for the purposes contemplated by this Agreement is - properly authorized and in compliance with all applicable laws and - regulations; and (iii) you have preserved in the Contribution all - applicable attributions, copyright notices and licenses for any - third party software or data included in the Contribution. - -3. Except for the licenses granted in this Agreement, you reserve all - right, title and interest in your Contribution. - -4. You hereby grant to Brigham, with the right to sublicense, a - perpetual, worldwide, non-exclusive, no charge, royalty-free, - irrevocable license to use, reproduce, make derivative works of, - display and distribute the Contribution. If your Contribution is - protected by patent, you hereby grant to Brigham, with the right to - sublicense, a perpetual, worldwide, non-exclusive, no-charge, - royalty-free, irrevocable license under your interest in patent - rights covering the Contribution, to make, have made, use, sell and - otherwise transfer your Contribution, alone or in combination with - any other code. - -5. You acknowledge and agree that Brigham may incorporate your - Contribution into Slicer and may make Slicer available to members - of the public on an open source basis under terms substantially in - accordance with the Software License set forth in Part B of this - Agreement. You further acknowledge and agree that Brigham shall - have no liability arising in connection with claims resulting from - your breach of any of the terms of this Agreement. - -6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION - DOES NOT CONTAIN ANY CODE THAT REQURES OR PRESCRIBES AN "OPEN - SOURCE LICENSE" FOR DERIVATIVE WORKS (by way of non-limiting - example, the GNU General Public License or other so-called - "reciprocal" license that requires any derived work to be licensed - under the GNU General Public License or other "open source - license"). - -PART B. DOWNLOADING AGREEMENT - License from Brigham with Right to -Sublicense ("Software License"). - -1. As used in this Software License, "you" means the individual - downloading and/or using, reproducing, modifying, displaying and/or - distributing the Software and the institution or entity which - employs or is otherwise affiliated with such individual in - connection therewith. The Brigham and Women?s Hospital, - Inc. ("Brigham") hereby grants you, with right to sublicense, with - respect to Brigham's rights in the software, and data, if any, - which is the subject of this Software License (collectively, the - "Software"), a royalty-free, non-exclusive license to use, - reproduce, make derivative works of, display and distribute the - Software, provided that: - -(a) you accept and adhere to all of the terms and conditions of this -Software License; - -(b) in connection with any copy of or sublicense of all or any portion -of the Software, all of the terms and conditions in this Software -License shall appear in and shall apply to such copy and such -sublicense, including without limitation all source and executable -forms and on any user documentation, prefaced with the following -words: "All or portions of this licensed product (such portions are -the "Software") have been obtained under license from The Brigham and -Women's Hospital, Inc. and are subject to the following terms and -conditions:" - -(c) you preserve and maintain all applicable attributions, copyright -notices and licenses included in or applicable to the Software; - -(d) modified versions of the Software must be clearly identified and -marked as such, and must not be misrepresented as being the original -Software; and - -(e) you consider making, but are under no obligation to make, the -source code of any of your modifications to the Software freely -available to others on an open source basis. - -2. The license granted in this Software License includes without - limitation the right to (i) incorporate the Software into - proprietary programs (subject to any restrictions applicable to - such programs), (ii) add your own copyright statement to your - modifications of the Software, and (iii) provide additional or - different license terms and conditions in your sublicenses of - modifications of the Software; provided that in each case your use, - reproduction or distribution of such modifications otherwise - complies with the conditions stated in this Software License. - -3. This Software License does not grant any rights with respect to - third party software, except those rights that Brigham has been - authorized by a third party to grant to you, and accordingly you - are solely responsible for (i) obtaining any permissions from third - parties that you need to use, reproduce, make derivative works of, - display and distribute the Software, and (ii) informing your - sublicensees, including without limitation your end-users, of their - obligations to secure any such required permissions. - -4. The Software has been designed for research purposes only and has - not been reviewed or approved by the Food and Drug Administration - or by any other agency. YOU ACKNOWLEDGE AND AGREE THAT CLINICAL - APPLICATIONS ARE NEITHER RECOMMENDED NOR ADVISED. Any - commercialization of the Software is at the sole risk of the party - or parties engaged in such commercialization. You further agree to - use, reproduce, make derivative works of, display and distribute - the Software in compliance with all applicable governmental laws, - regulations and orders, including without limitation those relating - to export and import control. - -5. The Software is provided "AS IS" and neither Brigham nor any - contributor to the software (each a "Contributor") shall have any - obligation to provide maintenance, support, updates, enhancements - or modifications thereto. BRIGHAM AND ALL CONTRIBUTORS SPECIFICALLY - DISCLAIM ALL EXPRESS AND IMPLIED WARRANTIES OF ANY KIND INCLUDING, - BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR - A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL - BRIGHAM OR ANY CONTRIBUTOR BE LIABLE TO ANY PARTY FOR DIRECT, - INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ARISING IN ANY WAY - RELATED TO THE SOFTWARE, EVEN IF BRIGHAM OR ANY CONTRIBUTOR HAS - BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM - EXTENT NOT PROHIBITED BY LAW OR REGULATION, YOU FURTHER ASSUME ALL - LIABILITY FOR YOUR USE, REPRODUCTION, MAKING OF DERIVATIVE WORKS, - DISPLAY, LICENSE OR DISTRIBUTION OF THE SOFTWARE AND AGREE TO - INDEMNIFY AND HOLD HARMLESS BRIGHAM AND ALL CONTRIBUTORS FROM AND - AGAINST ANY AND ALL CLAIMS, SUITS, ACTIONS, DEMANDS AND JUDGMENTS - ARISING THEREFROM. - -6. None of the names, logos or trademarks of Brigham or any of - Brigham's affiliates or any of the Contributors, or any funding - agency, may be used to endorse or promote products produced in - whole or in part by operation of the Software or derived from or - based on the Software without specific prior written permission - from the applicable party. - -7. Any use, reproduction or distribution of the Software which is not - in accordance with this Software License shall automatically revoke - all rights granted to you under this Software License and render - Paragraphs 1 and 2 of this Software License null and void. - -8. This Software License does not grant any rights in or to any - intellectual property owned by Brigham or any Contributor except - those rights expressly granted hereunder. - -PART C. MISCELLANEOUS - -This Agreement shall be governed by and construed in accordance with -the laws of The Commonwealth of Massachusetts without regard to -principles of conflicts of law. This Agreement shall supercede and -replace any license terms that you may have agreed to previously with -respect to Slicer. - -# CMake - -CMake - Cross Platform Makefile Generator -Copyright 2000-2015 Kitware, Inc. -Copyright 2000-2011 Insight Software Consortium -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the names of Kitware, Inc., the Insight Software Consortium, - nor the names of their contributors may be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ------------------------------------------------------------------------------- - -The above copyright and license notice applies to distributions of -CMake in source and binary form. Some source files contain additional -notices of original copyright by their contributors; see each source -for details. Third-party software packages supplied with CMake under -compatible licenses provide their own copyright notices documented in -corresponding subdirectories. - ------------------------------------------------------------------------------- - -CMake was initially developed by Kitware with the following sponsorship: - - * National Library of Medicine at the National Institutes of Health - as part of the Insight Segmentation and Registration Toolkit (ITK). - - * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel - Visualization Initiative. - - * National Alliance for Medical Image Computing (NAMIC) is funded by the - National Institutes of Health through the NIH Roadmap for Medical Research, - Grant U54 EB005149. - - * Kitware, Inc. diff --git a/cmake/FindOpenGLES.cmake b/cmake/FindOpenGLES.cmake new file mode 100644 index 0000000000..7ee2c07f18 --- /dev/null +++ b/cmake/FindOpenGLES.cmake @@ -0,0 +1,94 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +# - Try to find OpenGLES +# Once done this will define +# +# OPENGLES_FOUND - system has OpenGLES +# OPENGLES_INCLUDE_DIR - the GL include directory +# OPENGLES_LIBRARIES - Link these to use OpenGLES + +IF (WIN32) + IF (CYGWIN) + + FIND_PATH(OPENGLES_INCLUDE_DIR GLES/gl.h ) + + FIND_LIBRARY(OPENGLES_gl_LIBRARY libgles_cm ) + + ELSE (CYGWIN) + + IF(BORLAND) + SET (OPENGLES_gl_LIBRARY import32 CACHE STRING "OpenGL ES 1.x library for win32") + ELSE(BORLAND) + #MS compiler - todo - fix the following line: + SET (OPENGLES_gl_LIBRARY ${OGRE_SOURCE_DIR}/Dependencies/lib/release/libgles_cm.lib CACHE STRING "OpenGL ES 1.x library for win32") + ENDIF(BORLAND) + + ENDIF (CYGWIN) + +ELSE (WIN32) + + IF (APPLE) + + #create_search_paths(/Developer/Platforms) + #findpkg_framework(OpenGLES) + #set(OPENGLES_gl_LIBRARY "-framework OpenGLES") + + ELSE(APPLE) + + FIND_PATH(OPENGLES_INCLUDE_DIR GLES/gl.h + /opt/vc/include + /opt/graphics/OpenGL/include + /usr/openwin/share/include + /usr/X11R6/include + /usr/include + ) + + FIND_LIBRARY(OPENGLES_gl_LIBRARY + NAMES GLES_CM GLESv1_CM + PATHS /opt/vc/lib + /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib /usr/X11R6/lib + /usr/lib + ) + + # On Unix OpenGL most certainly always requires X11. + # Feel free to tighten up these conditions if you don't + # think this is always true. + + #IF (OPENGLES_gl_LIBRARY) + # IF(NOT X11_FOUND) + # INCLUDE(FindX11) + # ENDIF(NOT X11_FOUND) + # IF (X11_FOUND) + # SET (OPENGLES_LIBRARIES ${X11_LIBRARIES}) + # ENDIF (X11_FOUND) + #ENDIF (OPENGLES_gl_LIBRARY) + + ENDIF(APPLE) +ENDIF (WIN32) + +SET( OPENGLES_FOUND "NO" ) +IF(OPENGLES_gl_LIBRARY) + + SET( OPENGLES_LIBRARIES ${OPENGLES_gl_LIBRARY} ${OPENGLES_LIBRARIES}) + + SET( OPENGLES_FOUND "YES" ) + +ENDIF(OPENGLES_gl_LIBRARY) + +MARK_AS_ADVANCED( + OPENGLES_INCLUDE_DIR + OPENGLES_gl_LIBRARY +) + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OPENGLES REQUIRED_VARS OPENGLES_LIBRARIES OPENGLES_INCLUDE_DIR) diff --git a/cmake/GetPrerequisitesWithRPath.cmake b/cmake/GetPrerequisitesWithRPath.cmake deleted file mode 100644 index 5b5751ead3..0000000000 --- a/cmake/GetPrerequisitesWithRPath.cmake +++ /dev/null @@ -1,990 +0,0 @@ -# - Functions to analyze and list executable file prerequisites. -# This module provides functions to list the .dll, .dylib or .so -# files that an executable or shared library file depends on. (Its -# prerequisites.) -# -# It uses various tools to obtain the list of required shared library files: -# dumpbin (Windows) -# objdump (MinGW on Windows) -# ldd (Linux/Unix) -# otool (Mac OSX) -# The following functions are provided by this module: -# get_prerequisites -# list_prerequisites -# list_prerequisites_by_glob -# gp_append_unique -# is_file_executable -# gp_item_default_embedded_path -# (projects can override with gp_item_default_embedded_path_override) -# gp_resolve_item -# (projects can override with gp_resolve_item_override) -# gp_resolve_embedded_item -# (projects can override with gp_resolve_embedded_item_override) -# gp_resolved_file_type -# (projects can override with gp_resolved_file_type_override) -# gp_file_type -# Requires CMake 2.6 or greater because it uses function, break, return and -# PARENT_SCOPE. -# -# GET_PREREQUISITES( -# ) -# Get the list of shared library files required by . The list in -# the variable named should be empty on first entry to -# this function. On exit, will contain the list of -# required shared library files. -# -# is the full path to an executable file. is the -# name of a CMake variable to contain the results. must be 0 -# or 1 indicating whether to include or exclude "system" prerequisites. If -# is set to 1 all prerequisites will be found recursively, if set to -# 0 only direct prerequisites are listed. is the path to the top -# level executable used for @executable_path replacment on the Mac. is -# a list of paths where libraries might be found: these paths are searched -# first when a target without any path info is given. Then standard system -# locations are also searched: PATH, Framework locations, /usr/lib... -# -# LIST_PREREQUISITES( [ [ []]]) -# Print a message listing the prerequisites of . -# -# is the name of a shared library or executable target or the full -# path to a shared library or executable file. If is set to 1 all -# prerequisites will be found recursively, if set to 0 only direct -# prerequisites are listed. must be 0 or 1 indicating whether -# to include or exclude "system" prerequisites. With set to 0 only -# the full path names of the prerequisites are printed, set to 1 extra -# informatin will be displayed. -# -# LIST_PREREQUISITES_BY_GLOB( ) -# Print the prerequisites of shared library and executable files matching a -# globbing pattern. is GLOB or GLOB_RECURSE and is a -# globbing expression used with "file(GLOB" or "file(GLOB_RECURSE" to retrieve -# a list of matching files. If a matching file is executable, its prerequisites -# are listed. -# -# Any additional (optional) arguments provided are passed along as the -# optional arguments to the list_prerequisites calls. -# -# GP_APPEND_UNIQUE( ) -# Append to the list variable only if the value is not -# already in the list. -# -# IS_FILE_EXECUTABLE( ) -# Return 1 in if is a binary executable, 0 otherwise. -# -# GP_IS_FILE_EXECUTABLE_EXCLUDE_REGEX can be set to a regular expression used -# to give a hint to identify more quickly if a given file is an executable or not. -# This is particularly useful on unix platform where it can avoid a lot of -# time-consuming call to "file" external process. For packages bundling hundreds -# of libraries, executables, resources and data, it largely speeds up the function -# "get_bundle_all_executables". -# On unix, a convenient command line allowing to collect recursively all file extensions -# useful to generate a regular expression like "\\.(dylib|py|pyc|so)$" is: -# find . -type f -name '*.*' | sed 's@.*/.*\.@@' | sort | uniq | tr "\\n" "|" -# -# GP_ITEM_DEFAULT_EMBEDDED_PATH( ) -# Return the path that others should refer to the item by when the item -# is embedded inside a bundle. -# -# Override on a per-project basis by providing a project-specific -# gp_item_default_embedded_path_override function. -# -# GP_RESOLVE_ITEM( ) -# Resolve an item into an existing full path file. -# -# Override on a per-project basis by providing a project-specific -# gp_resolve_item_override function. -# -# GP_RESOLVE_EMBEDDED_ITEM( ) -# Resolve an embedded item into the full path within the full path. Since the item can be -# copied later, it doesn't have to exist when calling this function. -# -# Override on a per-project basis by providing a project-specific -# gp_resolve_embedded_item_override function. -# -# If GP_RPATH_DIR variable is set then item matching '@rpath' are -# resolved using the provided directory. Currently setting this variable -# has an effect only on MacOSX when fixing up application bundle. The directory -# are also assumed to be located within the application bundle. It is -# usually the directory passed to the 'rpath' linker option. -# -# GP_RESOLVED_FILE_TYPE( ) -# Return the type of with respect to . String -# describing type of prerequisite is returned in variable named . -# -# Use and if necessary to resolve non-absolute -# values -- but only for non-embedded items. -# -# Possible types are: -# system -# local -# embedded -# other -# Override on a per-project basis by providing a project-specific -# gp_resolved_file_type_override function. -# -# GP_FILE_TYPE( ) -# Return the type of with respect to . String -# describing type of prerequisite is returned in variable named . -# -# Possible types are: -# system -# local -# embedded -# other - -#============================================================================= -# Copyright 2008-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# Copyright (c) 2015 BWH and 3D Slicer contributors, http://slicer.org -# -# Redistribution AND use is allowed according to the terms of the -# BSD-style license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -function(gp_append_unique list_var value) - set(contains 0) - - foreach(item ${${list_var}}) - if("${item}" STREQUAL "${value}") - set(contains 1) - break() - endif() - endforeach() - - if(NOT contains) - set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE) - endif() -endfunction() - - -function(is_file_executable file result_var) - # - # A file is not executable until proven otherwise: - # - set(${result_var} 0 PARENT_SCOPE) - - get_filename_component(file_full "${file}" ABSOLUTE) - string(TOLOWER "${file_full}" file_full_lower) - - # If file name ends in .exe on Windows, *assume* executable: - # - if(WIN32 AND NOT UNIX) - if("${file_full_lower}" MATCHES "\\.exe$") - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - - # A clause could be added here that uses output or return value of dumpbin - # to determine ${result_var}. In 99%+? practical cases, the exe name - # match will be sufficient... - # - endif() - - # Use the information returned from the Unix shell command "file" to - # determine if ${file_full} should be considered an executable file... - # - # If the file command's output contains "executable" and does *not* contain - # "text" then it is likely an executable suitable for prerequisite analysis - # via the get_prerequisites macro. - # - if(UNIX) - - if(NOT "${GP_IS_FILE_EXECUTABLE_EXCLUDE_REGEX}" STREQUAL "") - if(${file_full} MATCHES "${GP_IS_FILE_EXECUTABLE_EXCLUDE_REGEX}") - set(${result_var} 0 PARENT_SCOPE) - return() - endif() - endif() - - if(NOT file_cmd) - find_program(file_cmd "file") - mark_as_advanced(file_cmd) - endif() - - if(file_cmd) - execute_process(COMMAND "${file_cmd}" "${file_full}" - OUTPUT_VARIABLE file_ov - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - # Replace the name of the file in the output with a placeholder token - # (the string " _file_full_ ") so that just in case the path name of - # the file contains the word "text" or "executable" we are not fooled - # into thinking "the wrong thing" because the file name matches the - # other 'file' command output we are looking for... - # - string(REPLACE "${file_full}" " _file_full_ " file_ov "${file_ov}") - string(TOLOWER "${file_ov}" file_ov) - - #message(STATUS "file_ov='${file_ov}'") - if("${file_ov}" MATCHES "executable") - #message(STATUS "executable!") - if("${file_ov}" MATCHES "text") - #message(STATUS "but text, so *not* a binary executable!") - else() - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - endif() - - # Also detect position independent executables on Linux, - # where "file" gives "shared object ... (uses shared libraries)" - if("${file_ov}" MATCHES "shared object.*\(uses shared libs\)") - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - - else() - message(STATUS "warning: No 'file' command, skipping execute_process...") - endif() - endif() -endfunction() - - -function(gp_item_default_embedded_path item default_embedded_path_var) - - # On Windows and Linux, "embed" prerequisites in the same directory - # as the executable by default: - # - set(path "@executable_path") - set(overridden 0) - - # On the Mac, relative to the executable depending on the type - # of the thing we are embedding: - # - if(APPLE) - # - # The assumption here is that all executables in the bundle will be - # in same-level-directories inside the bundle. The parent directory - # of an executable inside the bundle should be MacOS or a sibling of - # MacOS and all embedded paths returned from here will begin with - # "@executable_path/../" and will work from all executables in all - # such same-level-directories inside the bundle. - # - - # By default, embed things right next to the main bundle executable: - # - set(path "@executable_path/../../Contents/MacOS") - - # Embed .dylibs right next to the main bundle executable: - # - if(item MATCHES "\\.dylib$") - set(path "@executable_path/../MacOS") - set(overridden 1) - endif() - - # Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS): - # - if(NOT overridden) - if(item MATCHES "[^/]+\\.framework/") - set(path "@executable_path/../Frameworks") - set(overridden 1) - endif() - endif() - endif() - - # Provide a hook so that projects can override the default embedded location - # of any given library by whatever logic they choose: - # - if(COMMAND gp_item_default_embedded_path_override) - gp_item_default_embedded_path_override("${item}" path) - endif() - - set(${default_embedded_path_var} "${path}" PARENT_SCOPE) -endfunction() - - -function(gp_resolve_item context item exepath dirs resolved_item_var) - set(resolved 0) - set(resolved_item "${item}") - - # Is it already resolved? - # - if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}") - set(resolved 1) - endif() - - if(NOT resolved) - if(item MATCHES "@executable_path") - # - # @executable_path references are assumed relative to exepath - # - string(REPLACE "@executable_path" "${exepath}" ri "${item}") - get_filename_component(ri "${ri}" ABSOLUTE) - - if(EXISTS "${ri}") - #message(STATUS "info: embedded item exists (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - else() - message(STATUS "warning: embedded item does not exist '${ri}'") - endif() - endif() - endif() - - if(NOT resolved) - if(item MATCHES "@loader_path") - # - # @loader_path references are assumed relative to the - # PATH of the given "context" (presumably another library) - # - get_filename_component(contextpath "${context}" PATH) - string(REPLACE "@loader_path" "${contextpath}" ri "${item}") - get_filename_component(ri "${ri}" ABSOLUTE) - - if(EXISTS "${ri}") - #message(STATUS "info: embedded item exists (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - else() - message(STATUS "warning: embedded item does not exist '${ri}'") - endif() - endif() - endif() - - if(NOT resolved) - if(item MATCHES "@rpath") - # - # @rpath references are relative to the paths built into the binaries with -rpath - # We handle this case like we do for other Unixes. - # - # Two cases of item resolution are considered: - # - # (1) item has been copied into the bundle - # - # (2) item has NOT been copied into the bundle: Since the item can exist in a build or - # install tree outside of the bundle, the item is resolved using its name and the - # passed list of directories. - # - string(REPLACE "@rpath/" "" norpath_item "${item}") - - set(ri "ri-NOTFOUND") - if(EXISTS ${GP_RPATH_DIR}/${norpath_item}) - set(ri ${GP_RPATH_DIR}/${norpath_item}) - set(_msg "'find_file' in GP_RPATH_DIR (${ri})") - else() - get_filename_component(norpath_item_name ${norpath_item} NAME) - find_file(ri "${norpath_item_name}" ${exepath} ${dirs} NO_DEFAULT_PATH) - set(_msg "'find_file' in exepath/dirs (${ri})") - endif() - if(ri) - #message(STATUS "info: ${_msg}") - set(resolved 1) - set(resolved_item "${ri}") - set(ri "ri-NOTFOUND") - endif() - - endif() - endif() - - if(NOT resolved) - set(ri "ri-NOTFOUND") - find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH) - find_file(ri "${item}" ${exepath} ${dirs} /usr/lib) - if(ri) - #message(STATUS "info: 'find_file' in exepath/dirs (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - set(ri "ri-NOTFOUND") - endif() - endif() - - if(NOT resolved) - if(item MATCHES "[^/]+\\.framework/") - set(fw "fw-NOTFOUND") - find_file(fw "${item}" - "~/Library/Frameworks" - "/Library/Frameworks" - "/System/Library/Frameworks" - ) - if(fw) - #message(STATUS "info: 'find_file' found framework (${fw})") - set(resolved 1) - set(resolved_item "${fw}") - set(fw "fw-NOTFOUND") - endif() - endif() - endif() - - # Using find_program on Windows will find dll files that are in the PATH. - # (Converting simple file names into full path names if found.) - # - if(WIN32 AND NOT UNIX) - if(NOT resolved) - set(ri "ri-NOTFOUND") - find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH) - find_program(ri "${item}" PATHS "${exepath};${dirs}") - if(ri) - #message(STATUS "info: 'find_program' in exepath/dirs (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - set(ri "ri-NOTFOUND") - endif() - endif() - endif() - - # Provide a hook so that projects can override item resolution - # by whatever logic they choose: - # - if(COMMAND gp_resolve_item_override) - gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved) - endif() - - if(NOT resolved) - message(STATUS " -warning: cannot resolve item '${item}' - - possible problems: - need more directories? - need to use InstallRequiredSystemLibraries? - run in install tree instead of build tree? -") -# message(STATUS " -#****************************************************************************** -#warning: cannot resolve item '${item}' -# -# possible problems: -# need more directories? -# need to use InstallRequiredSystemLibraries? -# run in install tree instead of build tree? -# -# context='${context}' -# item='${item}' -# exepath='${exepath}' -# dirs='${dirs}' -# resolved_item_var='${resolved_item_var}' -#****************************************************************************** -#") - endif() - - set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE) -endfunction() - -function(gp_resolve_embedded_item context embedded_item exepath resolved_embedded_item_var) - #message(STATUS "**") - set(resolved 0) - set(resolved_embedded_item "${embedded_item}") - - if(embedded_item MATCHES "@executable_path") - string(REPLACE "@executable_path" "${exepath}" resolved_embedded_item "${embedded_item}") - set(resolved 1) - endif() - if(EXISTS "${GP_RPATH_DIR}" AND embedded_item MATCHES "@rpath") - string(REPLACE "@rpath" "${GP_RPATH_DIR}" resolved_embedded_item "${embedded_item}") - set(resolved 1) - endif() - - # Provide a hook so that projects can override embedded item resolution - # by whatever logic they choose: - # - if(COMMAND gp_resolve_embedded_item_override) - gp_resolve_embedded_item_override( - "${context}" "${embedded_item}" "${exepath}" resolved_embedded_item resolved) - endif() - - if(NOT resolved) - message(STATUS " -warning: cannot resolve embedded item '${embedded_item}' - possible problems: - need more directories? - need to use InstallRequiredSystemLibraries? - run in install tree instead of build tree? - - context='${context}' - embedded_item='${embedded_item}' - GP_RPATH_DIR='${GP_RPATH_DIR}' - exepath='${exepath}' - resolved_embedded_item_var='${resolved_embedded_item_var}' -") - endif() - - set(${resolved_embedded_item_var} "${resolved_embedded_item}" PARENT_SCOPE) -endfunction() - -function(gp_resolved_file_type original_file file exepath dirs type_var) - #message(STATUS "**") - - if(NOT IS_ABSOLUTE "${original_file}") - message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file") - endif() - - set(is_embedded 0) - set(is_local 0) - set(is_system 0) - - set(resolved_file "${file}") - - if("${file}" MATCHES "^@(executable_|loader_|r)path") - set(is_embedded 1) - endif() - - if(NOT is_embedded) - if(NOT IS_ABSOLUTE "${file}") - gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file) - endif() - - string(TOLOWER "${original_file}" original_lower) - string(TOLOWER "${resolved_file}" lower) - - if(UNIX) - if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11/|/usr/X11R6/|/usr/bin/)") - set(is_system 1) - endif() - endif() - - if(APPLE) - if(resolved_file MATCHES "^(/System/Library/|/usr/lib/|/opt/X11/)") - set(is_system 1) - endif() - endif() - - if(WIN32) - string(TOLOWER "$ENV{SystemRoot}" sysroot) - string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}") - - string(TOLOWER "$ENV{windir}" windir) - string(REGEX REPLACE "\\\\" "/" windir "${windir}") - - if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)") - set(is_system 1) - endif() - - if(UNIX) - # if cygwin, we can get the properly formed windows paths from cygpath - find_program(CYGPATH_EXECUTABLE cygpath) - - if(CYGPATH_EXECUTABLE) - execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W - OUTPUT_VARIABLE env_windir - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S - OUTPUT_VARIABLE env_sysdir - OUTPUT_STRIP_TRAILING_WHITESPACE) - string(TOLOWER "${env_windir}" windir) - string(TOLOWER "${env_sysdir}" sysroot) - - if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)") - set(is_system 1) - endif() - endif() - endif() - endif() - - if(NOT is_system) - get_filename_component(original_path "${original_lower}" PATH) - get_filename_component(path "${lower}" PATH) - if("${original_path}" STREQUAL "${path}") - set(is_local 1) - else() - string(LENGTH "${original_path}/" original_length) - string(LENGTH "${lower}" path_length) - if(${path_length} GREATER ${original_length}) - string(SUBSTRING "${lower}" 0 ${original_length} path) - if("${original_path}/" STREQUAL "${path}") - set(is_embedded 1) - endif() - endif() - endif() - endif() - endif() - - # Return type string based on computed booleans: - # - set(type "other") - - if(is_system) - set(type "system") - elseif(is_embedded) - set(type "embedded") - elseif(is_local) - set(type "local") - endif() - - #message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'") - #message(STATUS " type: '${type}'") - - if(NOT is_embedded) - if(NOT IS_ABSOLUTE "${resolved_file}") - if(lower MATCHES "^msvc[^/]+dll" AND is_system) - message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'") - else() - message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect") - endif() - endif() - endif() - - # Provide a hook so that projects can override the decision on whether a - # library belongs to the system or not by whatever logic they choose: - # - if(COMMAND gp_resolved_file_type_override) - gp_resolved_file_type_override("${resolved_file}" type) - endif() - - set(${type_var} "${type}" PARENT_SCOPE) - - #message(STATUS "**") -endfunction() - - -function(gp_file_type original_file file type_var) - if(NOT IS_ABSOLUTE "${original_file}") - message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file") - endif() - - get_filename_component(exepath "${original_file}" PATH) - - set(type "") - gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type) - - set(${type_var} "${type}" PARENT_SCOPE) -endfunction() - - -function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs) - set(verbose 0) - set(eol_char "E") - - if(NOT IS_ABSOLUTE "${target}") - message("warning: target '${target}' is not absolute...") - endif() - - if(NOT EXISTS "${target}") - message("warning: target '${target}' does not exist...") - endif() - - set(gp_cmd_paths ${gp_cmd_paths} - "C:/Program Files/Microsoft Visual Studio 9.0/VC/bin" - "C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin" - "C:/Program Files/Microsoft Visual Studio 8/VC/BIN" - "C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN" - "C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN" - "C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN" - "/usr/local/bin" - "/usr/bin" - ) - - # - # - # Try to choose the right tool by default. Caller can set gp_tool prior to - # calling this function to force using a different tool. - # - if("${gp_tool}" STREQUAL "") - set(gp_tool "ldd") - - if(APPLE) - set(gp_tool "otool") - endif() - - if(WIN32 AND NOT UNIX) # This is how to check for cygwin, har! - find_program(gp_dumpbin "dumpbin" PATHS ${gp_cmd_paths}) - if(gp_dumpbin) - set(gp_tool "dumpbin") - else() # Try harder. Maybe we're on MinGW - set(gp_tool "objdump") - endif() - endif() - endif() - - find_program(gp_cmd ${gp_tool} PATHS ${gp_cmd_paths}) - - if(NOT gp_cmd) - message(STATUS "warning: could not find '${gp_tool}' - cannot analyze prerequisites...") - return() - endif() - - set(gp_tool_known 0) - - if("${gp_tool}" STREQUAL "ldd") - set(gp_cmd_args "") - set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$") - set(gp_regex_error "not found${eol_char}$") - set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$") - set(gp_regex_cmp_count 1) - set(gp_tool_known 1) - endif() - - if("${gp_tool}" STREQUAL "otool") - set(gp_cmd_args "-L") - set(gp_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$") - set(gp_regex_error "") - set(gp_regex_fallback "") - set(gp_regex_cmp_count 3) - set(gp_tool_known 1) - endif() - - if("${gp_tool}" STREQUAL "dumpbin") - set(gp_cmd_args "/dependents") - set(gp_regex "^ ([^ ].*[Dd][Ll][Ll])${eol_char}$") - set(gp_regex_error "") - set(gp_regex_fallback "") - set(gp_regex_cmp_count 1) - set(gp_tool_known 1) - set(ENV{VS_UNICODE_OUTPUT} "") # Block extra output from inside VS IDE. - endif() - - if("${gp_tool}" STREQUAL "objdump") - set(gp_cmd_args "-p") - set(gp_regex "^\t*DLL Name: (.*\\.[Dd][Ll][Ll])${eol_char}$") - set(gp_regex_error "") - set(gp_regex_fallback "") - set(gp_regex_cmp_count 1) - set(gp_tool_known 1) - endif() - - if(NOT gp_tool_known) - message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...") - message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'") - message(STATUS "Valid gp_tool values are dumpbin, ldd, objdump and otool.") - return() - endif() - - - if("${gp_tool}" STREQUAL "dumpbin") - # When running dumpbin, it also needs the "Common7/IDE" directory in the - # PATH. It will already be in the PATH if being run from a Visual Studio - # command prompt. Add it to the PATH here in case we are running from a - # different command prompt. - # - get_filename_component(gp_cmd_dir "${gp_cmd}" PATH) - get_filename_component(gp_cmd_dlls_dir "${gp_cmd_dir}/../../Common7/IDE" ABSOLUTE) - # Use cmake paths as a user may have a PATH element ending with a backslash. - # This will escape the list delimiter and create havoc! - if(EXISTS "${gp_cmd_dlls_dir}") - # only add to the path if it is not already in the path - set(gp_found_cmd_dlls_dir 0) - file(TO_CMAKE_PATH "$ENV{PATH}" env_path) - foreach(gp_env_path_element ${env_path}) - if("${gp_env_path_element}" STREQUAL "${gp_cmd_dlls_dir}") - set(gp_found_cmd_dlls_dir 1) - endif() - endforeach() - - if(NOT gp_found_cmd_dlls_dir) - file(TO_NATIVE_PATH "${gp_cmd_dlls_dir}" gp_cmd_dlls_dir) - set(ENV{PATH} "$ENV{PATH};${gp_cmd_dlls_dir}") - endif() - endif() - endif() - # - # - - if("${gp_tool}" STREQUAL "ldd") - set(old_ld_env "$ENV{LD_LIBRARY_PATH}") - foreach(dir ${exepath} ${dirs}) - set(ENV{LD_LIBRARY_PATH} "${dir}:$ENV{LD_LIBRARY_PATH}") - endforeach() - endif() - - - # Track new prerequisites at each new level of recursion. Start with an - # empty list at each level: - # - set(unseen_prereqs) - - # Run gp_cmd on the target: - # - execute_process( - COMMAND ${gp_cmd} ${gp_cmd_args} ${target} - OUTPUT_VARIABLE gp_cmd_ov - ) - - if("${gp_tool}" STREQUAL "ldd") - set(ENV{LD_LIBRARY_PATH} "${old_ld_env}") - endif() - - if(verbose) - message(STATUS "") - message(STATUS "gp_cmd_ov='${gp_cmd_ov}'") - message(STATUS "") - endif() - - get_filename_component(target_dir "${target}" PATH) - - # Convert to a list of lines: - # - string(REGEX REPLACE ";" "\\\\;" candidates "${gp_cmd_ov}") - string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}") - - # check for install id and remove it from list, since otool -L can include a - # reference to itself - set(gp_install_id) - if("${gp_tool}" STREQUAL "otool") - execute_process( - COMMAND otool -D ${target} - OUTPUT_VARIABLE gp_install_id_ov - ) - # second line is install name - string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}") - if(gp_install_id) - # trim - string(REGEX MATCH "[^\n ].*[^\n ]" gp_install_id "${gp_install_id}") - #message("INSTALL ID is \"${gp_install_id}\"") - endif() - endif() - - # Analyze each line for file names that match the regular expression: - # - foreach(candidate ${candidates}) - if("${candidate}" MATCHES "${gp_regex}") - - # Extract information from each candidate: - if(gp_regex_error AND "${candidate}" MATCHES "${gp_regex_error}") - string(REGEX REPLACE "${gp_regex_fallback}" "\\1" raw_item "${candidate}") - else() - string(REGEX REPLACE "${gp_regex}" "\\1" raw_item "${candidate}") - endif() - - if(gp_regex_cmp_count GREATER 1) - string(REGEX REPLACE "${gp_regex}" "\\2" raw_compat_version "${candidate}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}") - endif() - - if(gp_regex_cmp_count GREATER 2) - string(REGEX REPLACE "${gp_regex}" "\\3" raw_current_version "${candidate}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}") - endif() - - # Use the raw_item as the list entries returned by this function. Use the - # gp_resolve_item function to resolve it to an actual full path file if - # necessary. - # - set(item "${raw_item}") - - # Add each item unless it is excluded: - # - set(add_item 1) - - if("${item}" STREQUAL "${gp_install_id}") - set(add_item 0) - endif() - - if(add_item AND ${exclude_system}) - set(type "") - gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type) - - if("${type}" STREQUAL "system") - set(add_item 0) - endif() - endif() - - if(add_item) - list(LENGTH ${prerequisites_var} list_length_before_append) - gp_append_unique(${prerequisites_var} "${item}") - list(LENGTH ${prerequisites_var} list_length_after_append) - - if(${recurse}) - # If item was really added, this is the first time we have seen it. - # Add it to unseen_prereqs so that we can recursively add *its* - # prerequisites... - # - # But first: resolve its name to an absolute full path name such - # that the analysis tools can simply accept it as input. - # - if(NOT list_length_before_append EQUAL list_length_after_append) - gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item) - set(unseen_prereqs ${unseen_prereqs} "${resolved_item}") - endif() - endif() - endif() - else() - if(verbose) - message(STATUS "ignoring non-matching line: '${candidate}'") - endif() - endif() - endforeach() - - list(LENGTH ${prerequisites_var} prerequisites_var_length) - if(prerequisites_var_length GREATER 0) - list(SORT ${prerequisites_var}) - endif() - if(${recurse}) - set(more_inputs ${unseen_prereqs}) - foreach(input ${more_inputs}) - get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}") - endforeach() - endif() - - set(${prerequisites_var} ${${prerequisites_var}} PARENT_SCOPE) -endfunction() - - -function(list_prerequisites target) - if("${ARGV1}" STREQUAL "") - set(all 1) - else() - set(all "${ARGV1}") - endif() - - if("${ARGV2}" STREQUAL "") - set(exclude_system 0) - else() - set(exclude_system "${ARGV2}") - endif() - - if("${ARGV3}" STREQUAL "") - set(verbose 0) - else() - set(verbose "${ARGV3}") - endif() - - set(count 0) - set(count_str "") - set(print_count "${verbose}") - set(print_prerequisite_type "${verbose}") - set(print_target "${verbose}") - set(type_str "") - - get_filename_component(exepath "${target}" PATH) - - set(prereqs "") - get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "") - - if(print_target) - message(STATUS "File '${target}' depends on:") - endif() - - foreach(d ${prereqs}) - math(EXPR count "${count} + 1") - - if(print_count) - set(count_str "${count}. ") - endif() - - if(print_prerequisite_type) - gp_file_type("${target}" "${d}" type) - set(type_str " (${type})") - endif() - - message(STATUS "${count_str}${d}${type_str}") - endforeach() -endfunction() - - -function(list_prerequisites_by_glob glob_arg glob_exp) - message(STATUS "=============================================================================") - message(STATUS "List prerequisites of executables matching ${glob_arg} '${glob_exp}'") - message(STATUS "") - file(${glob_arg} file_list ${glob_exp}) - foreach(f ${file_list}) - is_file_executable("${f}" is_f_executable) - if(is_f_executable) - message(STATUS "=============================================================================") - list_prerequisites("${f}" ${ARGN}) - message(STATUS "") - endif() - endforeach() -endfunction() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 00eac6ca52..bbbff234c4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -20,7 +20,11 @@ else (GIT_CHECKOUT) configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) endif (GIT_CHECKOUT) -find_package(OpenGL REQUIRED) +if (OPENGL_ES) + find_package(OpenGLES REQUIRED) +else() + find_package(OpenGL REQUIRED) +endif() # source files @@ -37,11 +41,11 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager texturemanager resourcesystem + scenemanager keyframemanager texturemanager resourcesystem bulletshapemanager bulletshape niffilemanager objectcache ) add_component_dir (sceneutil - clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller + clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller positionattitudetransform # not used yet #workqueue ) @@ -55,7 +59,7 @@ add_component_dir (nifosg ) add_component_dir (nifbullet - bulletnifloader bulletshapemanager + bulletnifloader ) add_component_dir (to_utf8 @@ -115,7 +119,7 @@ add_component_dir (loadinglistener ) add_component_dir (myguiplatform - myguirendermanager myguidatamanager myguiplatform myguitexture myguiloglistener + myguirendermanager myguidatamanager myguiplatform myguitexture myguiloglistener additivelayer scalinglayer ) add_component_dir (widgets @@ -137,29 +141,31 @@ add_component_dir (version set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) -add_component_qt_dir (contentselector - model/modelitem model/esmfile - model/naturalsort model/contentmodel - model/loadordererror - view/combobox view/contentselector - ) -add_component_qt_dir (config - gamesettings - launchersettings - settingsbase - ) - -add_component_qt_dir (process - processinvoker -) - -if (DESIRED_QT_VERSION MATCHES 4) - include(${QT_USE_FILE}) - QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) -else() - QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) +if (USE_QT) + add_component_qt_dir (contentselector + model/modelitem model/esmfile + model/naturalsort model/contentmodel + model/loadordererror + view/combobox view/contentselector + ) + add_component_qt_dir (config + gamesettings + launchersettings + settingsbase + ) + + add_component_qt_dir (process + processinvoker + ) + + if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) + QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) + else() + QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) + QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) + endif() endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -172,30 +178,46 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) +if (OPENGL_ES) + set(GL_LIB ${OPENGLES_gl_LIBRARY}) +else() + set(GL_LIB ${OPENGL_gl_LIBRARY}) +endif() + target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${OPENSCENEGRAPH_LIBRARIES} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + ${OSGPARTICLE_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGDB_LIBRARIES} + ${OSGVIEWER_LIBRARIES} + ${OSGGA_LIBRARIES} + ${OSGFX_LIBRARIES} + ${OSGANIMATION_LIBRARIES} ${BULLET_LIBRARIES} ${SDL2_LIBRARY} # For MyGUI platform - ${OPENGL_gl_LIBRARY} + ${GL_LIB} ${MYGUI_LIBRARIES} -) + ) if (WIN32) target_link_libraries(components ${Boost_LOCALE_LIBRARY}) endif() -if (DESIRED_QT_VERSION MATCHES 4) - target_link_libraries(components - ${QT_QTCORE_LIBRARY} - ${QT_QTGUI_LIBRARY}) -else() - qt5_use_modules(components Widgets Core) +if (USE_QT) + if (DESIRED_QT_VERSION MATCHES 4) + target_link_libraries(components + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY}) + else() + qt5_use_modules(components Widgets Core) + endif() endif() if (GIT_CHECKOUT) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 401d043d98..2b9a3f6325 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -28,8 +28,6 @@ #include #include -#include "../files/constrainedfilestream.hpp" - using namespace std; using namespace Bsa; diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index a987a86da2..7f02255db2 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -32,7 +32,10 @@ namespace Compiler void ErrorHandler::warning (const std::string& message, const TokenLoc& loc) { - if (mWarningsMode==1) + if (mWarningsMode==1 || + // temporarily change from mode 2 to mode 1 if error downgrading is enabled to + // avoid infinite recursion + (mWarningsMode==2 && mDowngradeErrors)) { ++mWarnings; report (message, loc, WarningMessage); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 6e65c31838..8f7650191a 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -310,7 +310,7 @@ namespace Compiler extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling); extensions.registerInstruction ("betacomment", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); extensions.registerInstruction ("bc", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); - extensions.registerInstruction ("ori", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("ori", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); // 'ori' stands for 'ObjectReferenceInfo' extensions.registerInstruction ("addtolevcreature", "ccl", opcodeAddToLevCreature); extensions.registerInstruction ("removefromlevcreature", "ccl", opcodeRemoveFromLevCreature); extensions.registerInstruction ("addtolevitem", "ccl", opcodeAddToLevItem); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index c1622c3e04..ce1e1e463c 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -411,7 +411,12 @@ namespace Compiler } case Scanner::K_set: mState = SetState; return true; - case Scanner::K_messagebox: mState = MessageState; return true; + + case Scanner::K_messagebox: + + mState = MessageState; + scanner.enableStrictKeywords(); + return true; case Scanner::K_return: diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 3c5bb77475..b370f74a17 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -26,6 +26,7 @@ namespace Compiler if (c=='\n') { + mStrictKeywords = false; mLoc.mColumn = 0; ++mLoc.mLine; mLoc.mLiteral.clear(); @@ -294,8 +295,11 @@ namespace Compiler name = name.substr (1, name.size()-2); // allow keywords enclosed in "" /// \todo optionally disable -// cont = parser.parseName (name, loc, *this); -// return true; + if (mStrictKeywords) + { + cont = parser.parseName (name, loc, *this); + return true; + } } int i = 0; @@ -567,7 +571,8 @@ namespace Compiler Scanner::Scanner (ErrorHandler& errorHandler, std::istream& inputStream, const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), - mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0) + mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), + mStrictKeywords (false) { } @@ -619,4 +624,9 @@ namespace Compiler if (mExtensions) mExtensions->listKeywords (keywords); } + + void Scanner::enableStrictKeywords() + { + mStrictKeywords = true; + } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 8478959785..270782c744 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -37,7 +37,7 @@ namespace Compiler float mPutbackFloat; std::string mPutbackName; TokenLoc mPutbackLoc; - bool mNameStartingWithDigit; + bool mStrictKeywords; public: @@ -117,13 +117,18 @@ namespace Compiler ///< put back a float token void putbackName (const std::string& name, const TokenLoc& loc); - ///< put back a name toekn + ///< put back a name token void putbackKeyword (int keyword, const TokenLoc& loc); ///< put back a keyword token void listKeywords (std::vector& keywords); - ///< Append all known keywords to \a kaywords. + ///< Append all known keywords to \a keywords. + + /// Do not accept keywords in quotation marks anymore. + /// + /// \attention This mode lasts only until the next newline is reached. + void enableStrictKeywords(); }; } diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index ca6bfd80da..a897806c2a 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -454,3 +454,11 @@ QStringList Config::GameSettings::getContentList() const return Config::LauncherSettings::reverse(values(sContentKey)); } +void Config::GameSettings::clear() +{ + mSettings.clear(); + mUserSettings.clear(); + mDataDirs.clear(); + mDataLocal.clear(); +} + diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 992a3e5655..eeac893c23 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -72,6 +72,8 @@ namespace Config void setContentList(const QStringList& fileNames); QStringList getContentList() const; + void clear(); + private: Files::ConfigurationManager &mCfgMgr; diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 1d4b428c91..8f34988262 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -25,8 +25,6 @@ QStringList Config::LauncherSettings::subKeys(const QString &key) QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); - qDebug() << keys; - QRegExp keyRe("(.+)/"); QStringList result; diff --git a/components/config/settingsbase.hpp b/components/config/settingsbase.hpp index c798d2893b..08cd0bfc6f 100644 --- a/components/config/settingsbase.hpp +++ b/components/config/settingsbase.hpp @@ -101,6 +101,11 @@ namespace Config return true; } + void clear() + { + mSettings.clear(); + } + private: Map mSettings; diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 769afee37b..8dc4351f6d 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -7,7 +7,7 @@ #include #include -#include "components/esm/esmreader.hpp" +#include ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon) : QAbstractTableModel(parent), diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 78aa20cd26..87c9cea082 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -1,6 +1,6 @@ #include "contentselector.hpp" -#include "../model/esmfile.hpp" +#include #include diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 4e9fcfb3c8..9f775d5978 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -4,7 +4,7 @@ #include #include "ui_contentselector.h" -#include "../model/contentmodel.hpp" +#include class QSortFilterProxyModel; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 33ac4a91e8..7acaed86d5 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -3,12 +3,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::RefNum::load (ESMReader& esm, bool wide) +void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) - esm.getHNT (*this, "FRMR", 8); + esm.getHNT (*this, tag.c_str(), 8); else - esm.getHNT (mIndex, "FRMR"); + esm.getHNT (mIndex, tag.c_str()); } void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const @@ -24,10 +24,10 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const } -void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) +void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); - loadData(esm); + loadData(esm, isDeleted); } void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) @@ -39,71 +39,98 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) if (esm.isNextSub ("NAM0")) esm.skipHSub(); + blank(); + mRefNum.load (esm, wideRefNum); mRefID = esm.getHNString ("NAME"); } -void ESM::CellRef::loadData(ESMReader &esm) +void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { - // Again, UNAM sometimes appears after NAME and sometimes later. - // Or perhaps this UNAM means something different? - mReferenceBlocked = -1; - esm.getHNOT (mReferenceBlocked, "UNAM"); - - mScale = 1.0; - esm.getHNOT (mScale, "XSCL"); - - mOwner = esm.getHNOString ("ANAM"); - mGlobalVariable = esm.getHNOString ("BNAM"); - mSoul = esm.getHNOString ("XSOL"); - - mFaction = esm.getHNOString ("CNAM"); - mFactionRank = -2; - esm.getHNOT (mFactionRank, "INDX"); - - mGoldValue = 1; - mChargeInt = -1; - mEnchantmentCharge = -1; - - esm.getHNOT (mEnchantmentCharge, "XCHG"); - - esm.getHNOT (mChargeInt, "INTV"); - - esm.getHNOT (mGoldValue, "NAM9"); + isDeleted = false; - // Present for doors that teleport you to another cell. - if (esm.isNextSub ("DODT")) + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - mTeleport = true; - esm.getHT (mDoorDest); - mDestCell = esm.getHNOString ("DNAM"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'U','N','A','M'>::value: + esm.getHT(mReferenceBlocked); + break; + case ESM::FourCC<'X','S','C','L'>::value: + esm.getHT(mScale); + break; + case ESM::FourCC<'A','N','A','M'>::value: + mOwner = esm.getHString(); + break; + case ESM::FourCC<'B','N','A','M'>::value: + mGlobalVariable = esm.getHString(); + break; + case ESM::FourCC<'X','S','O','L'>::value: + mSoul = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mFaction = esm.getHString(); + break; + case ESM::FourCC<'I','N','D','X'>::value: + esm.getHT(mFactionRank); + break; + case ESM::FourCC<'X','C','H','G'>::value: + esm.getHT(mEnchantmentCharge); + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mChargeInt); + break; + case ESM::FourCC<'N','A','M','9'>::value: + esm.getHT(mGoldValue); + break; + case ESM::FourCC<'D','O','D','T'>::value: + esm.getHT(mDoorDest); + mTeleport = true; + break; + case ESM::FourCC<'D','N','A','M'>::value: + mDestCell = esm.getHString(); + break; + case ESM::FourCC<'F','L','T','V'>::value: + esm.getHT(mLockLevel); + break; + case ESM::FourCC<'K','N','A','M'>::value: + mKey = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTrap = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mPos, 24); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.skipHSub(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - else - mTeleport = false; - - mLockLevel = 0; //Set to 0 to indicate no lock - esm.getHNOT (mLockLevel, "FLTV"); - - mKey = esm.getHNOString ("KNAM"); - mTrap = esm.getHNOString ("TNAM"); - - esm.getHNOT (mReferenceBlocked, "UNAM"); - if (esm.isNextSub("FLTV")) // no longer used - esm.skipHSub(); - - esm.getHNOT(mPos, "DATA", 24); - - if (esm.isNextSub("NAM0")) - esm.skipHSub(); } -void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const +void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); esm.writeHNCString("NAME", mRefID); + if (isDeleted) { + esm.writeHNCString("DELE", ""); + return; + } + if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } @@ -134,7 +161,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } if (!inInventory && mLockLevel != 0) { - esm.writeHNT("FLTV", mLockLevel); + esm.writeHNT("FLTV", mLockLevel); } if (!inInventory) @@ -153,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); + mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); @@ -169,7 +196,7 @@ void ESM::CellRef::blank() mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; - + for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index e9959611b3..3c646cc616 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -16,7 +16,7 @@ namespace ESM unsigned int mIndex; int mContentFile; - void load (ESMReader& esm, bool wide = false); + void load (ESMReader& esm, bool wide = false, const std::string& tag = "FRMR"); void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const; @@ -34,7 +34,6 @@ namespace ESM class CellRef { public: - // Reference number // Note: Currently unused for items in containers RefNum mRefNum; @@ -100,14 +99,14 @@ namespace ESM Position mPos; /// Calls loadId and loadData - void load (ESMReader& esm, bool wideRefNum = false); + void load (ESMReader& esm, bool &isDeleted, bool wideRefNum = false); void loadId (ESMReader& esm, bool wideRefNum = false); /// Implicitly called by load - void loadData (ESMReader& esm); + void loadData (ESMReader& esm, bool &isDeleted); - void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 9d605a6af9..66e338d0c4 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -6,15 +6,48 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; -void ESM::DebugProfile::load (ESMReader& esm) +void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { - mDescription = esm.getHNString ("DESC"); - mScriptText = esm.getHNString ("SCRP"); - esm.getHNT (mFlags, "FLAG"); + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','P'>::value: + mScriptText = esm.getHString(); + break; + case ESM::FourCC<'F','L','A','G'>::value: + esm.getHT(mFlags); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } -void ESM::DebugProfile::save (ESMWriter& esm) const +void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const { + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index b54e8ff5fc..c056750a88 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -27,8 +27,8 @@ namespace ESM unsigned int mFlags; - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID). void blank(); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 9f1a935aea..8066b622ae 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -133,5 +133,12 @@ enum RecNameInts REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; +/// Common subrecords +enum SubRecNameInts +{ + SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, + SREC_NAME = ESM::FourCC<'N','A','M','E'>::value +}; + } #endif diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 4be334970e..6ef14a70e0 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -187,6 +187,11 @@ bool ESMReader::peekNextSub(const char *name) return mCtx.subName == name; } +void ESMReader::cacheSubName() +{ + mCtx.subCached = true; +} + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() @@ -276,6 +281,7 @@ void ESMReader::skipRecord() { skip(mCtx.leftRec); mCtx.leftRec = 0; + mCtx.subCached = false; } void ESMReader::getRecHeader(uint32_t &flags) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index c3e6bbbd30..4772aeb6fc 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -185,6 +185,9 @@ public: bool peekNextSub(const char* name); + // Store the current subrecord name for the next call of getSubName() + void cacheSubName(); + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void getSubName(); diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 5bc768f725..c3658f1520 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -6,14 +6,46 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; -void ESM::Filter::load (ESMReader& esm) +void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { - mFilter = esm.getHNString ("FILT"); - mDescription = esm.getHNString ("DESC"); + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + break; + case ESM::FourCC<'F','I','L','T'>::value: + mFilter = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } -void ESM::Filter::save (ESMWriter& esm) const +void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const { + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index bc3dd7bdcb..b1c511ebba 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -18,8 +18,8 @@ namespace ESM std::string mFilter; - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index e7257ae537..b24128ec35 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -31,13 +31,20 @@ void ESM::InventoryState::load (ESMReader &esm) ++index; } - + //Next item is Levelled item while (esm.isNextSub("LEVM")) { + //Get its name std::string id = esm.getHString(); int count; + std::string parentGroup = ""; + //Then get its count esm.getHNT (count, "COUN"); - mLevelledItemMap[id] = count; + //Old save formats don't have information about parent group; check for that + if(esm.isNextSub("LGRP")) + //Newest saves contain parent group + parentGroup = esm.getHString(); + mLevelledItemMap[std::make_pair(id, parentGroup)] = count; } while (esm.isNextSub("MAGI")) @@ -79,10 +86,11 @@ void ESM::InventoryState::save (ESMWriter &esm) const iter->save (esm, true); } - for (std::map::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) + for (std::map, int>::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) { - esm.writeHNString ("LEVM", it->first); + esm.writeHNString ("LEVM", it->first.first); esm.writeHNT ("COUN", it->second); + esm.writeHNString("LGRP", it->first.second); } for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp index d5c317beb9..f4bb0ab487 100644 --- a/components/esm/inventorystate.hpp +++ b/components/esm/inventorystate.hpp @@ -20,7 +20,7 @@ namespace ESM // std::map mEquipmentSlots; - std::map mLevelledItemMap; + std::map, int> mLevelledItemMap; typedef std::map > > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index b5adce5509..b9934d3d39 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -8,14 +8,20 @@ namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; - void Activator::load(ESMReader &esm) + void Activator::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -25,13 +31,29 @@ namespace ESM case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Activator::save(ESMWriter &esm) const + void Activator::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index d9a55023bc..4cc72d5283 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -17,8 +17,8 @@ struct Activator std::string mId, mName, mScript, mModel; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 18db512c0c..2049045024 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -8,16 +8,23 @@ namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; - void Potion::load(ESMReader &esm) + void Potion::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -37,15 +44,31 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing ALDT"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing ALDT subrecord"); } - void Potion::save(ESMWriter &esm) const + void Potion::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index b90a7c448d..9ef390ebd9 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -33,8 +33,8 @@ struct Potion std::string mId, mName, mModel, mIcon, mScript; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index f2c82aacfb..490881fae7 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -8,47 +8,69 @@ namespace ESM { unsigned int Apparatus::sRecordId = REC_APPA; -void Apparatus::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void Apparatus::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'A','A','D','T'>::value: - esm.getHT(mData); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','A','D','T'>::value: + esm.getHT(mData); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing AADT subrecord"); } - if (!hasData) - esm.fail("Missing AADT"); -} -void Apparatus::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNCString("ITEX", mIcon); -} + void Apparatus::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNCString("FNAM", mName); + esm.writeHNT("AADT", mData, 16); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNCString("ITEX", mIcon); + } void Apparatus::blank() { diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index f18b046486..0590d33edf 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -38,8 +38,8 @@ struct Apparatus AADTstruct mData; std::string mId, mModel, mIcon, mScript, mName; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 066551d6fe..f2526554af 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -38,16 +38,23 @@ namespace ESM unsigned int Armor::sRecordId = REC_ARMO; - void Armor::load(ESMReader &esm) + void Armor::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -70,16 +77,32 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Armor::save(ESMWriter &esm) const + void Armor::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 54416fd318..ef3bb734c0 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -96,8 +96,8 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index ed24ded57b..09113e8d1b 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -8,40 +8,61 @@ namespace ESM { unsigned int BodyPart::sRecordId = REC_BODY; - -void BodyPart::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void BodyPart::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mRace = esm.getHString(); - break; - case ESM::FourCC<'B','Y','D','T'>::value: - esm.getHT(mData, 4); - hasData = true; - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'B','Y','D','T'>::value: + esm.getHT(mData, 4); + hasData = true; + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing BYDT subrecord"); } - if (!hasData) - esm.fail("Missing BYDT subrecord"); -} -void BodyPart::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); -} + void BodyPart::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mRace); + esm.writeHNT("BYDT", mData, 4); + } void BodyPart::blank() { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index c48c313056..bf320330ff 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -60,8 +60,8 @@ struct BodyPart BYDTstruct mData; std::string mId, mModel, mRace; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 47f52fc31a..617040be42 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Book::sRecordId = REC_BOOK; - void Book::load(ESMReader &esm) + void Book::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -39,15 +45,31 @@ namespace ESM case ESM::FourCC<'T','E','X','T'>::value: mText = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing BKDT subrecord"); } - void Book::save(ESMWriter &esm) const + void Book::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("BKDT", mData, 20); diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 6211b3e457..3d50356aea 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -28,8 +28,8 @@ struct Book std::string mName, mModel, mIcon, mScript, mEnchant, mText; std::string mId; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index e0cd83ea07..9a4d98bb7b 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -8,41 +8,63 @@ namespace ESM { unsigned int BirthSign::sRecordId = REC_BSGN; -void BirthSign::load(ESMReader &esm) -{ - mPowers.mList.clear(); - while (esm.hasMoreSubs()) + void BirthSign::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mPowers.mList.clear(); + + bool hasName = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'T','N','A','M'>::value: - mTexture = esm.getHString(); - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTexture = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } -} -void BirthSign::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("TNAM", mTexture); - esm.writeHNOCString("DESC", mDescription); + void BirthSign::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - mPowers.save(esm); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("TNAM", mTexture); + esm.writeHNOCString("DESC", mDescription); + + mPowers.save(esm); + } void BirthSign::blank() { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index f91f91c974..24d27a7f85 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -22,8 +22,8 @@ struct BirthSign // List of powers and abilities that come with this birth sign. SpellList mPowers; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 94f4b0b6e7..eb9d6b8d8e 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -52,173 +52,215 @@ namespace ESM return ref.mRefNum == refNum; } -void Cell::load(ESMReader &esm, bool saveContext) -{ - loadData(esm); - loadCell(esm, saveContext); -} - -void Cell::loadCell(ESMReader &esm, bool saveContext) -{ - mRefNumCounter = 0; + void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext) + { + loadNameAndData(esm, isDeleted); + loadCell(esm, saveContext); + } - if (mData.mFlags & Interior) + void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted) { - // Interior cells - if (esm.isNextSub("INTV")) + isDeleted = false; + + blank(); + + bool hasData = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - int waterl; - esm.getHT(waterl); - mWater = (float) waterl; - mWaterInt = true; + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mName = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - else if (esm.isNextSub("WHGT")) + + if (!hasData) + esm.fail("Missing DATA subrecord"); + + mCellId.mPaged = !(mData.mFlags & Interior); + + if (mCellId.mPaged) { - esm.getHT(mWater); + mCellId.mWorldspace = "sys::default"; + mCellId.mIndex.mX = mData.mX; + mCellId.mIndex.mY = mData.mY; + } + else + { + mCellId.mWorldspace = Misc::StringUtils::lowerCase (mName); + mCellId.mIndex.mX = 0; + mCellId.mIndex.mY = 0; } - - // Quasi-exterior cells have a region (which determines the - // weather), pure interior cells have ambient lighting - // instead. - if (mData.mFlags & QuasiEx) - mRegion = esm.getHNOString("RGNN"); - else if (esm.isNextSub("AMBI")) - esm.getHT(mAmbi); } - else + + void Cell::loadCell(ESMReader &esm, bool saveContext) { - // Exterior cells - mRegion = esm.getHNOString("RGNN"); + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + int waterl; + esm.getHT(waterl); + mWater = static_cast(waterl); + mWaterInt = true; + break; + case ESM::FourCC<'W','H','G','T'>::value: + esm.getHT(mWater); + mWaterInt = false; + break; + case ESM::FourCC<'A','M','B','I'>::value: + esm.getHT(mAmbi); + break; + case ESM::FourCC<'R','G','N','N'>::value: + mRegion = esm.getHString(); + break; + case ESM::FourCC<'N','A','M','5'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.getHT(mRefNumCounter); + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } - mMapColor = 0; - esm.getHNOT(mMapColor, "NAM5"); - } - if (esm.isNextSub("NAM0")) { - esm.getHT(mRefNumCounter); + if (saveContext) + { + mContextList.push_back(esm.getContext()); + esm.skipRecord(); + } } - if (saveContext) { + void Cell::postLoad(ESMReader &esm) + { + // Save position of the cell references and move on mContextList.push_back(esm.getContext()); esm.skipRecord(); } -} - -void Cell::loadData(ESMReader &esm) -{ - // Ignore this for now, it might mean we should delete the entire - // cell? - // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! - if (esm.isNextSub("DELE")) { - esm.skipHSub(); - } - - esm.getHNT(mData, "DATA", 12); -} - -void Cell::postLoad(ESMReader &esm) -{ - // Save position of the cell references and move on - mContextList.push_back(esm.getContext()); - esm.skipRecord(); -} -void Cell::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - if (mData.mFlags & Interior) + void Cell::save(ESMWriter &esm, bool isDeleted) const { - if (mWaterInt) { - int water = - (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); - esm.writeHNT("INTV", water); - } else { - esm.writeHNT("WHGT", mWater); + esm.writeHNOCString("NAME", mName); + esm.writeHNT("DATA", mData, 12); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; } - if (mData.mFlags & QuasiEx) - esm.writeHNOCString("RGNN", mRegion); + if (mData.mFlags & Interior) + { + if (mWaterInt) { + int water = + (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); + esm.writeHNT("INTV", water); + } else { + esm.writeHNT("WHGT", mWater); + } + + if (mData.mFlags & QuasiEx) + esm.writeHNOCString("RGNN", mRegion); + else + esm.writeHNT("AMBI", mAmbi, 16); + } else - esm.writeHNT("AMBI", mAmbi, 16); - } - else - { - esm.writeHNOCString("RGNN", mRegion); - if (mMapColor != 0) - esm.writeHNT("NAM5", mMapColor); - } - - if (mRefNumCounter != 0) - esm.writeHNT("NAM0", mRefNumCounter); -} - -void Cell::restore(ESMReader &esm, int iCtx) const -{ - esm.restoreContext(mContextList.at (iCtx)); -} + { + esm.writeHNOCString("RGNN", mRegion); + if (mMapColor != 0) + esm.writeHNT("NAM5", mMapColor); + } -std::string Cell::getDescription() const -{ - if (mData.mFlags & Interior) - { - return mName; + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } - else + + void Cell::restore(ESMReader &esm, int iCtx) const { - std::ostringstream stream; - stream << mData.mX << ", " << mData.mY; - return stream.str(); + esm.restoreContext(mContextList.at (iCtx)); } -} - -bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMoves, MovedCellRef *mref) -{ - // TODO: Try and document reference numbering, I don't think this has been done anywhere else. - if (!esm.hasMoreSubs()) - return false; - // NOTE: We should not need this check. It is a safety check until we have checked - // more plugins, and how they treat these moved references. - if (esm.isNextSub("MVRF")) + std::string Cell::getDescription() const { - if (ignoreMoves) + if (mData.mFlags & Interior) { - esm.getHT (mref->mRefNum.mIndex); - esm.getHNOT (mref->mTarget, "CNDT"); - adjustRefNum (mref->mRefNum, esm); + return mName; } else { - // skip rest of cell record (moved references), they are handled elsewhere - esm.skipRecord(); // skip MVRF, CNDT - return false; + std::ostringstream stream; + stream << mData.mX << ", " << mData.mY; + return stream.str(); } } - ref.load (esm); + bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref) + { + isDeleted = false; - // Identify references belonging to a parent file and adapt the ID accordingly. - adjustRefNum (ref.mRefNum, esm); + // TODO: Try and document reference numbering, I don't think this has been done anywhere else. + if (!esm.hasMoreSubs()) + return false; - if (esm.isNextSub("DELE")) - { - esm.skipHSub(); - deleted = true; - } - else - deleted = false; + // NOTE: We should not need this check. It is a safety check until we have checked + // more plugins, and how they treat these moved references. + if (esm.isNextSub("MVRF")) + { + if (ignoreMoves) + { + esm.getHT (mref->mRefNum.mIndex); + esm.getHNOT (mref->mTarget, "CNDT"); + adjustRefNum (mref->mRefNum, esm); + } + else + { + // skip rest of cell record (moved references), they are handled elsewhere + esm.skipRecord(); // skip MVRF, CNDT + return false; + } + } - return true; -} + if (esm.peekNextSub("FRMR")) + { + ref.load (esm, isDeleted); -bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) -{ - esm.getHT(mref.mRefNum.mIndex); - esm.getHNOT(mref.mTarget, "CNDT"); + // Identify references belonging to a parent file and adapt the ID accordingly. + adjustRefNum (ref.mRefNum, esm); + return true; + } + return false; + } - adjustRefNum (mref.mRefNum, esm); + bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) + { + esm.getHT(mref.mRefNum.mIndex); + esm.getHNOT(mref.mTarget, "CNDT"); - return true; -} + adjustRefNum (mref.mRefNum, esm); + + return true; + } void Cell::blank() { @@ -239,25 +281,8 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mAmbi.mFogDensity = 0; } - CellId Cell::getCellId() const + const CellId& Cell::getCellId() const { - CellId id; - - id.mPaged = !(mData.mFlags & Interior); - - if (id.mPaged) - { - id.mWorldspace = "sys::default"; - id.mIndex.mX = mData.mX; - id.mIndex.mY = mData.mY; - } - else - { - id.mWorldspace = Misc::StringUtils::lowerCase (mName); - id.mIndex.mX = 0; - id.mIndex.mY = 0; - } - - return id; + return mCellId; } } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 12fb8c0684..f92e0b5b70 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -8,6 +8,7 @@ #include "esmcommon.hpp" #include "defs.hpp" #include "cellref.hpp" +#include "cellid.hpp" namespace MWWorld { @@ -18,7 +19,6 @@ namespace ESM { class ESMReader; class ESMWriter; -struct CellId; /* Moved cell reference tracking object. This mainly stores the target cell of the reference, so we can easily know where it has been moved when another @@ -97,6 +97,8 @@ struct Cell std::vector mContextList; // File position; multiple positions for multiple plugin support DATAstruct mData; + CellId mCellId; + AMBIstruct mAmbi; float mWater; // Water level @@ -116,11 +118,11 @@ struct Cell // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. - void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) - void loadData(ESMReader &esm); // Load DATAstruct only - void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references + void load(ESMReader &esm, bool &isDeleted, bool saveContext = true); // Load everything (except references) + void loadNameAndData(ESMReader &esm, bool &isDeleted); // Load NAME and DATAstruct + void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; bool isExterior() const { @@ -159,8 +161,11 @@ struct Cell reuse one memory location without blanking it between calls. */ /// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef. - static bool getNextRef(ESMReader &esm, - CellRef &ref, bool& deleted, bool ignoreMoves = false, MovedCellRef *mref = 0); + static bool getNextRef(ESMReader &esm, + CellRef &ref, + bool &isDeleted, + bool ignoreMoves = false, + MovedCellRef *mref = 0); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. @@ -170,7 +175,7 @@ struct Cell void blank(); ///< Set record to default state (does not touch the ID/index). - CellId getCellId() const; + const CellId& getCellId() const; }; } #endif diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 66acaea721..61960b87db 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -22,7 +22,6 @@ namespace ESM "sSpecializationStealth" }; - int& Class::CLDTstruct::getSkill (int index, bool major) { if (index<0 || index>=5) @@ -39,15 +38,21 @@ namespace ESM return mSkills[index][major ? 1 : 0]; } - void Class::load(ESMReader &esm) + void Class::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -60,15 +65,31 @@ namespace ESM case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CLDT subrecord"); } - void Class::save(ESMWriter &esm) const + void Class::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 972b48e889..833dd6757d 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -73,8 +73,8 @@ struct Class std::string mId, mName, mDescription; CLDTstruct mData; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 5f49b5e709..2ef69e5e91 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -8,16 +8,23 @@ namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; - void Clothing::load(ESMReader &esm) + void Clothing::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -40,16 +47,32 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Clothing::save(ESMWriter &esm) const + void Clothing::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CTDT", mData, 12); diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 6945f224a4..39e5ea6729 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -48,8 +48,8 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 3481189c37..739b0d7db6 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -24,17 +24,24 @@ namespace ESM unsigned int Container::sRecordId = REC_CONT; - void Container::load(ESMReader &esm) + void Container::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mInventory.mList.clear(); + + bool hasName = false; bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -59,18 +66,34 @@ namespace ESM case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasWeight) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasWeight && !isDeleted) esm.fail("Missing CNDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Container::save(ESMWriter &esm) const + void Container::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CNDT", mWeight, 4); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index ab587f9353..4c847f4e2e 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -52,8 +52,8 @@ struct Container int mFlags; InventoryList mInventory; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index fb235e6b3d..b517820896 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -1,5 +1,7 @@ #include "loadcrea.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -8,8 +10,10 @@ namespace ESM { unsigned int Creature::sRecordId = REC_CREA; - void Creature::load(ESMReader &esm) + void Creature::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mAiPackage.mList.clear(); @@ -19,14 +23,19 @@ namespace ESM { mScale = 1.f; mHasAI = false; + + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -72,18 +81,40 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + case ESM::FourCC<'I','N','D','X'>::value: + // seems to occur only in .ESS files, unsure of purpose + int index; + esm.getHT(index); + std::cerr << "Creature::load: Unhandled INDX " << index << std::endl; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Creature::save(ESMWriter &esm) const + void Creature::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 47e5954a5f..a5147619cf 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -91,7 +91,6 @@ struct Creature InventoryList mInventory; SpellList mSpells; - bool mHasAI; AIData mAiData; AIPackageList mAiPackage; @@ -99,8 +98,8 @@ struct Creature const std::vector& getTransport() const; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index f2da8f3775..bc87c4f57d 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -10,121 +10,150 @@ namespace ESM { unsigned int Dialogue::sRecordId = REC_DIAL; -void Dialogue::load(ESMReader &esm) -{ - esm.getSubNameIs("DATA"); - esm.getSubHeader(); - int si = esm.getSubSize(); - if (si == 1) - esm.getT(mType); - else if (si == 4) + void Dialogue::load(ESMReader &esm, bool &isDeleted) { - // These are just markers, their values are not used. - int i; - esm.getT(i); - esm.getHNT(i, "DELE"); - mType = Deleted; + loadId(esm); + loadData(esm, isDeleted); } - else - esm.fail("Unknown sub record size"); -} -void Dialogue::save(ESMWriter &esm) const -{ - if (mType != Deleted) - esm.writeHNT("DATA", mType); - else + void Dialogue::loadId(ESMReader &esm) { - esm.writeHNT("DATA", (int)1); - esm.writeHNT("DELE", (int)1); + mId = esm.getHNString("NAME"); } -} - -void Dialogue::blank() -{ - mInfo.clear(); -} - -void Dialogue::readInfo(ESMReader &esm, bool merge) -{ - const std::string& id = esm.getHNOString("INAM"); - if (!merge || mInfo.empty()) + void Dialogue::loadData(ESMReader &esm, bool &isDeleted) { - ESM::DialInfo info; - info.mId = id; - info.load(esm); - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'D','A','T','A'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size == 1) + { + esm.getT(mType); + } + else + { + esm.skip(size); + } + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + mType = Unknown; + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } - ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); - - std::map::iterator lookup; - - lookup = mLookup.find(id); - - ESM::DialInfo info; - if (lookup != mLookup.end()) - { - it = lookup->second; - - // Merge with existing record. Only the subrecords that are present in - // the new record will be overwritten. - it->load(esm); - info = *it; - - // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record - mInfo.erase(it); - mLookup.erase(lookup); - } - else + void Dialogue::save(ESMWriter &esm, bool isDeleted) const { - info.mId = id; - info.load(esm); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNT("DATA", mType); + } } - if (info.mNext.empty()) - { - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } - if (info.mPrev.empty()) + void Dialogue::blank() { - mLookup[id] = mInfo.insert(mInfo.begin(), info); - return; + mInfo.clear(); } - lookup = mLookup.find(info.mPrev); - if (lookup != mLookup.end()) + void Dialogue::readInfo(ESMReader &esm, bool merge) { - it = lookup->second; + ESM::DialInfo info; + info.loadId(esm); - mLookup[id] = mInfo.insert(++it, info); - return; - } + bool isDeleted = false; + if (!merge || mInfo.empty()) + { + info.loadData(esm, isDeleted); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); - lookup = mLookup.find(info.mNext); - if (lookup != mLookup.end()) - { - it = lookup->second; + return; + } - mLookup[id] = mInfo.insert(it, info); - return; - } + InfoContainer::iterator it = mInfo.end(); - std::cerr << "Failed to insert info " << id << std::endl; -} + LookupMap::iterator lookup; + lookup = mLookup.find(info.mId); -void Dialogue::clearDeletedInfos() -{ - for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) - { - if (it->mQuestStatus == DialInfo::QS_Deleted) - it = mInfo.erase(it); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + // Merge with existing record. Only the subrecords that are present in + // the new record will be overwritten. + it->loadData(esm, isDeleted); + info = *it; + + // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record + mInfo.erase(it); + mLookup.erase(lookup); + } else - ++it; + { + info.loadData(esm, isDeleted); + } + + if (info.mNext.empty()) + { + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); + return; + } + if (info.mPrev.empty()) + { + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); + return; + } + + lookup = mLookup.find(info.mPrev); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); + return; + } + + lookup = mLookup.find(info.mNext); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted); + return; + } + + std::cerr << "Failed to insert info " << info.mId << std::endl; } -} + void Dialogue::clearDeletedInfos() + { + LookupMap::const_iterator current = mLookup.begin(); + LookupMap::const_iterator end = mLookup.end(); + for (; current != end; ++current) + { + if (current->second.second) + { + mInfo.erase(current->second.first); + } + } + mLookup.clear(); + } } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 58598d3536..b80cbd74c3 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -31,7 +31,7 @@ struct Dialogue Greeting = 2, Persuasion = 3, Journal = 4, - Deleted = -1 + Unknown = -1 // Used for deleted dialogues }; std::string mId; @@ -39,17 +39,24 @@ struct Dialogue typedef std::list InfoContainer; - typedef std::map LookupMap; + // Parameters: Info ID, (Info iterator, Deleted flag) + typedef std::map > LookupMap; InfoContainer mInfo; // This is only used during the loading phase to speed up DialInfo merging. LookupMap mLookup; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Dialogue record + void loadId(ESMReader &esm); + ///< Loads NAME sub-record of Dialogue record + void loadData(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Dialogue record, except NAME sub-record - /// Remove all INFOs marked as QS_Deleted from mInfos. + void save(ESMWriter &esm, bool isDeleted = false) const; + + /// Remove all INFOs that are deleted void clearDeletedInfos(); /// Read the next info record diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index f446eed611..6d8c0978c8 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -8,14 +8,20 @@ namespace ESM { unsigned int Door::sRecordId = REC_DOOR; - void Door::load(ESMReader &esm) + void Door::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -31,14 +37,30 @@ namespace ESM case ESM::FourCC<'A','N','A','M'>::value: mCloseSound = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Door::save(ESMWriter &esm) const + void Door::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 3073f4e9de..3afe5d5e4b 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -17,8 +17,8 @@ struct Door std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 54690d9a0b..ae83c63f70 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -8,37 +8,58 @@ namespace ESM { unsigned int Enchantment::sRecordId = REC_ENCH; -void Enchantment::load(ESMReader &esm) -{ - mEffects.mList.clear(); - bool hasData = false; - while (esm.hasMoreSubs()) + void Enchantment::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'E','N','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'E','N','A','M'>::value: - mEffects.add(esm); - break; - default: - esm.fail("Unknown subrecord"); - break; + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'E','N','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEffects.add(esm); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing ENDT subrecord"); } - if (!hasData) - esm.fail("Missing ENDT subrecord"); -} -void Enchantment::save(ESMWriter &esm) const -{ - esm.writeHNT("ENDT", mData, 16); - mEffects.save(esm); -} + void Enchantment::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNT("ENDT", mData, 16); + mEffects.save(esm); + } void Enchantment::blank() { diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index cfcdd4edce..7b93b519c3 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,8 +42,8 @@ struct Enchantment ENDTstruct mData; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 006ca0ce00..75c482d201 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -26,69 +26,92 @@ namespace ESM return mSkills[index]; } -void Faction::load(ESMReader &esm) -{ - mReactions.clear(); - for (int i=0;i<10;++i) - mRanks[i].clear(); - - int rankCounter=0; - bool hasData = false; - while (esm.hasMoreSubs()) + void Faction::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mReactions.clear(); + for (int i=0;i<10;++i) + mRanks[i].clear(); + + int rankCounter = 0; + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','N','A','M'>::value: - if (rankCounter >= 10) - esm.fail("Rank out of range"); - mRanks[rankCounter++] = esm.getHString(); - break; - case ESM::FourCC<'F','A','D','T'>::value: - esm.getHT(mData, 240); - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); - hasData = true; - break; - case ESM::FourCC<'A','N','A','M'>::value: + esm.getSubName(); + switch (esm.retSubName().val) { - std::string faction = esm.getHString(); - int reaction; - esm.getHNT(reaction, "INTV"); - mReactions[faction] = reaction; - break; + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + if (rankCounter >= 10) + esm.fail("Rank out of range"); + mRanks[rankCounter++] = esm.getHString(); + break; + case ESM::FourCC<'F','A','D','T'>::value: + esm.getHT(mData, 240); + if (mData.mIsHidden > 1) + esm.fail("Unknown flag!"); + hasData = true; + break; + case ESM::FourCC<'A','N','A','M'>::value: + { + std::string faction = esm.getHString(); + int reaction; + esm.getHNT(reaction, "INTV"); + mReactions[faction] = reaction; + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } - default: - esm.fail("Unknown subrecord"); } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing FADT subrecord"); } - if (!hasData) - esm.fail("Missing FADT subrecord"); -} -void Faction::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - for (int i = 0; i < 10; i++) + void Faction::save(ESMWriter &esm, bool isDeleted) const { - if (mRanks[i].empty()) - break; + esm.writeHNCString("NAME", mId); - esm.writeHNString("RNAM", mRanks[i], 32); - } + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } - esm.writeHNT("FADT", mData, 240); + esm.writeHNOCString("FNAM", mName); - for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) - { - esm.writeHNString("ANAM", it->first); - esm.writeHNT("INTV", it->second); + for (int i = 0; i < 10; i++) + { + if (mRanks[i].empty()) + break; + + esm.writeHNString("RNAM", mRanks[i], 32); + } + + esm.writeHNT("FADT", mData, 240); + + for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) + { + esm.writeHNString("ANAM", it->first); + esm.writeHNT("INTV", it->second); + } } -} void Faction::blank() { diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 8645e23fd8..cc715d2660 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -62,8 +62,8 @@ struct Faction // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index a78ed1a1be..72ecce503c 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,19 +1,42 @@ #include "loadglob.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Global::sRecordId = REC_GLOB; - void Global::load (ESMReader &esm) + void Global::load (ESMReader &esm, bool &isDeleted) { - mValue.read (esm, ESM::Variant::Format_Global); + isDeleted = false; + + mId = esm.getHNString ("NAME"); + + if (esm.isNextSub ("DELE")) + { + esm.skipHSub(); + isDeleted = true; + } + else + { + mValue.read (esm, ESM::Variant::Format_Global); + } } - void Global::save (ESMWriter &esm) const + void Global::save (ESMWriter &esm, bool isDeleted) const { - mValue.write (esm, ESM::Variant::Format_Global); + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString ("DELE", ""); + } + else + { + mValue.write (esm, ESM::Variant::Format_Global); + } } void Global::blank() diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index cc5dbbdcfc..0533cc95ea 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -24,8 +24,8 @@ struct Global std::string mId; Variant mValue; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 21d66339a9..1ebb002e68 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,18 +1,24 @@ #include "loadgmst.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int GameSetting::sRecordId = REC_GMST; - void GameSetting::load (ESMReader &esm) + void GameSetting::load (ESMReader &esm, bool &isDeleted) { + isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) + + mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); } - void GameSetting::save (ESMWriter &esm) const + void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { + esm.writeHNCString("NAME", mId); mValue.write (esm, ESM::Variant::Format_Gmst); } diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index d9d9048b61..73a723e810 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -26,7 +26,7 @@ struct GameSetting Variant mValue; - void load(ESMReader &esm); + void load(ESMReader &esm, bool &isDeleted); /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). @@ -39,7 +39,7 @@ struct GameSetting std::string getString() const; ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index a2bade1c5e..246baf0265 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -8,156 +8,140 @@ namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; -void DialInfo::load(ESMReader &esm) -{ - mQuestStatus = QS_None; - mFactionLess = false; - - mPrev = esm.getHNString("PNAM"); - mNext = esm.getHNString("NNAM"); - - // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings - mSelects.clear(); - - // Not present if deleted - if (esm.isNextSub("DATA")) { - esm.getHT(mData, 12); - } - - if (!esm.hasMoreSubs()) - return; - - // What follows is somewhat spaghetti-ish, but it's worth if for - // an extra speedup. INFO is by far the most common record type. - - // subName is a reference to the original, so it changes whenever - // a new sub name is read. esm.isEmptyOrGetName() will get the - // next name for us, or return true if there are no more records. - esm.getSubName(); - const NAME &subName = esm.retSubName(); - - if (subName.val == REC_ONAM) + void DialInfo::load(ESMReader &esm, bool &isDeleted) { - mActor = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_RNAM) - { - mRace = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_CNAM) - { - mClass = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; + loadId(esm); + loadData(esm, isDeleted); } - if (subName.val == REC_FNAM) + void DialInfo::loadId(ESMReader &esm) { - mFaction = esm.getHString(); - if (mFaction == "FFFF") - mFactionLess = true; - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_ANAM) - { - mCell = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_DNAM) - { - mPcFaction = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_SNAM) - { - mSound = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_NAME) - { - mResponse = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; + mId = esm.getHNString("INAM"); } - while (subName.val == REC_SCVR) + void DialInfo::loadData(ESMReader &esm, bool &isDeleted) { - SelectStruct ss; + isDeleted = false; - ss.mSelectRule = esm.getHString(); - - ss.mValue.read (esm, Variant::Format_Info); + mQuestStatus = QS_None; + mFactionLess = false; - mSelects.push_back(ss); + mPrev = esm.getHNString("PNAM"); + mNext = esm.getHNString("NNAM"); - if (esm.isEmptyOrGetName()) - return; - } + // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings + mSelects.clear(); - if (subName.val == REC_BNAM) - { - mResultScript = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + break; + case ESM::FourCC<'O','N','A','M'>::value: + mActor = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mClass = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + { + mFaction = esm.getHString(); + if (mFaction == "FFFF") + { + mFactionLess = true; + } + break; + } + case ESM::FourCC<'A','N','A','M'>::value: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','N','A','M'>::value: + mPcFaction = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mSound = esm.getHString(); + break; + case ESM::SREC_NAME: + mResponse = esm.getHString(); + break; + case ESM::FourCC<'S','C','V','R'>::value: + { + SelectStruct ss; + ss.mSelectRule = esm.getHString(); + ss.mValue.read(esm, Variant::Format_Info); + mSelects.push_back(ss); + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mResultScript = esm.getHString(); + break; + case ESM::FourCC<'Q','S','T','N'>::value: + mQuestStatus = QS_Name; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','F'>::value: + mQuestStatus = QS_Finished; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','R'>::value: + mQuestStatus = QS_Restart; + esm.skipRecord(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } - if (subName.val == REC_QSTN) - mQuestStatus = QS_Name; - else if (subName.val == REC_QSTF) - mQuestStatus = QS_Finished; - else if (subName.val == REC_QSTR) - mQuestStatus = QS_Restart; - else if (subName.val == REC_DELE) - mQuestStatus = QS_Deleted; - else - esm.fail( - "Don't know what to do with " + subName.toString() - + " in INFO " + mId); - - if (mQuestStatus != QS_None) - // Skip rest of record - esm.skipRecord(); -} - -void DialInfo::save(ESMWriter &esm) const -{ - esm.writeHNCString("PNAM", mPrev); - esm.writeHNCString("NNAM", mNext); - esm.writeHNT("DATA", mData, 12); - esm.writeHNOCString("ONAM", mActor); - esm.writeHNOCString("RNAM", mRace); - esm.writeHNOCString("CNAM", mClass); - esm.writeHNOCString("FNAM", mFaction); - esm.writeHNOCString("ANAM", mCell); - esm.writeHNOCString("DNAM", mPcFaction); - esm.writeHNOCString("SNAM", mSound); - esm.writeHNOString("NAME", mResponse); - - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) + void DialInfo::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write (esm, Variant::Format_Info); - } - - esm.writeHNOString("BNAM", mResultScript); + esm.writeHNCString("INAM", mId); + esm.writeHNCString("PNAM", mPrev); + esm.writeHNCString("NNAM", mNext); - switch(mQuestStatus) - { - case QS_Name: esm.writeHNT("QSTN",'\1'); break; - case QS_Finished: esm.writeHNT("QSTF", '\1'); break; - case QS_Restart: esm.writeHNT("QSTR", '\1'); break; - case QS_Deleted: esm.writeHNT("DELE", '\1'); break; - default: break; + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNT("DATA", mData, 12); + esm.writeHNOCString("ONAM", mActor); + esm.writeHNOCString("RNAM", mRace); + esm.writeHNOCString("CNAM", mClass); + esm.writeHNOCString("FNAM", mFaction); + esm.writeHNOCString("ANAM", mCell); + esm.writeHNOCString("DNAM", mPcFaction); + esm.writeHNOCString("SNAM", mSound); + esm.writeHNOString("NAME", mResponse); + + for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) + { + esm.writeHNString("SCVR", it->mSelectRule); + it->mValue.write (esm, Variant::Format_Info); + } + + esm.writeHNOString("BNAM", mResultScript); + + switch(mQuestStatus) + { + case QS_Name: esm.writeHNT("QSTN",'\1'); break; + case QS_Finished: esm.writeHNT("QSTF", '\1'); break; + case QS_Restart: esm.writeHNT("QSTR", '\1'); break; + default: break; + } } -} void DialInfo::blank() { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 54003b0d96..8123a9ace8 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -59,8 +59,7 @@ struct DialInfo QS_None = 0, QS_Name = 1, QS_Finished = 2, - QS_Restart = 3, - QS_Deleted + QS_Restart = 3 }; // Rules for when to include this item in the final list of options @@ -106,8 +105,14 @@ struct DialInfo REC_DELE = 0x454c4544 }; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Info record + void loadId(ESMReader &esm); + ///< Loads only Id of Info record (INAM sub-record) + void loadData(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Info record, except INAM sub-record + + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 7e0cc3168d..e00e73ab0b 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; - void Ingredient::load(ESMReader &esm) + void Ingredient::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,12 +39,19 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing IRDT subrecord"); // horrible hack to fix broken data in records @@ -65,8 +78,16 @@ namespace ESM } } - void Ingredient::save(ESMWriter &esm) const + void Ingredient::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 5846a97809..c0f4450238 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -31,8 +31,8 @@ struct Ingredient IRDTstruct mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 784cfd4078..e7be033219 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -10,212 +10,251 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) const -{ - if (mDataTypes & Land::DATA_VNML) { - esm.writeHNT("VNML", mNormals, sizeof(mNormals)); - } - if (mDataTypes & Land::DATA_VHGT) { - VHGT offsets; - offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mUnk1; - offsets.mUnk2 = mUnk2; - - 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); - - float prevX = prevY = mHeights[number]; - ++number; - - for (int j = 1; j < LAND_SIZE; ++j) { - diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + void Land::LandData::save(ESMWriter &esm) const + { + if (mDataTypes & Land::DATA_VNML) { + esm.writeHNT("VNML", mNormals, sizeof(mNormals)); + } + if (mDataTypes & Land::DATA_VHGT) { + VHGT offsets; + offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; + offsets.mUnk1 = mUnk1; + offsets.mUnk2 = mUnk2; + + 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 = mHeights[number]; + float prevX = prevY = mHeights[number]; ++number; + + for (int j = 1; j < LAND_SIZE; ++j) { + diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + offsets.mHeightData[number] = + (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); + + prevX = mHeights[number]; + ++number; + } } + esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + } + if (mDataTypes & Land::DATA_WNAM) { + esm.writeHNT("WNAM", mWnam, 81); + } + if (mDataTypes & Land::DATA_VCLR) { + esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); + } + if (mDataTypes & Land::DATA_VTEX) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + transposeTextureData(mTextures, vtex); + esm.writeHNT("VTEX", vtex, sizeof(vtex)); } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); - } - if (mDataTypes & Land::DATA_WNAM) { - esm.writeHNT("WNAM", mWnam, 81); } - if (mDataTypes & Land::DATA_VCLR) { - esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); + + Land::Land() + : mFlags(0) + , mX(0) + , mY(0) + , mPlugin(0) + , mEsm(NULL) + , mDataTypes(0) + , mDataLoaded(false) + , mLandData(NULL) + { } - if (mDataTypes & Land::DATA_VTEX) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mTextures, vtex); - esm.writeHNT("VTEX", vtex, sizeof(vtex)); + + void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) + { + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; } -} -void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) -{ - int readPos = 0; //bit ugly, but it works - for ( int y1 = 0; y1 < 4; y1++ ) - for ( int x1 = 0; x1 < 4; x1++ ) - for ( int y2 = 0; y2 < 4; y2++) - for ( int x2 = 0; x2 < 4; x2++ ) - out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; -} + Land::~Land() + { + delete mLandData; + } -Land::Land() - : mFlags(0) - , mX(0) - , mY(0) - , mPlugin(0) - , mEsm(NULL) - , mDataTypes(0) - , mDataLoaded(false) - , mLandData(NULL) -{ -} + void Land::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -Land::~Land() -{ - delete mLandData; -} + mEsm = &esm; + mPlugin = mEsm->getIndex(); -void Land::load(ESMReader &esm) -{ - mEsm = &esm; - mPlugin = mEsm->getIndex(); + bool hasLocation = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + esm.getSubHeaderIs(8); + esm.getT(mX); + esm.getT(mY); + hasLocation = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } - // Get the grid location - esm.getSubNameIs("INTV"); - esm.getSubHeaderIs(8); - esm.getT(mX); - esm.getT(mY); + if (!hasLocation) + esm.fail("Missing INTV subrecord"); - esm.getHNT(mFlags, "DATA"); + mContext = esm.getContext(); - // Store the file position - mContext = esm.getContext(); + // Skip the land data here. Load it when the cell is loaded. + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'V','N','M','L'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VNML; + break; + case ESM::FourCC<'V','H','G','T'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VHGT; + break; + case ESM::FourCC<'W','N','A','M'>::value: + esm.skipHSub(); + mDataTypes |= DATA_WNAM; + break; + case ESM::FourCC<'V','C','L','R'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VCLR; + break; + case ESM::FourCC<'V','T','E','X'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VTEX; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } - // Skip these here. Load the actual data when the cell is loaded. - if (esm.isNextSub("VNML")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VNML; - } - if (esm.isNextSub("VHGT")) - { - esm.skipHSubSize(4232); - mDataTypes |= DATA_VHGT; - } - if (esm.isNextSub("WNAM")) - { - esm.skipHSubSize(81); - mDataTypes |= DATA_WNAM; - } - if (esm.isNextSub("VCLR")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VCLR; - } - if (esm.isNextSub("VTEX")) - { - esm.skipHSubSize(512); - mDataTypes |= DATA_VTEX; + mDataLoaded = 0; + mLandData = NULL; } - mDataLoaded = 0; - mLandData = NULL; -} + void Land::save(ESMWriter &esm, bool isDeleted) const + { + esm.startSubRecord("INTV"); + esm.writeT(mX); + esm.writeT(mY); + esm.endRecord("INTV"); -void Land::save(ESMWriter &esm) const -{ - esm.startSubRecord("INTV"); - esm.writeT(mX); - esm.writeT(mY); - esm.endRecord("INTV"); + esm.writeHNT("DATA", mFlags); - esm.writeHNT("DATA", mFlags); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } -void Land::loadData(int flags) const -{ - // Try to load only available data - flags = flags & mDataTypes; - // Return if all required data is loaded - if ((mDataLoaded & flags) == flags) { - return; - } - // Create storage if nothing is loaded - if (mLandData == NULL) { - mLandData = new LandData; - mLandData->mDataTypes = mDataTypes; + if (mLandData) + { + mLandData->save(esm); + } } - mEsm->restoreContext(mContext); - if (mEsm->isNextSub("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); - } + void Land::loadData(int flags) const + { + // Try to load only available data + flags = flags & mDataTypes; + // Return if all required data is loaded + if ((mDataLoaded & flags) == flags) { + return; + } + // Create storage if nothing is loaded + if (mLandData == NULL) { + mLandData = new LandData; + mLandData->mDataTypes = mDataTypes; + } + mEsm->restoreContext(mContext); - if (mEsm->isNextSub("VHGT")) { - static VHGT vhgt; - if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { - float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + if (mEsm->isNextSub("VNML")) { + condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + } - mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + if (mEsm->isNextSub("VHGT")) { + static VHGT vhgt; + if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + float rowOffset = vhgt.mHeightOffset; + for (int y = 0; y < LAND_SIZE; y++) { + rowOffset += vhgt.mHeightData[y * LAND_SIZE]; - float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + + float colOffset = rowOffset; + for (int x = 1; x < LAND_SIZE; x++) { + colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; + mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + } } + mLandData->mUnk1 = vhgt.mUnk1; + mLandData->mUnk2 = vhgt.mUnk2; } - mLandData->mUnk1 = vhgt.mUnk1; - mLandData->mUnk2 = vhgt.mUnk2; } - } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); - } - if (mEsm->isNextSub("VCLR")) - condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - if (mEsm->isNextSub("VTEX")) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { - LandData::transposeTextureData(vtex, mLandData->mTextures); + if (mEsm->isNextSub("WNAM")) { + condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + } + if (mEsm->isNextSub("VCLR")) + condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + if (mEsm->isNextSub("VTEX")) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { + LandData::transposeTextureData(vtex, mLandData->mTextures); + } } } -} -void Land::unloadData() -{ - if (mDataLoaded) + void Land::unloadData() { - delete mLandData; - mLandData = NULL; - mDataLoaded = 0; + if (mDataLoaded) + { + delete mLandData; + mLandData = NULL; + mDataLoaded = 0; + } } -} -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const -{ - if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { - mEsm->getHExact(ptr, size); - mDataLoaded |= dataFlag; - return true; + bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const + { + if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { + mEsm->getHExact(ptr, size); + mDataLoaded |= dataFlag; + return true; + } + mEsm->skipHSubSize(size); + return false; } - mEsm->skipHSubSize(size); - return false; -} -bool Land::isDataLoaded(int flags) const -{ - return (mDataLoaded & flags) == (flags & mDataTypes); -} + bool Land::isDataLoaded(int flags) const + { + return (mDataLoaded & flags) == (flags & mDataTypes); + } Land::Land (const Land& land) : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 8ec4f74ea0..65ac88cda3 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -106,8 +106,8 @@ struct Land static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank() {} diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index ca5c5d74d8..8c0d503244 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -6,42 +6,84 @@ namespace ESM { - - void LevelledListBase::load(ESMReader &esm) + void LevelledListBase::load(ESMReader &esm, bool &isDeleted) { - esm.getHNT(mFlags, "DATA"); - esm.getHNT(mChanceNone, "NNAM"); + isDeleted = false; - if (esm.isNextSub("INDX")) - { - int len; - esm.getHT(len); - mList.resize(len); - } - else + bool hasName = false; + bool hasList = false; + while (esm.hasMoreSubs()) { - // Original engine ignores rest of the record, even if there are items following - mList.clear(); - esm.skipRecord(); - return; - } + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'N','N','A','M'>::value: + esm.getHT(mChanceNone); + break; + case ESM::FourCC<'I','N','D','X'>::value: + { + int length = 0; + esm.getHT(length); + mList.resize(length); - // If this levelled list was already loaded by a previous content file, - // we overwrite the list. Merging lists should probably be left to external tools, - // with the limited amount of information there is in the records, all merging methods - // will be flawed in some way. For a proper fix the ESM format would have to be changed - // to actually track list changes instead of including the whole list for every file - // that does something with that list. + // If this levelled list was already loaded by a previous content file, + // we overwrite the list. Merging lists should probably be left to external tools, + // with the limited amount of information there is in the records, all merging methods + // will be flawed in some way. For a proper fix the ESM format would have to be changed + // to actually track list changes instead of including the whole list for every file + // that does something with that list. + for (size_t i = 0; i < mList.size(); i++) + { + LevelItem &li = mList[i]; + li.mId = esm.getHNString(mRecName); + esm.getHNT(li.mLevel, "INTV"); + } - for (size_t i = 0; i < mList.size(); i++) - { - LevelItem &li = mList[i]; - li.mId = esm.getHNString(mRecName); - esm.getHNT(li.mLevel, "INTV"); + hasList = true; + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + { + if (!hasList) + { + // Original engine ignores rest of the record, even if there are items following + mList.clear(); + esm.skipRecord(); + } + else + { + esm.fail("Unknown subrecord"); + } + break; + } + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void LevelledListBase::save(ESMWriter &esm) const + + void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index dc6fcda5ec..ed4131c165 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -36,8 +36,8 @@ struct LevelledListBase std::vector mList; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 26d70d964a..20c700b238 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Light::sRecordId = REC_LIGH; - void Light::load(ESMReader &esm) + void Light::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -36,15 +42,31 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing LHDT subrecord"); } - void Light::save(ESMWriter &esm) const + void Light::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index ed8c36665c..8509c64b6d 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -47,8 +47,8 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 2747a6f787..fc6af86990 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; - void Lockpick::load(ESMReader &esm) + void Lockpick::load(ESMReader &esm, bool &isDeleted) { - bool hasData = true; + isDeleted = false; + + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,16 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing LKDT subrecord"); } - void Lockpick::save(ESMWriter &esm) const + void Lockpick::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 0d678cd64c..9db41af978 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -27,8 +27,8 @@ struct Lockpick Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index c3e2d50ff5..a42ae7c5bf 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -8,21 +8,58 @@ namespace ESM { unsigned int LandTexture::sRecordId = REC_LTEX; -void LandTexture::load(ESMReader &esm) -{ - esm.getHNT(mIndex, "INTV"); - mTexture = esm.getHNString("DATA"); -} -void LandTexture::save(ESMWriter &esm) const -{ - esm.writeHNT("INTV", mIndex); - esm.writeHNCString("DATA", mTexture); -} + void LandTexture::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -void LandTexture::blank() -{ - mTexture.clear(); - mIndex = -1; -} + bool hasName = false; + bool hasIndex = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mIndex); + hasIndex = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + mTexture = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasIndex) + esm.fail("Missing INTV subrecord"); + } + void LandTexture::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + esm.writeHNT("INTV", mIndex); + esm.writeHNCString("DATA", mTexture); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + } + void LandTexture::blank() + { + mTexture.clear(); + mIndex = -1; + } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 50a7881054..2cb5abf0c8 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -34,11 +34,11 @@ struct LandTexture std::string mId, mTexture; int mIndex; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; + void blank(); ///< Set record to default state (does not touch the ID). - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 6f859ab3cd..eef58aa2f1 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -189,8 +189,10 @@ namespace ESM { unsigned int MagicEffect::sRecordId = REC_MGEF; -void MagicEffect::load(ESMReader &esm) +void MagicEffect::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) + esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); @@ -209,8 +211,7 @@ void MagicEffect::load(ESMReader &esm) while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); @@ -250,7 +251,7 @@ void MagicEffect::load(ESMReader &esm) } } } -void MagicEffect::save(ESMWriter &esm) const +void MagicEffect::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index eeb4268c2c..a52ed797f9 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -96,8 +96,8 @@ struct MagicEffect // sMagicCreature04ID/05ID. int mIndex; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID/index). void blank(); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 81c094f2bc..199b4e3b2c 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; - void Miscellaneous::load(ESMReader &esm) + void Miscellaneous::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,14 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing MCDT subrecord"); } - void Miscellaneous::save(ESMWriter &esm) const + void Miscellaneous::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("MCDT", mData, 12); diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 6e0b4e01b8..e7a3239042 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -32,8 +32,8 @@ struct Miscellaneous std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 67a437176c..e524e6a7cc 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -8,24 +8,30 @@ namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; - void NPC::load(ESMReader &esm) + void NPC::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mSpells.mList.clear(); mInventory.mList.clear(); mTransport.mList.clear(); mAiPackage.mList.clear(); + mHasAI = false; + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; - mHasAI = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -92,17 +98,33 @@ namespace ESM case AI_CNDT: mAiPackage.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void NPC::save(ESMWriter &esm) const + void NPC::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNCString("RNAM", mRace); diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 9bda9560e1..5b89f40554 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -130,8 +130,8 @@ struct NPC // body parts std::string mHair, mHead; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; bool isMale() const; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index fc0974c9d8..69ee60eeb8 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -32,98 +32,130 @@ namespace ESM { } -void Pathgrid::load(ESMReader &esm) -{ - esm.getHNT(mData, "DATA", 12); - mCell = esm.getHNString("NAME"); + void Pathgrid::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; - mPoints.clear(); - mEdges.clear(); + mPoints.clear(); + mEdges.clear(); - // keep track of total connections so we can reserve edge vector size - int edgeCount = 0; + // keep track of total connections so we can reserve edge vector size + int edgeCount = 0; - if (esm.isNextSub("PGRP")) - { - esm.getSubHeader(); - int size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * s2 (path points) - if (size != static_cast (sizeof(Point) * mData.mS2)) - esm.fail("Path point subrecord size mismatch"); - else + bool hasData = false; + while (esm.hasMoreSubs()) { - int pointCount = mData.mS2; - mPoints.reserve(pointCount); - for (int i = 0; i < pointCount; ++i) + esm.getSubName(); + switch (esm.retSubName().val) { - Point p; - esm.getExact(&p, sizeof(Point)); - mPoints.push_back(p); - edgeCount += p.mConnectionNum; + case ESM::SREC_NAME: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'P','G','R','P'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + // Check that the sizes match up. Size = 16 * s2 (path points) + if (size != static_cast (sizeof(Point) * mData.mS2)) + esm.fail("Path point subrecord size mismatch"); + else + { + int pointCount = mData.mS2; + mPoints.reserve(pointCount); + for (int i = 0; i < pointCount; ++i) + { + Point p; + esm.getExact(&p, sizeof(Point)); + mPoints.push_back(p); + edgeCount += p.mConnectionNum; + } + } + break; + } + case ESM::FourCC<'P','G','R','C'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size % sizeof(int) != 0) + esm.fail("PGRC size not a multiple of 4"); + else + { + int rawConnNum = size / sizeof(int); + std::vector rawConnections; + rawConnections.reserve(rawConnNum); + for (int i = 0; i < rawConnNum; ++i) + { + int currentValue; + esm.getT(currentValue); + rawConnections.push_back(currentValue); + } + + std::vector::const_iterator rawIt = rawConnections.begin(); + int pointIndex = 0; + mEdges.reserve(edgeCount); + for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) + { + unsigned char connectionNum = (*it).mConnectionNum; + for (int i = 0; i < connectionNum; ++i) { + Edge edge; + edge.mV0 = pointIndex; + edge.mV1 = *rawIt; + ++rawIt; + mEdges.push_back(edge); + } + } + } + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } + + if (!hasData) + esm.fail("Missing DATA subrecord"); } - if (esm.isNextSub("PGRC")) + void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { - esm.getSubHeader(); - int size = esm.getSubSize(); - if (size % sizeof(int) != 0) - esm.fail("PGRC size not a multiple of 4"); - else - { - int rawConnNum = size / sizeof(int); - std::vector rawConnections; - rawConnections.reserve(rawConnNum); - for (int i = 0; i < rawConnNum; ++i) - { - int currentValue; - esm.getT(currentValue); - rawConnections.push_back(currentValue); - } + esm.writeHNCString("NAME", mCell); + esm.writeHNT("DATA", mData, 12); - std::vector::const_iterator rawIt = rawConnections.begin(); - int pointIndex = 0; - mEdges.reserve(edgeCount); - for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) - { - unsigned char connectionNum = (*it).mConnectionNum; - for (int i = 0; i < connectionNum; ++i) { - Edge edge; - edge.mV0 = pointIndex; - edge.mV1 = *rawIt; - ++rawIt; - mEdges.push_back(edge); - } - } + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; } - } -} -void Pathgrid::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - esm.writeHNCString("NAME", mCell); - if (!mPoints.empty()) - { - esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + if (!mPoints.empty()) { - esm.writeT(*it); + esm.startSubRecord("PGRP"); + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + { + esm.writeT(*it); + } + esm.endRecord("PGRP"); } - esm.endRecord("PGRP"); - } - if (!mEdges.empty()) - { - esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + if (!mEdges.empty()) { - esm.writeT(it->mV1); + esm.startSubRecord("PGRC"); + for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + { + esm.writeT(it->mV1); + } + esm.endRecord("PGRC"); } - esm.endRecord("PGRC"); } -} void Pathgrid::blank() { diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index f33ccbedf9..d1003eb865 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -53,8 +53,8 @@ struct Pathgrid typedef std::vector EdgeList; EdgeList mEdges; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index c5f80c5844..baf466490b 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Probe::sRecordId = REC_PROB; - void Probe::load(ESMReader &esm) + void Probe::load(ESMReader &esm, bool &isDeleted) { - bool hasData = true; + isDeleted = false; + + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,16 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing PBDT subrecord"); } - void Probe::save(ESMWriter &esm) const + void Probe::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index c737757aa3..da203b456b 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -27,8 +27,8 @@ struct Probe Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e88454d4c1..a371751446 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -18,44 +18,65 @@ namespace ESM return static_cast(male ? mMale : mFemale); } -void Race::load(ESMReader &esm) -{ - mPowers.mList.clear(); + void Race::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + mPowers.mList.clear(); + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','A','D','T'>::value: + esm.getHT(mData, 140); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + } + } - bool hasData = false; - while (esm.hasMoreSubs()) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RADT subrecord"); + } + void Race::save(ESMWriter &esm, bool isDeleted) const { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + esm.writeHNCString("NAME", mId); + + if (isDeleted) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','A','D','T'>::value: - esm.getHT(mData, 140); - hasData = true; - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.writeHNCString("DELE", ""); + return; } + + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("RADT", mData, 140); + mPowers.save(esm); + esm.writeHNOString("DESC", mDescription); } - if (!hasData) - esm.fail("Missing RADT subrecord"); -} -void Race::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RADT", mData, 140); - mPowers.save(esm); - esm.writeHNOString("DESC", mDescription); -} void Race::blank() { diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 553d2e68b1..bf0573075c 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -68,8 +68,8 @@ struct Race std::string mId, mName, mDescription; SpellList mPowers; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 1b08b72172..e5382f50b6 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -8,62 +8,102 @@ namespace ESM { unsigned int Region::sRecordId = REC_REGN; -void Region::load(ESMReader &esm) -{ - mName = esm.getHNOString("FNAM"); - - esm.getSubNameIs("WEAT"); - esm.getSubHeader(); - if (esm.getVer() == VER_12) + void Region::load(ESMReader &esm, bool &isDeleted) { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData) - 2); - } - else if (esm.getVer() == VER_13) - { - // May include the additional two bytes (but not necessarily) - if (esm.getSubSize() == sizeof(mData)) - esm.getExact(&mData, sizeof(mData)); - else + isDeleted = false; + + bool hasName = false; + while (esm.hasMoreSubs()) { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData)-2); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'W','E','A','T'>::value: + { + esm.getSubHeader(); + if (esm.getVer() == VER_12) + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData) - 2); + } + else if (esm.getVer() == VER_13) + { + // May include the additional two bytes (but not necessarily) + if (esm.getSubSize() == sizeof(mData)) + { + esm.getExact(&mData, sizeof(mData)); + } + else + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData)-2); + } + } + else + { + esm.fail("Don't know what to do in this version"); + } + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mSleepList = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'S','N','A','M'>::value: + SoundRef sr; + esm.getHT(sr, 33); + mSoundList.push_back(sr); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - else - esm.fail("Don't know what to do in this version"); - mSleepList = esm.getHNOString("BNAM"); + void Region::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - esm.getHNT(mMapColor, "CNAM"); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } - mSoundList.clear(); - while (esm.hasMoreSubs()) - { - SoundRef sr; - esm.getHNT(sr, "SNAM", 33); - mSoundList.push_back(sr); - } -} -void Region::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); - if (esm.getVersion() == VER_12) - esm.writeHNT("WEAT", mData, sizeof(mData) - 2); - else - esm.writeHNT("WEAT", mData); + if (esm.getVersion() == VER_12) + esm.writeHNT("WEAT", mData, sizeof(mData) - 2); + else + esm.writeHNT("WEAT", mData); - esm.writeHNOCString("BNAM", mSleepList); + esm.writeHNOCString("BNAM", mSleepList); - esm.writeHNT("CNAM", mMapColor); - for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) - { - esm.writeHNT("SNAM", *it); + esm.writeHNT("CNAM", mMapColor); + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + { + esm.writeHNT("SNAM", *it); + } } -} void Region::blank() { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 1e241fffbe..a946e488e2 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -45,14 +45,14 @@ struct Region WEATstruct mData; int mMapColor; // RGBA - // sleepList refers to a eveled list of creatures you can meet if + // sleepList refers to a leveled list of creatures you can meet if // you sleep outside in this region. std::string mId, mName, mSleepList; std::vector mSoundList; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index f90f9e39dc..e4af3d9378 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -8,48 +8,70 @@ namespace ESM { unsigned int Repair::sRecordId = REC_REPA; -void Repair::load(ESMReader &esm) -{ - bool hasData = true; - while (esm.hasMoreSubs()) + void Repair::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','I','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','I','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RIDT subrecord"); } - if (!hasData) - esm.fail("Missing RIDT subrecord"); -} -void Repair::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); + void Repair::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - esm.writeHNT("RIDT", mData, 16); - esm.writeHNOString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + + esm.writeHNT("RIDT", mData, 16); + esm.writeHNOString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Repair::blank() { diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index e765bc93a5..2537c53cb6 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -27,8 +27,8 @@ struct Repair Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index fd807ddd37..b46d5b658a 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -8,7 +8,6 @@ namespace ESM { - unsigned int Script::sRecordId = REC_SCPT; void Script::loadSCVR(ESMReader &esm) @@ -58,21 +57,25 @@ namespace ESM } } - void Script::load(ESMReader &esm) + void Script::load(ESMReader &esm, bool &isDeleted) { - SCHD data; - esm.getHNT(data, "SCHD", 52); - mData = data.mData; - mId = data.mName.toString(); + isDeleted = false; mVarNames.clear(); + bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'S','C','H','D'>::value: + SCHD data; + esm.getHT(data, 52); + mData = data.mData; + mId = data.mName.toString(); + hasHeader = true; + break; case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); @@ -85,13 +88,21 @@ namespace ESM case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasHeader) + esm.fail("Missing SCHD subrecord"); } - void Script::save(ESMWriter &esm) const + void Script::save(ESMWriter &esm, bool isDeleted) const { std::string varNameString; if (!mVarNames.empty()) @@ -106,6 +117,12 @@ namespace ESM esm.writeHNT("SCHD", data, 52); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 56390f3841..b8a06406dd 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -50,8 +50,8 @@ public: /// Script source code std::string mScriptText; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 7883b8a1a9..c520897910 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -129,15 +129,16 @@ namespace ESM unsigned int Skill::sRecordId = REC_SKIL; - void Skill::load(ESMReader &esm) + void Skill::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // Skill record can't be deleted now (may be changed in the future) + bool hasIndex = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); @@ -164,7 +165,7 @@ namespace ESM mId = indexToId (mIndex); } - void Skill::save(ESMWriter &esm) const + void Skill::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index e001842970..5430b422d1 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -78,8 +78,8 @@ struct Skill static const std::string sIconNames[Length]; static const boost::array sSkillIds; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 5ee6f5245c..d84fe624d0 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; - void SoundGenerator::load(ESMReader &esm) + void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mType, 4); hasData = true; @@ -27,18 +33,35 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA subrecord"); } - void SoundGenerator::save(ESMWriter &esm) const + void SoundGenerator::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); + } void SoundGenerator::blank() diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 056958f0a8..70b221e98c 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -36,8 +36,8 @@ struct SoundGenerator std::string mId, mCreature, mSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 690c1b4484..82f169e54f 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; - void Sound::load(ESMReader &esm) + void Sound::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mSound = esm.getHString(); break; @@ -24,16 +30,32 @@ namespace ESM esm.getHT(mData, 3); hasData = true; break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA subrecord"); } - void Sound::save(ESMWriter &esm) const + void Sound::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index ff2202ca7d..937e22be88 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -23,8 +23,8 @@ struct Sound SOUNstruct mData; std::string mId, mSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 96c048e0a9..728b7bc2a7 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -8,17 +8,23 @@ namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; - void Spell::load(ESMReader &esm) + void Spell::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t val = esm.retSubName().val; - - switch (val) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -31,14 +37,32 @@ namespace ESM esm.getHT(s, 24); mEffects.mList.push_back(s); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing SPDT subrecord"); } - void Spell::save(ESMWriter &esm) const + void Spell::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); mEffects.save(esm); @@ -51,7 +75,6 @@ namespace ESM mData.mFlags = 0; mName.clear(); - mEffects.mList.clear(); } } diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 491da1d179..1763d0991c 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -45,8 +45,8 @@ struct Spell std::string mId, mName; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 7380dd0a7f..ab4c09750d 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -8,37 +8,51 @@ namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; - void StartScript::load(ESMReader &esm) + void StartScript::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'D','A','T','A'>::value: mData = esm.getHString(); hasData = true; break; - case ESM::FourCC<'N','A','M','E'>::value: - mId = esm.getHString(); - hasName = true; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + if (!hasName) esm.fail("Missing NAME"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA"); } - void StartScript::save(ESMWriter &esm) const + void StartScript::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNString("DATA", mData); - esm.writeHNString("NAME", mId); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNString("DATA", mData); + } } void StartScript::blank() diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index dc7ad6a42a..ce2ff49e77 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -27,8 +27,8 @@ struct StartScript std::string mId; // Load a record and add it to the list - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index b0ab89bedd..62d495ee34 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -8,13 +8,47 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; - void Static::load(ESMReader &esm) + void Static::load(ESMReader &esm, bool &isDeleted) { - mModel = esm.getHNString("MODL"); + isDeleted = false; + + bool hasName = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Static::save(ESMWriter &esm) const + void Static::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNCString("MODL", mModel); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNCString("MODL", mModel); + } } void Static::blank() diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index aa4fe67b8f..930cdb8491 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,8 +28,8 @@ struct Static std::string mId, mModel; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 981a5815a2..880a26bcb3 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; - void Weapon::load(ESMReader &esm) + void Weapon::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -36,15 +42,30 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing WPDT subrecord"); } - void Weapon::save(ESMWriter &esm) const + void Weapon::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("WPDT", mData, 32); diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index f66e9f3a68..eddcaee4f1 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -69,8 +69,8 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 62aa0452a8..d736bab66d 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -7,7 +7,8 @@ void ESM::ObjectState::load (ESMReader &esm) { mVersion = esm.getFormat(); - mRef.loadData(esm); + bool isDeleted; + mRef.loadData(esm, isDeleted); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); @@ -23,7 +24,8 @@ void ESM::ObjectState::load (ESMReader &esm) esm.getHNOT (mPosition, "POS_", 24); - esm.getHNOT (mLocalRotation, "LROT", 12); + if (esm.isNextSub("LROT")) + esm.skipHSub(); // local rotation, no longer used // obsolete int unused; @@ -51,10 +53,7 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const esm.writeHNT ("COUN", mCount); if (!inInventory) - { esm.writeHNT ("POS_", mPosition, 24); - esm.writeHNT ("LROT", mLocalRotation, 12); - } if (!mHasCustomState) esm.writeHNT ("HCUS", false); @@ -70,7 +69,6 @@ void ESM::ObjectState::blank() { mPosition.pos[i] = 0; mPosition.rot[i] = 0; - mLocalRotation[i] = 0; } mHasCustomState = true; } diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 674bcb8fc1..215cb74baa 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -24,7 +24,6 @@ namespace ESM unsigned char mEnabled; int mCount; ESM::Position mPosition; - float mLocalRotation[3]; // Is there any class-specific state following the ObjectState bool mHasCustomState; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index ccfe6d9ee5..f0865a0a98 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -1,6 +1,7 @@ #include "storage.hpp" #include +#include #include #include @@ -34,19 +35,22 @@ namespace ESMTerrain osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - assert(origin.x() == (int) origin.x()); - assert(origin.y() == (int) origin.y()); + int cellX = static_cast(std::floor(origin.x())); + int cellY = static_cast(std::floor(origin.y())); - int cellX = static_cast(origin.x()); - int cellY = static_cast(origin.y()); + int startRow = (origin.x() - cellX) * ESM::Land::LAND_SIZE; + int startColumn = (origin.y() - cellY) * ESM::Land::LAND_SIZE; + + int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; + int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT)) { min = std::numeric_limits::max(); max = -std::numeric_limits::max(); - for (int row=0; rowmHeights[col*ESM::Land::LAND_SIZE+row]; if (h > max) @@ -143,11 +147,9 @@ namespace ESMTerrain size_t increment = 1 << lodLevel; osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - assert(origin.x() == (int) origin.x()); - assert(origin.y() == (int) origin.y()); - int startX = static_cast(origin.x()); - int startY = static_cast(origin.y()); + int startCellX = static_cast(std::floor(origin.x())); + int startCellY = static_cast(std::floor(origin.y())); size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); @@ -162,10 +164,10 @@ namespace ESMTerrain float vertX = 0; float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); @@ -175,18 +177,31 @@ namespace ESMTerrain 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 + // This is only relevant if we're creating a chunk spanning multiple cells if (colStart == 0 && vertY_ != 0) colStart += increment; if (rowStart == 0 && vertX_ != 0) rowStart += increment; + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + vertY = vertY_; - for (int col=colStart; col= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); float height = -2048; if (heightData) @@ -200,7 +215,7 @@ namespace ESMTerrain if (normalData) { for (int i=0; i<3; ++i) - normal[i] = normalData->mNormals[arrayIndex+i]; + normal[i] = normalData->mNormals[srcArrayIndex+i]; normal.normalize(); } @@ -222,7 +237,7 @@ namespace ESMTerrain if (colourData) { for (int i=0; i<3; ++i) - color[i] = colourData->mColours[arrayIndex+i] / 255.f; + color[i] = colourData->mColours[srcArrayIndex+i] / 255.f; } else { @@ -285,11 +300,17 @@ namespace ESMTerrain std::string Storage::getTextureName(UniqueTextureId id) { + static const std::string defaultTexture = "textures\\_land_default.dds"; if (id.first == 0) - return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded? + return defaultTexture; // 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); + if (!ltex) + { + std::cerr << "Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead" << std::endl; + return defaultTexture; + } // this is needed due to MWs messed up texture handling std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture, mVFS); @@ -305,8 +326,19 @@ namespace ESMTerrain // and interpolate the rest of the cell by hand? :/ osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); - int cellX = static_cast(origin.x()); - int cellY = static_cast(origin.y()); + int cellX = static_cast(std::floor(origin.x())); + int cellY = static_cast(std::floor(origin.y())); + + int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE+1; // add 1 to wrap around next cell + + int rowStart = (origin.x() - cellX) * realTextureSize; + int colStart = (origin.y() - cellY) * realTextureSize; + int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; + int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; + + assert (rowStart >= 0 && colStart >= 0); + assert (rowEnd <= realTextureSize); + assert (colEnd <= realTextureSize); // Save the used texture indices so we know the total number of textures // and number of required blend maps @@ -317,8 +349,8 @@ namespace ESMTerrain // 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; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index dc6f02b608..9c9cebe96f 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -60,10 +60,14 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m loadConfig(mFixedPath.getUserConfigPath(), variables, description); boost::program_options::notify(variables); - loadConfig(mFixedPath.getLocalPath(), variables, description); - boost::program_options::notify(variables); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + // read either local or global config depending on type of installation + bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); boost::program_options::notify(variables); + if (!loaded) + { + loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + boost::program_options::notify(variables); + } mSilent = silent; } @@ -126,7 +130,7 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre boost::bind(&boost::filesystem::path::empty, _1)), dataDirs.end()); } -void ConfigurationManager::loadConfig(const boost::filesystem::path& path, +bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, boost::program_options::options_description& description) { @@ -145,13 +149,16 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, if (!mSilent) std::cout << "done." << std::endl; + return true; } else { if (!mSilent) std::cout << "failed." << std::endl; + return false; } } + return false; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 102f7c3cb7..58ee5c1aef 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -58,7 +58,7 @@ struct ConfigurationManager typedef std::tr1::unordered_map TokensMappingContainer; #endif - void loadConfig(const boost::filesystem::path& path, + bool loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, boost::program_options::options_description& description); diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index a105bb928d..212db562c0 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace { @@ -139,7 +141,7 @@ boost::filesystem::path LinuxPath::getInstallPath() const { // Change drive letter to lowercase, so we could use // ~/.wine/dosdevices symlinks - mwpath[0] = tolower(mwpath[0]); + mwpath[0] = Misc::StringUtils::toLower(mwpath[0]); installPath /= homePath; installPath /= ".wine/dosdevices/"; installPath /= mwpath; diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 8456c7a26d..169a6f8131 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #endif #if FILE_API == FILE_API_STDIO @@ -139,7 +141,7 @@ void LowLevelFile::open (char const * filename) if (mHandle == -1) { std::ostringstream os; - os << "Failed to open '" << filename << "' for reading."; + os << "Failed to open '" << filename << "' for reading: " << strerror(errno); throw std::runtime_error (os.str ()); } } @@ -160,15 +162,27 @@ size_t LowLevelFile::size () size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); if (oldPosition == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } size_t Size = ::lseek (mHandle, 0, SEEK_END); if (Size == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } if (lseek (mHandle, oldPosition, SEEK_SET) == -1) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } return Size; } @@ -178,7 +192,11 @@ void LowLevelFile::seek (size_t Position) assert (mHandle != -1); if (::lseek (mHandle, Position, SEEK_SET) == -1) - throw std::runtime_error ("A seek operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } } size_t LowLevelFile::tell () @@ -188,7 +206,11 @@ size_t LowLevelFile::tell () size_t Position = ::lseek (mHandle, 0, SEEK_CUR); if (Position == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } return Position; } @@ -200,7 +222,11 @@ size_t LowLevelFile::read (void * data, size_t size) int amount = ::read (mHandle, data, size); if (amount == -1) - throw std::runtime_error ("A read operation on a file failed."); + { + std::ostringstream os; + os << "An attempt to read " << size << "bytes failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } return amount; } diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 6e794796f9..2371419605 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -7,6 +7,8 @@ #include #include +#include + namespace { boost::filesystem::path getUserHome() @@ -129,7 +131,7 @@ boost::filesystem::path MacOsPath::getInstallPath() const if (!mwpath.empty()) { // Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks - mwpath[0] = tolower(mwpath[0]); + mwpath[0] = Misc::StringUtils::toLower(mwpath[0]); installPath /= homePath; installPath /= ".wine/dosdevices/"; installPath /= mwpath; diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 7b3b0c440c..b37a95b2fc 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -8,6 +8,8 @@ #include +#include + namespace Files { struct NameEqual @@ -28,8 +30,8 @@ namespace Files for (std::size_t i=0; i #include #include -#include #include #include +#include + namespace Files { typedef std::vector PathContainer; @@ -25,12 +26,11 @@ namespace Files return left #include #include #include #include +#include + namespace Interpreter{ bool check(const std::string& str, const std::string& escword, unsigned int* i, unsigned int* start) @@ -34,8 +35,7 @@ namespace Interpreter{ if(text[i] == eschar) { retval << text.substr(start, i - start); - std::string temp = text.substr(i+1, 100); - transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); bool found = false; try diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 47962015c2..1d48cce0b9 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -1,26 +1,37 @@ #ifndef COMPONENTS_LOADINGLISTENER_H #define COMPONENTS_LOADINGLISTENER_H +#include + namespace Loading { class Listener { public: - virtual void setLabel (const std::string& label) = 0; - - // Use ScopedLoad instead of using these directly - virtual void loadingOn() = 0; - virtual void loadingOff() = 0; + /// Set a text label to show on the loading screen. + /// @param label The label + /// @param important Is the label considered important to show? + /// @note "non-important" labels may not show on screen if the loading process went so fast + /// that the implementation decided not to show a loading screen at all. "important" labels + /// will show in a separate message-box if the loading screen was not shown. + virtual void setLabel (const std::string& label, bool important=false) {} - /// Indicate that some progress has been made, without specifying how much - virtual void indicateProgress () = 0; + /// Start a loading sequence. Must call loadingOff() when done. + /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. + /// @note It is best to use the ScopedLoad object instead of using loadingOn()/loadingOff() directly, + /// so that the loading is exception safe. + virtual void loadingOn() {} + virtual void loadingOff() {} - virtual void setProgressRange (size_t range) = 0; - virtual void setProgress (size_t value) = 0; - virtual void increaseProgress (size_t increase = 1) = 0; + /// Set the total range of progress (e.g. the number of objects to load). + virtual void setProgressRange (size_t range) {} + /// Set current progress. Valid range is [0, progressRange) + virtual void setProgress (size_t value) {} + /// Increase current progress, default by 1. + virtual void increaseProgress (size_t increase = 1) {} }; - // Used for stopping a loading sequence when the object goes out of scope + /// @brief Used for stopping a loading sequence when the object goes out of scope struct ScopedLoad { ScopedLoad(Listener* l) : mListener(l) { mListener->loadingOn(); } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 0c2635752d..6fe13abf31 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -51,7 +51,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLev std::string prefix2 = topLevelDirectory + '/'; std::string correctedPath = resPath; - Misc::StringUtils::toLower(correctedPath); + Misc::StringUtils::lowerCaseInPlace(correctedPath); // Apparently, leading separators are allowed while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp deleted file mode 100644 index 723c1c1e0b..0000000000 --- a/components/misc/stringops.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "stringops.hpp" - -namespace Misc -{ - -std::locale StringUtils::mLocale = std::locale::classic(); - -} diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 04dedb0721..2723527f1d 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,22 +4,57 @@ #include #include #include -#include namespace Misc { class StringUtils { - - static std::locale mLocale; struct ci { bool operator()(char x, char y) const { - return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); + return toLower(x) < toLower(y); } }; public: + + /// Plain and simple locale-unaware toLower. Anything from A to Z is lower-cased, multibyte characters are unchanged. + /// Don't use std::tolower(char, locale&) because that is abysmally slow. + /// Don't use tolower(int) because that depends on global locale. + static char toLower(char c) + { + switch(c) + { + case 'A':return 'a'; + case 'B':return 'b'; + case 'C':return 'c'; + case 'D':return 'd'; + case 'E':return 'e'; + case 'F':return 'f'; + case 'G':return 'g'; + case 'H':return 'h'; + case 'I':return 'i'; + case 'J':return 'j'; + case 'K':return 'k'; + case 'L':return 'l'; + case 'M':return 'm'; + case 'N':return 'n'; + case 'O':return 'o'; + case 'P':return 'p'; + case 'Q':return 'q'; + case 'R':return 'r'; + case 'S':return 's'; + case 'T':return 't'; + case 'U':return 'u'; + case 'V':return 'v'; + case 'W':return 'w'; + case 'X':return 'x'; + case 'Y':return 'y'; + case 'Z':return 'z'; + default:return c; + }; + } + static bool ciLess(const std::string &x, const std::string &y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } @@ -31,7 +66,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { + if (toLower(*xit) != toLower(*yit)) { return false; } } @@ -45,7 +80,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) + if(res != 0 && toLower(*xit) != toLower(*yit)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -59,17 +94,17 @@ public: } /// Transforms input string to lower case w/o copy - static std::string &toLower(std::string &inout) { + static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i +#include + +#include "myguirendermanager.hpp" + +namespace osgMyGUI +{ + + AdditiveLayer::AdditiveLayer() + { + mStateSet = new osg::StateSet; + mStateSet->setAttributeAndModes(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE)); + } + + AdditiveLayer::~AdditiveLayer() + { + // defined in .cpp file since we can't delete incomplete types + } + + void AdditiveLayer::renderToTarget(MyGUI::IRenderTarget *_target, bool _update) + { + RenderManager& renderManager = static_cast(MyGUI::RenderManager::getInstance()); + + renderManager.setInjectState(mStateSet.get()); + + MyGUI::OverlappedLayer::renderToTarget(_target, _update); + + renderManager.setInjectState(NULL); + } + +} diff --git a/components/myguiplatform/additivelayer.hpp b/components/myguiplatform/additivelayer.hpp new file mode 100644 index 0000000000..f3d47bc826 --- /dev/null +++ b/components/myguiplatform/additivelayer.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_ADDITIVELAYER +#define OPENMW_COMPONENTS_MYGUIPLATFORM_ADDITIVELAYER + +#include + +#include + +namespace osg +{ + class StateSet; +} + +namespace osgMyGUI +{ + + /// @brief A Layer rendering with additive blend mode. + class AdditiveLayer : public MyGUI::OverlappedLayer + { + public: + MYGUI_RTTI_DERIVED( AdditiveLayer ) + + AdditiveLayer(); + ~AdditiveLayer(); + + virtual void renderToTarget(MyGUI::IRenderTarget* _target, bool _update); + + private: + osg::ref_ptr mStateSet; + }; + +} + +#endif diff --git a/components/myguiplatform/myguiplatform.cpp b/components/myguiplatform/myguiplatform.cpp index 01d6ca567f..22b88438f6 100644 --- a/components/myguiplatform/myguiplatform.cpp +++ b/components/myguiplatform/myguiplatform.cpp @@ -1,2 +1,64 @@ #include "myguiplatform.hpp" +#include "myguirendermanager.hpp" +#include "myguidatamanager.hpp" +#include "myguiloglistener.hpp" + +namespace osgMyGUI +{ + +Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::TextureManager *textureManager, float uiScalingFactor) + : mRenderManager(nullptr) + , mDataManager(nullptr) + , mLogManager(nullptr) + , mLogFacility(nullptr) +{ + mLogManager = new MyGUI::LogManager(); + mRenderManager = new RenderManager(viewer, guiRoot, textureManager, uiScalingFactor); + mDataManager = new DataManager(); +} + +Platform::~Platform() +{ + delete mRenderManager; + mRenderManager = nullptr; + delete mDataManager; + mDataManager = nullptr; + delete mLogManager; + mLogManager = nullptr; + delete mLogFacility; + mLogFacility = nullptr; +} + +void Platform::initialise(const std::string &resourcePath, const std::string &_logName) +{ + if (!_logName.empty() && !mLogFacility) + { + mLogFacility = new LogFacility(_logName, false); + mLogManager->addLogSource(mLogFacility->getSource()); + } + + mDataManager->setResourcePath(resourcePath); + + mRenderManager->initialise(); + mDataManager->initialise(); +} + +void Platform::shutdown() +{ + mRenderManager->shutdown(); + mDataManager->shutdown(); +} + +RenderManager *Platform::getRenderManagerPtr() +{ + return mRenderManager; +} + +DataManager *Platform::getDataManagerPtr() +{ + return mDataManager; +} + + +} diff --git a/components/myguiplatform/myguiplatform.hpp b/components/myguiplatform/myguiplatform.hpp index 513267c991..56562e12a6 100644 --- a/components/myguiplatform/myguiplatform.hpp +++ b/components/myguiplatform/myguiplatform.hpp @@ -1,71 +1,46 @@ #ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H -#include "MyGUI_Prerequest.h" -#include "MyGUI_LogManager.h" +#include -#include "myguirendermanager.hpp" -#include "myguidatamanager.hpp" -#include "myguiloglistener.hpp" +namespace osgViewer +{ + class Viewer; +} +namespace osg +{ + class Group; +} +namespace Resource +{ + class TextureManager; +} +namespace MyGUI +{ + class LogManager; +} namespace osgMyGUI { + class RenderManager; + class DataManager; + class LogFacility; + class Platform { public: - Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::TextureManager* textureManager, float uiScalingFactor) - : mRenderManager(nullptr) - , mDataManager(nullptr) - , mLogManager(nullptr) - , mLogFacility(nullptr) - { - mLogManager = new MyGUI::LogManager(); - mRenderManager = new RenderManager(viewer, guiRoot, textureManager, uiScalingFactor); - mDataManager = new DataManager(); - } - - ~Platform() - { - delete mRenderManager; - mRenderManager = nullptr; - delete mDataManager; - mDataManager = nullptr; - delete mLogManager; - mLogManager = nullptr; - delete mLogFacility; - mLogFacility = nullptr; - } - - void initialise(const std::string& resourcePath, const std::string& _logName = "MyGUI.log") - { - if (!_logName.empty() && !mLogFacility) - { - mLogFacility = new LogFacility(_logName, false); - mLogManager->addLogSource(mLogFacility->getSource()); - } + Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::TextureManager* textureManager, float uiScalingFactor); - mDataManager->setResourcePath(resourcePath); + ~Platform(); - mRenderManager->initialise(); - mDataManager->initialise(); - } + void initialise(const std::string& resourcePath, const std::string& _logName = "MyGUI.log"); - void shutdown() - { - mRenderManager->shutdown(); - mDataManager->shutdown(); - } + void shutdown(); - RenderManager* getRenderManagerPtr() - { - return mRenderManager; - } + RenderManager* getRenderManagerPtr(); - DataManager* getDataManagerPtr() - { - return mDataManager; - } + DataManager* getDataManagerPtr(); private: RenderManager* mRenderManager; diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 160d659bd6..5bd56dc8f4 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -45,6 +45,9 @@ namespace osgMyGUI class Drawable : public osg::Drawable { osgMyGUI::RenderManager *mParent; + osg::ref_ptr mStateSet; + +public: // Stage 0: update widget animations and controllers. Run during the Update traversal. class FrameUpdate : public osg::Drawable::UpdateCallback @@ -101,6 +104,10 @@ class Drawable : public osg::Drawable { virtual void drawImplementation(osg::RenderInfo &renderInfo) const { osg::State *state = renderInfo.getState(); + + state->pushStateSet(mStateSet); + state->apply(); + state->disableAllVertexArrays(); state->setClientActiveTextureUnit(0); glEnableClientState(GL_VERTEX_ARRAY); @@ -113,6 +120,13 @@ class Drawable : public osg::Drawable { { const Batch& batch = *it; osg::VertexBufferObject *vbo = batch.mVertexBuffer; + + if (batch.mStateSet) + { + state->pushStateSet(batch.mStateSet); + state->apply(); + } + osg::Texture2D* texture = batch.mTexture; if(texture) state->applyTextureAttribute(0, texture); @@ -135,12 +149,20 @@ class Drawable : public osg::Drawable { } glDrawArrays(GL_TRIANGLES, 0, batch.mVertexCount); + + if (batch.mStateSet) + { + state->popStateSet(); + state->apply(); + } } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); + state->popStateSet(); + state->unbindVertexBufferObject(); state->dirtyAllVertexArrays(); state->disableAllVertexArrays(); @@ -161,10 +183,17 @@ public: osg::ref_ptr frameUpdate = new FrameUpdate; frameUpdate->setRenderManager(mParent); setUpdateCallback(frameUpdate); + + mStateSet = new osg::StateSet; + mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + mStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); + mStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mStateSet->setMode(GL_BLEND, osg::StateAttribute::ON); } Drawable(const Drawable ©, const osg::CopyOp ©op=osg::CopyOp::SHALLOW_COPY) : osg::Drawable(copy, copyop) , mParent(copy.mParent) + , mStateSet(copy.mStateSet) , mWriteTo(0) , mReadFrom(0) { @@ -180,6 +209,9 @@ public: // need to hold on to this too as the mVertexBuffer does not hold a ref to its own array osg::ref_ptr mArray; + // optional + osg::ref_ptr mStateSet; + size_t mVertexCount; }; @@ -321,6 +353,7 @@ RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, R , mUpdate(false) , mIsInitialise(false) , mInvScalingFactor(1.f) + , mInjectState(NULL) { if (scalingFactor != 0.f) mInvScalingFactor = 1.f / scalingFactor; @@ -364,12 +397,6 @@ void RenderManager::initialise() camera->setViewMatrix(osg::Matrix::identity()); camera->setRenderOrder(osg::Camera::POST_RENDER); camera->setClearMask(GL_NONE); - osg::StateSet *state = new osg::StateSet; - state->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); - state->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - state->setMode(GL_BLEND, osg::StateAttribute::ON); - state->setAttribute(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - geode->setStateSet(state); geode->setCullingActive(false); camera->addChild(geode.get()); @@ -418,10 +445,17 @@ void RenderManager::doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *text if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() } + if (mInjectState) + batch.mStateSet = mInjectState; mDrawable->addBatch(batch); } +void RenderManager::setInjectState(osg::StateSet* stateSet) +{ + mInjectState = stateSet; +} + void RenderManager::end() { } diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index d9fdc1834a..f2251cdb04 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -20,6 +20,7 @@ namespace osg class Group; class Camera; class RenderInfo; + class StateSet; } namespace osgMyGUI @@ -48,6 +49,8 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget float mInvScalingFactor; + osg::StateSet* mInjectState; + void destroyAllResources(); public: @@ -95,6 +98,9 @@ public: /** @see IRenderTarget::doRender */ virtual void doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *texture, size_t count); + /** specify a StateSet to inject for rendering. The StateSet will be used by future doRender calls until you reset it to NULL again. */ + void setInjectState(osg::StateSet* stateSet); + /** @see IRenderTarget::getInfo */ virtual const MyGUI::RenderTargetInfo& getInfo() { return mInfo; } diff --git a/components/myguiplatform/scalinglayer.cpp b/components/myguiplatform/scalinglayer.cpp new file mode 100644 index 0000000000..ee9de349ed --- /dev/null +++ b/components/myguiplatform/scalinglayer.cpp @@ -0,0 +1,140 @@ +#include "scalinglayer.hpp" + +#include +#include + +namespace osgMyGUI +{ + + /// @brief the ProxyRenderTarget allows to adjust the pixel scale and offset for a "source" render target. + class ProxyRenderTarget : public MyGUI::IRenderTarget + { + public: + /// @param target The target to render to. + /// @param viewSize The size of the underlying layer node to render. + /// @param hoffset The horizontal rendering offset, specified as an offset from the left screen edge in range 0-1. + /// @param voffset The vertical rendering offset, specified as an offset from the top screen edge in range 0-1. + ProxyRenderTarget(MyGUI::IRenderTarget* target, MyGUI::IntSize viewSize, float hoffset, float voffset) + : mTarget(target) + , mViewSize(viewSize) + , mHOffset(hoffset) + , mVOffset(voffset) + { + } + + virtual void begin() + { + mTarget->begin(); + } + + virtual void end() + { + mTarget->end(); + } + + virtual void doRender(MyGUI::IVertexBuffer* _buffer, MyGUI::ITexture* _texture, size_t _count) + { + mTarget->doRender(_buffer, _texture, _count); + } + + virtual const MyGUI::RenderTargetInfo& getInfo() + { + mInfo = mTarget->getInfo(); + mInfo.hOffset = mHOffset; + mInfo.vOffset = mVOffset; + mInfo.pixScaleX = 1.f / mViewSize.width; + mInfo.pixScaleY = 1.f / mViewSize.height; + return mInfo; + } + + private: + MyGUI::IRenderTarget* mTarget; + MyGUI::IntSize mViewSize; + float mHOffset, mVOffset; + MyGUI::RenderTargetInfo mInfo; + }; + + MyGUI::ILayerItem *ScalingLayer::getLayerItemByPoint(int _left, int _top) const + { + screenToLayerCoords(_left, _top); + + return OverlappedLayer::getLayerItemByPoint(_left, _top); + } + + void ScalingLayer::screenToLayerCoords(int& _left, int& _top) const + { + float scale = getScaleFactor(); + if (scale <= 0.f) + return; + + MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); + + _left -= globalViewSize.width/2; + _top -= globalViewSize.height/2; + + _left /= scale; + _top /= scale; + + _left += mViewSize.width/2; + _top += mViewSize.height/2; + } + + float ScalingLayer::getScaleFactor() const + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float w = viewSize.width; + float h = viewSize.height; + + float heightScale = (h / mViewSize.height); + float widthScale = (w / mViewSize.width); + return std::min(widthScale, heightScale); + } + + MyGUI::IntPoint ScalingLayer::getPosition(int _left, int _top) const + { + screenToLayerCoords(_left, _top); + return MyGUI::IntPoint(_left, _top); + } + + void ScalingLayer::renderToTarget(MyGUI::IRenderTarget *_target, bool _update) + { + MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntSize viewSize = globalViewSize; + float scale = getScaleFactor(); + viewSize.width /= scale; + viewSize.height /= scale; + + float hoffset = (globalViewSize.width - mViewSize.width*getScaleFactor())/2.f / static_cast(globalViewSize.width); + float voffset = (globalViewSize.height - mViewSize.height*getScaleFactor())/2.f / static_cast(globalViewSize.height); + + ProxyRenderTarget proxy(_target, viewSize, hoffset, voffset); + + MyGUI::OverlappedLayer::renderToTarget(&proxy, _update); + } + + void ScalingLayer::resizeView(const MyGUI::IntSize &_viewSize) + { + // do nothing + } + + void ScalingLayer::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) + { + MyGUI::OverlappedLayer::deserialization(_node, _version); + + MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator(); + while (info.next()) + { + if (info->getName() == "Property") + { + const std::string& key = info->findAttribute("key"); + const std::string& value = info->findAttribute("value"); + + if (key == "Size") + { + mViewSize = MyGUI::IntSize::parse(value); + } + } + } + } + +} diff --git a/components/myguiplatform/scalinglayer.hpp b/components/myguiplatform/scalinglayer.hpp new file mode 100644 index 0000000000..3ee1489f19 --- /dev/null +++ b/components/myguiplatform/scalinglayer.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_SCALINGLAYER +#define OPENMW_COMPONENTS_MYGUIPLATFORM_SCALINGLAYER + +#include + +namespace osgMyGUI +{ + + ///@brief A Layer that lays out and renders widgets in screen-relative coordinates. The "Size" property determines the size of the virtual screen, + /// which is then upscaled to the real screen size during rendering. The aspect ratio is kept intact, adding blanks to the sides when necessary. + class ScalingLayer : public MyGUI::OverlappedLayer + { + public: + MYGUI_RTTI_DERIVED(ScalingLayer) + + virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version); + + virtual MyGUI::ILayerItem* getLayerItemByPoint(int _left, int _top) const; + virtual MyGUI::IntPoint getPosition(int _left, int _top) const; + virtual void renderToTarget(MyGUI::IRenderTarget* _target, bool _update); + + virtual void resizeView(const MyGUI::IntSize& _viewSize); + + private: + void screenToLayerCoords(int& _left, int& _top) const; + float getScaleFactor() const; + }; + +} + +#endif diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 41dcb09de9..79cd104312 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -5,29 +5,19 @@ namespace Nif { -void NiLight::SLight::read(NIFStream *nif) +void NiLight::read(NIFStream *nif) { + NiDynamicEffect::read(nif); + dimmer = nif->getFloat(); ambient = nif->getVector3(); diffuse = nif->getVector3(); specular = nif->getVector3(); } -void NiLight::read(NIFStream *nif) -{ - Effect::read(nif); - - nif->getInt(); // 1 - nif->getInt(); // 1? - light.read(nif); -} - void NiTextureEffect::read(NIFStream *nif) { - Effect::read(nif); - - int tmp = nif->getInt(); - if(tmp) nif->getInt(); // always 1? + NiDynamicEffect::read(nif); /* 3 x Vector4 = [1,0,0,0] @@ -52,10 +42,25 @@ void NiTextureEffect::read(NIFStream *nif) void NiTextureEffect::post(NIFFile *nif) { - Effect::post(nif); + NiDynamicEffect::post(nif); texture.post(nif); } +void NiPointLight::read(NIFStream *nif) +{ + NiLight::read(nif); + + constantAttenuation = nif->getFloat(); + linearAttenuation = nif->getFloat(); + quadraticAttenuation = nif->getFloat(); +} +void NiSpotLight::read(NIFStream *nif) +{ + NiPointLight::read(nif); + + cutoff = nif->getFloat(); + exponent = nif->getFloat(); +} } diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index fae1cd7f58..02647e4448 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -29,27 +29,45 @@ namespace Nif { -typedef Node Effect; - -// Used for NiAmbientLight and NiDirectionalLight. Might also work for -// NiPointLight and NiSpotLight? -struct NiLight : Effect +struct NiDynamicEffect : public Node { - struct SLight + void read(NIFStream *nif) { - float dimmer; - osg::Vec3f ambient; - osg::Vec3f diffuse; - osg::Vec3f specular; + Node::read(nif); + unsigned int numAffectedNodes = nif->getUInt(); + for (unsigned int i=0; igetUInt(); // ref to another Node + } +}; + +// Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight. +struct NiLight : NiDynamicEffect +{ + float dimmer; + osg::Vec3f ambient; + osg::Vec3f diffuse; + osg::Vec3f specular; + + void read(NIFStream *nif); +}; + +struct NiPointLight : public NiLight +{ + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; - void read(NIFStream *nif); - }; - SLight light; + void read(NIFStream *nif); +}; +struct NiSpotLight : public NiPointLight +{ + float cutoff; + float exponent; void read(NIFStream *nif); }; -struct NiTextureEffect : Effect +struct NiTextureEffect : NiDynamicEffect { NiSourceTexturePtr texture; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 1db9c8ccf7..f6e489527e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -12,9 +12,8 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) : ver(0) , filename(name) , mUseSkinning(false) - , mStream(stream) { - parse(); + parse(stream); } NIFFile::~NIFFile() @@ -48,6 +47,8 @@ static std::map makeFactory() { std::map newFactory; newFactory.insert(makeEntry("NiNode", &construct , RC_NiNode )); + newFactory.insert(makeEntry("NiSwitchNode", &construct , RC_NiSwitchNode )); + newFactory.insert(makeEntry("NiLODNode", &construct , RC_NiLODNode )); newFactory.insert(makeEntry("AvoidNode", &construct , RC_AvoidNode )); newFactory.insert(makeEntry("NiBSParticleNode", &construct , RC_NiBSParticleNode )); newFactory.insert(makeEntry("NiBSAnimationNode", &construct , RC_NiBSAnimationNode )); @@ -80,6 +81,8 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiFlipController", &construct , RC_NiFlipController )); newFactory.insert(makeEntry("NiAmbientLight", &construct , RC_NiLight )); newFactory.insert(makeEntry("NiDirectionalLight", &construct , RC_NiLight )); + newFactory.insert(makeEntry("NiPointLight", &construct , RC_NiLight )); + newFactory.insert(makeEntry("NiSpotLight", &construct , RC_NiLight )); newFactory.insert(makeEntry("NiTextureEffect", &construct , RC_NiTextureEffect )); newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct , RC_NiVertWeightsExtraData )); newFactory.insert(makeEntry("NiTextKeyExtraData", &construct , RC_NiTextKeyExtraData )); @@ -111,7 +114,6 @@ static std::map makeFactory() ///Make the factory map used for parsing the file static const std::map factories = makeFactory(); -/// Get the file's version in a human readable form std::string NIFFile::printVersion(unsigned int version) { union ver_quad @@ -130,9 +132,9 @@ std::string NIFFile::printVersion(unsigned int version) return stream.str(); } -void NIFFile::parse() +void NIFFile::parse(Files::IStreamPtr stream) { - NIFStream nif (this, mStream); + NIFStream nif (this, stream); // Check the header string std::string head = nif.getVersionString(); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6fbef31ca6..900c360bb8 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -35,7 +35,7 @@ class NIFFile bool mUseSkinning; /// Parse the file - void parse(); + void parse(Files::IStreamPtr stream); /// Get the file's version in a human readable form ///\returns A string containing a human readable NIF version number @@ -46,8 +46,6 @@ class NIFFile ///\overload void operator = (NIFFile const &); - Files::IStreamPtr mStream; - public: /// Used if file parsing fails void fail(const std::string &msg) diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index d702d02925..75353044d7 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -37,6 +37,9 @@ template struct KeyMapT { typedef std::map< float, KeyT > MapType; + typedef T ValueType; + typedef KeyT KeyType; + static const unsigned int sLinearInterpolation = 1; static const unsigned int sQuadraticInterpolation = 2; static const unsigned int sTBCInterpolation = 3; @@ -135,6 +138,11 @@ private: /*key.mBackwardValue = */(nif.*getValue)(); } + static void readQuadratic(NIFStream &nif, KeyT &key) + { + readValue(nif, key); + } + static void readTBC(NIFStream &nif, KeyT &key) { readValue(nif, key); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 943ddcc661..326e9802fd 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -250,5 +250,41 @@ struct NiRotatingParticles : Node } }; +// A node used as the base to switch between child nodes, such as for LOD levels. +struct NiSwitchNode : public NiNode +{ + void read(NIFStream *nif) + { + NiNode::read(nif); + nif->getInt(); // unknown + } +}; + +struct NiLODNode : public NiSwitchNode +{ + osg::Vec3f lodCenter; + + struct LODRange + { + float minRange; + float maxRange; + }; + std::vector lodLevels; + + void read(NIFStream *nif) + { + NiSwitchNode::read(nif); + lodCenter = nif->getVector3(); + unsigned int numLodLevels = nif->getUInt(); + for (unsigned int i=0; igetFloat(); + r.maxRange = nif->getFloat(); + lodLevels.push_back(r); + } + } +}; + } // Namespace #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 1022802cc4..bcbdba1154 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,8 @@ enum RecordType { RC_MISSING = 0, RC_NiNode, + RC_NiSwitchNode, + RC_NiLODNode, RC_NiBillboardNode, RC_AvoidNode, RC_NiTriShape, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 2496c68cde..19afe49d63 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -12,12 +12,11 @@ #include -#include "../nif/niffile.hpp" -#include "../nif/node.hpp" -#include "../nif/data.hpp" -#include "../nif/property.hpp" -#include "../nif/controller.hpp" -#include "../nif/extra.hpp" +#include +#include +#include +#include +#include namespace @@ -40,21 +39,6 @@ btVector3 getbtVector(const osg::Vec3f &v) namespace NifBullet { -// Subclass btBhvTriangleMeshShape to auto-delete the meshInterface -struct TriangleMeshShape : public btBvhTriangleMeshShape -{ - TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) - : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) - { - } - - virtual ~TriangleMeshShape() - { - delete getTriangleInfoMap(); - delete m_meshInterface; - } -}; - BulletNifLoader::BulletNifLoader() : mCompoundShape(NULL) , mStaticMesh(NULL) @@ -65,9 +49,9 @@ BulletNifLoader::~BulletNifLoader() { } -osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr nif) +osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr nif) { - mShape = new BulletShape; + mShape = new Resource::BulletShape; mCompoundShape = NULL; mStaticMesh = NULL; @@ -126,11 +110,11 @@ osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr nif) { btTransform trans; trans.setIdentity(); - mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); + mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh,true)); } } else if (mStaticMesh) - mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true); + mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh,true); return mShape; } @@ -306,7 +290,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); } - TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true); + Resource::TriangleMeshShape* childShape = new Resource::TriangleMeshShape(childMesh,true); float scale = shape->trafo.scale; const Nif::Node* parent = shape; @@ -335,6 +319,9 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Vec3Array& vertices = *data->vertices; const osg::DrawElementsUShort& triangles = *data->triangles; + mStaticMesh->preallocateVertices(data->vertices->size()); + mStaticMesh->preallocateIndices(data->triangles->size()); + size_t numtris = data->triangles->size(); for(size_t i = 0;i < numtris;i+=3) { @@ -346,93 +333,4 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, } } -BulletShape::BulletShape() - : mCollisionShape(NULL) -{ - -} - -BulletShape::~BulletShape() -{ - deleteShape(mCollisionShape); -} - -void BulletShape::deleteShape(btCollisionShape* shape) -{ - if(shape!=NULL) - { - if(shape->isCompound()) - { - btCompoundShape* ms = static_cast(shape); - int a = ms->getNumChildShapes(); - for(int i=0; i getChildShape(i)); - } - delete shape; - } -} - -btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) const -{ - if(shape->isCompound()) - { - btCompoundShape *comp = static_cast(shape); - btCompoundShape *newShape = new btCompoundShape; - - int numShapes = comp->getNumChildShapes(); - for(int i = 0;i < numShapes;++i) - { - btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); - btTransform trans = comp->getChildTransform(i); - newShape->addChildShape(trans, child); - } - - return newShape; - } - - if(btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) - { -#if BT_BULLET_VERSION >= 283 - btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(trishape, btVector3(1.f, 1.f, 1.f)); -#else - // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions - btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); - btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); - NifBullet::TriangleMeshShape* newShape = new NifBullet::TriangleMeshShape(newMesh, true); -#endif - return newShape; - } - - if (btBoxShape* boxshape = dynamic_cast(shape)) - { - return new btBoxShape(*boxshape); - } - - throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); -} - -btCollisionShape *BulletShape::getCollisionShape() -{ - return mCollisionShape; -} - -osg::ref_ptr BulletShape::makeInstance() -{ - osg::ref_ptr instance (new BulletShapeInstance(this)); - return instance; -} - -BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) - : BulletShape() - , mSource(source) -{ - mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents; - mCollisionBoxTranslate = source->mCollisionBoxTranslate; - - mAnimatedShapes = source->mAnimatedShapes; - - if (source->mCollisionShape) - mCollisionShape = duplicateCollisionShape(source->mCollisionShape); -} - } // namespace NifBullet diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 52428cc74a..a30bf8fdf1 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -13,6 +13,7 @@ #include #include +#include class btTriangleMesh; class btCompoundShape; @@ -28,48 +29,6 @@ namespace Nif namespace NifBullet { -class BulletShapeInstance; -class BulletShape : public osg::Referenced -{ -public: - BulletShape(); - virtual ~BulletShape(); - - btCollisionShape* mCollisionShape; - - // Used for actors. Note, ideally actors would use a separate loader - as it is - // we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used. - // For now, use one file <-> one resource for simplicity. - osg::Vec3f mCollisionBoxHalfExtents; - osg::Vec3f mCollisionBoxTranslate; - - // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape - // will be a btCompoundShape (which consists of one or more child shapes). - // In this map, for each animated collision shape, - // we store the node's record index mapped to the child index of the shape in the btCompoundShape. - std::map mAnimatedShapes; - - osg::ref_ptr makeInstance(); - - btCollisionShape* duplicateCollisionShape(btCollisionShape* shape) const; - - btCollisionShape* getCollisionShape(); - -private: - void deleteShape(btCollisionShape* shape); -}; - -// An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape. -// Vertex data is shallow-copied where possible. A ref_ptr to the original shape needs to be held to keep vertex pointers intact. -class BulletShapeInstance : public BulletShape -{ -public: - BulletShapeInstance(osg::ref_ptr source); - -private: - osg::ref_ptr mSource; -}; - /** *Load bulletShape from NIF files. */ @@ -91,7 +50,7 @@ public: abort(); } - osg::ref_ptr load(const Nif::NIFFilePtr file); + osg::ref_ptr load(const Nif::NIFFilePtr file); private: bool findBoundingBox(const Nif::Node* node, int flags = 0); @@ -106,7 +65,7 @@ private: btTriangleMesh* mStaticMesh; - osg::ref_ptr mShape; + osg::ref_ptr mShape; }; } diff --git a/components/nifbullet/bulletshapemanager.cpp b/components/nifbullet/bulletshapemanager.cpp deleted file mode 100644 index 6acfdd4085..0000000000 --- a/components/nifbullet/bulletshapemanager.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "bulletshapemanager.hpp" - -#include - -#include - -namespace NifBullet -{ - -BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs) - : mVFS(vfs) -{ - -} - -BulletShapeManager::~BulletShapeManager() -{ - -} - -osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) -{ - std::string normalized = name; - mVFS->normalizeFilename(normalized); - - osg::ref_ptr shape; - Index::iterator it = mIndex.find(normalized); - if (it == mIndex.end()) - { - Files::IStreamPtr file = mVFS->get(normalized); - - // TODO: add support for non-NIF formats - - BulletNifLoader loader; - // might be worth sharing NIFFiles with SceneManager in some way - shape = loader.load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized))); - - mIndex[normalized] = shape; - } - else - shape = it->second; - - osg::ref_ptr instance = shape->makeInstance(); - return instance; -} - -} diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index bb698ed632..28f61e4b6c 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -86,56 +86,23 @@ KeyframeController::KeyframeController(const KeyframeController ©, const osg KeyframeController::KeyframeController(const Nif::NiKeyframeData *data) : mRotations(data->mRotations) - , mXRotations(data->mXRotations) - , mYRotations(data->mYRotations) - , mZRotations(data->mZRotations) - , mTranslations(data->mTranslations) - , mScales(data->mScales) + , mXRotations(data->mXRotations, 0.f) + , mYRotations(data->mYRotations, 0.f) + , mZRotations(data->mZRotations, 0.f) + , mTranslations(data->mTranslations, osg::Vec3f()) + , mScales(data->mScales, 1.f) { } -osg::Quat KeyframeController::interpKey(const Nif::QuaternionKeyMap::MapType &keys, float time) -{ - if(time <= keys.begin()->first) - return keys.begin()->second.mValue; - - Nif::QuaternionKeyMap::MapType::const_iterator it = keys.lower_bound(time); - if (it != keys.end()) - { - float aTime = it->first; - const Nif::QuaternionKey* aKey = &it->second; - - assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - - Nif::QuaternionKeyMap::MapType::const_iterator last = --it; - float aLastTime = last->first; - const Nif::QuaternionKey* aLastKey = &last->second; - - float a = (time - aLastTime) / (aTime - aLastTime); - - osg::Quat v1 = aLastKey->mValue; - osg::Quat v2 = aKey->mValue; - // don't take the long path - if (v1.x()*v2.x() + v1.y()*v2.y() + v1.z()*v2.z() + v1.w()*v2.w() < 0) // dotProduct(v1,v2) - v1 = -v1; - - osg::Quat result; - result.slerp(a, v1, v2); - return result; - } - else - return keys.rbegin()->second.mValue; -} - osg::Quat KeyframeController::getXYZRotation(float time) const { float xrot = 0, yrot = 0, zrot = 0; - if (mXRotations.get()) - xrot = interpKey(mXRotations->mKeys, time); - if (mYRotations.get()) - yrot = interpKey(mYRotations->mKeys, time); - if (mZRotations.get()) - zrot = interpKey(mZRotations->mKeys, time); + if (!mXRotations.empty()) + xrot = mXRotations.interpKey(time); + if (!mYRotations.empty()) + yrot = mYRotations.interpKey(time); + if (!mZRotations.empty()) + zrot = mZRotations.interpKey(time); osg::Quat xr(xrot, osg::Vec3f(1,0,0)); osg::Quat yr(yrot, osg::Vec3f(0,1,0)); osg::Quat zr(zrot, osg::Vec3f(0,0,1)); @@ -144,8 +111,8 @@ osg::Quat KeyframeController::getXYZRotation(float time) const osg::Vec3f KeyframeController::getTranslation(float time) const { - if(mTranslations.get() && mTranslations->mKeys.size() > 0) - return interpKey(mTranslations->mKeys, time); + if(!mTranslations.empty()) + return mTranslations.interpKey(time); return osg::Vec3f(); } @@ -162,12 +129,12 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) Nif::Matrix3& rot = userdata->mRotationScale; bool setRot = false; - if(mRotations.get() && !mRotations->mKeys.empty()) + if(!mRotations.empty()) { - mat.setRotate(interpKey(mRotations->mKeys, time)); + mat.setRotate(mRotations.interpKey(time)); setRot = true; } - else if (mXRotations.get() || mYRotations.get() || mZRotations.get()) + else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) { mat.setRotate(getXYZRotation(time)); setRot = true; @@ -186,15 +153,15 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) rot.mValues[i][j] = mat(j,i); // NB column/row major difference float& scale = userdata->mScale; - if(mScales.get() && !mScales->mKeys.empty()) - scale = interpKey(mScales->mKeys, time); + if(!mScales.empty()) + scale = mScales.interpKey(time); for (int i=0;i<3;++i) for (int j=0;j<3;++j) mat(i,j) *= scale; - if(mTranslations.get() && !mTranslations->mKeys.empty()) - mat.setTrans(interpKey(mTranslations->mKeys, time)); + if(!mTranslations.empty()) + mat.setTrans(mTranslations.interpKey(time)); trans->setMatrix(mat); } @@ -216,33 +183,35 @@ GeomMorpherController::GeomMorpherController(const GeomMorpherController ©, GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data) { for (unsigned int i=0; imMorphs.size(); ++i) - mKeyFrames.push_back(data->mMorphs[i].mKeyFrames); + mKeyFrames.push_back(FloatInterpolator(data->mMorphs[i].mKeyFrames)); } void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) { - osgAnimation::MorphGeometry* morphGeom = dynamic_cast(drawable); - if (morphGeom) + osgAnimation::MorphGeometry* morphGeom = static_cast(drawable); + if (hasInput()) { - if (hasInput()) + if (mKeyFrames.size() <= 1) + return; + float input = getInputValue(nv); + int i = 0; + for (std::vector::iterator it = mKeyFrames.begin()+1; it != mKeyFrames.end(); ++it,++i) { - if (mKeyFrames.size() <= 1) - return; - float input = getInputValue(nv); - int i = 0; - for (std::vector::iterator it = mKeyFrames.begin()+1; it != mKeyFrames.end(); ++it,++i) - { - float val = 0; - if (!(*it)->mKeys.empty()) - val = interpKey((*it)->mKeys, input); - val = std::max(0.f, std::min(1.f, val)); + float val = 0; + if (!(*it).empty()) + val = it->interpKey(input); + val = std::max(0.f, std::min(1.f, val)); - morphGeom->setWeight(i, val); + osgAnimation::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); + if (target.getWeight() != val) + { + target.setWeight(val); + morphGeom->dirty(); } } - - morphGeom->transformSoftwareMethod(); } + + // morphGeometry::transformSoftwareMethod() done in cull callback i.e. only for visible morph geometries } UVController::UVController() @@ -250,10 +219,10 @@ UVController::UVController() } UVController::UVController(const Nif::NiUVData *data, std::set textureUnits) - : mUTrans(data->mKeyList[0]) - , mVTrans(data->mKeyList[1]) - , mUScale(data->mKeyList[2]) - , mVScale(data->mKeyList[3]) + : mUTrans(data->mKeyList[0], 0.f) + , mVTrans(data->mKeyList[1], 0.f) + , mUScale(data->mKeyList[2], 1.f) + , mVScale(data->mKeyList[3], 1.f) , mTextureUnits(textureUnits) { } @@ -280,10 +249,10 @@ void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) if (hasInput()) { float value = getInputValue(nv); - float uTrans = interpKey(mUTrans->mKeys, value, 0.0f); - float vTrans = interpKey(mVTrans->mKeys, value, 0.0f); - float uScale = interpKey(mUScale->mKeys, value, 1.0f); - float vScale = interpKey(mVScale->mKeys, value, 1.0f); + float uTrans = mUTrans.interpKey(value); + float vTrans = mVTrans.interpKey(value); + float uScale = mUScale.interpKey(value); + float vScale = mVScale.interpKey(value); osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); mat.setTrans(uTrans, vTrans, 0); @@ -338,7 +307,7 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) } AlphaController::AlphaController(const Nif::NiFloatData *data) - : mData(data->mKeyList) + : mData(data->mKeyList, 1.f) { } @@ -348,7 +317,7 @@ AlphaController::AlphaController() } AlphaController::AlphaController(const AlphaController ©, const osg::CopyOp ©op) - : StateSetUpdater(copy, copyop), Controller(copy), ValueInterpolator() + : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) { } @@ -364,7 +333,7 @@ void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (hasInput()) { - float value = interpKey(mData->mKeys, getInputValue(nv)); + float value = mData.interpKey(getInputValue(nv)); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); diffuse.a() = value; @@ -373,7 +342,7 @@ void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) } MaterialColorController::MaterialColorController(const Nif::NiPosData *data) - : mData(data->mKeyList) + : mData(data->mKeyList, osg::Vec3f(1,1,1)) { } @@ -398,7 +367,7 @@ void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *n { if (hasInput()) { - osg::Vec3f value = interpKey(mData->mKeys, getInputValue(nv)); + osg::Vec3f value = mData.interpKey(getInputValue(nv)); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); diffuse.set(value.x(), value.y(), value.z(), diffuse.a()); @@ -466,10 +435,9 @@ void ParticleSystemController::operator() (osg::Node* node, osg::NodeVisitor* nv { if (hasInput()) { - osgParticle::ParticleProcessor* emitter = dynamic_cast(node); + osgParticle::ParticleProcessor* emitter = static_cast(node); float time = getInputValue(nv); - if (emitter) - emitter->setEnabled(time >= mEmitStart && time < mEmitStop); + emitter->setEnabled(time >= mEmitStart && time < mEmitStop); } traverse(node, nv); } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 58870317e9..e1e969d066 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -42,38 +42,118 @@ namespace osgAnimation namespace NifOsg { + // interpolation of keyframes + template class ValueInterpolator { - protected: - template - T interpKey (const std::map< float, Nif::KeyT >& keys, float time, T defaultValue = T()) const + public: + typedef typename MapT::ValueType ValueT; + + ValueInterpolator() + : mDefaultVal(ValueT()) + { + } + + ValueInterpolator(boost::shared_ptr keys, ValueT defaultVal = ValueT()) + : mKeys(keys) + , mDefaultVal(defaultVal) + { + if (keys) + { + mLastLowKey = mKeys->mKeys.end(); + mLastHighKey = mKeys->mKeys.end(); + } + } + + ValueT interpKey(float time) const { - if (keys.size() == 0) - return defaultValue; + if (empty()) + return mDefaultVal; + + const typename MapT::MapType & keys = mKeys->mKeys; if(time <= keys.begin()->first) return keys.begin()->second.mValue; - typename std::map< float, Nif::KeyT >::const_iterator it = keys.lower_bound(time); + // retrieve the current position in the map, optimized for the most common case + // where time moves linearly along the keyframe track + typename MapT::MapType::const_iterator it = mLastHighKey; + if (mLastHighKey != keys.end()) + { + if (time > mLastHighKey->first) + { + // try if we're there by incrementing one + ++mLastLowKey; + ++mLastHighKey; + it = mLastHighKey; + } + if (mLastHighKey == keys.end() || (time < mLastLowKey->first || time > mLastHighKey->first)) + it = keys.lower_bound(time); // still not there, reorient by performing lower_bound check on the whole map + } + else + it = keys.lower_bound(time); + + // now do the actual interpolation if (it != keys.end()) { float aTime = it->first; - const Nif::KeyT* aKey = &it->second; + const typename MapT::KeyType* aKey = &it->second; + + // cache for next time + mLastHighKey = it; assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - typename std::map< float, Nif::KeyT >::const_iterator last = --it; + typename MapT::MapType::const_iterator last = --it; + mLastLowKey = last; float aLastTime = last->first; - const Nif::KeyT* aLastKey = &last->second; + const typename MapT::KeyType* aLastKey = &last->second; float a = (time - aLastTime) / (aTime - aLastTime); - return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); + + return InterpolationFunc()(aLastKey->mValue, aKey->mValue, a); } else return keys.rbegin()->second.mValue; } + + bool empty() const + { + return !mKeys || mKeys->mKeys.empty(); + } + + private: + mutable typename MapT::MapType::const_iterator mLastLowKey; + mutable typename MapT::MapType::const_iterator mLastHighKey; + + boost::shared_ptr mKeys; + + ValueT mDefaultVal; + }; + + struct LerpFunc + { + template + inline ValueType operator()(const ValueType& a, const ValueType& b, float fraction) + { + return a + ((b - a) * fraction); + } }; + struct QuaternionSlerpFunc + { + inline osg::Quat operator()(const osg::Quat& a, const osg::Quat& b, float fraction) + { + osg::Quat result; + result.slerp(fraction, a, b); + return result; + } + }; + + typedef ValueInterpolator QuaternionInterpolator; + typedef ValueInterpolator FloatInterpolator; + typedef ValueInterpolator Vec3Interpolator; + class ControllerFunction : public SceneUtil::ControllerFunction { private: @@ -97,7 +177,8 @@ namespace NifOsg virtual float getMaximum() const; }; - class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller, public ValueInterpolator + /// Must be set on an osgAnimation::MorphGeometry. + class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller { public: GeomMorpherController(const Nif::NiMorphData* data); @@ -109,10 +190,10 @@ namespace NifOsg virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable); private: - std::vector mKeyFrames; + std::vector mKeyFrames; }; - class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller, public ValueInterpolator + class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller { public: KeyframeController(const Nif::NiKeyframeData *data); @@ -126,27 +207,23 @@ namespace NifOsg virtual void operator() (osg::Node*, osg::NodeVisitor*); private: - Nif::QuaternionKeyMapPtr mRotations; - - Nif::FloatKeyMapPtr mXRotations; - Nif::FloatKeyMapPtr mYRotations; - Nif::FloatKeyMapPtr mZRotations; - - Nif::Vector3KeyMapPtr mTranslations; - Nif::FloatKeyMapPtr mScales; + QuaternionInterpolator mRotations; - using ValueInterpolator::interpKey; + FloatInterpolator mXRotations; + FloatInterpolator mYRotations; + FloatInterpolator mZRotations; - osg::Quat interpKey(const Nif::QuaternionKeyMap::MapType &keys, float time); + Vec3Interpolator mTranslations; + FloatInterpolator mScales; osg::Quat getXYZRotation(float time) const; }; - class UVController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller, public ValueInterpolator + class UVController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { public: UVController(); - UVController(const UVController&,const osg::CopyOp& = osg::CopyOp::SHALLOW_COPY); + UVController(const UVController&,const osg::CopyOp&); UVController(const Nif::NiUVData *data, std::set textureUnits); META_Object(NifOsg,UVController) @@ -155,10 +232,10 @@ namespace NifOsg virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); private: - Nif::FloatKeyMapPtr mUTrans; - Nif::FloatKeyMapPtr mVTrans; - Nif::FloatKeyMapPtr mUScale; - Nif::FloatKeyMapPtr mVScale; + FloatInterpolator mUTrans; + FloatInterpolator mVTrans; + FloatInterpolator mUScale; + FloatInterpolator mVScale; std::set mTextureUnits; }; @@ -179,10 +256,10 @@ namespace NifOsg virtual void operator() (osg::Node* node, osg::NodeVisitor* nv); }; - class AlphaController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller, public ValueInterpolator + class AlphaController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { private: - Nif::FloatKeyMapPtr mData; + FloatInterpolator mData; public: AlphaController(const Nif::NiFloatData *data); @@ -196,10 +273,10 @@ namespace NifOsg META_Object(NifOsg, AlphaController) }; - class MaterialColorController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller, public ValueInterpolator + class MaterialColorController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { private: - Nif::Vector3KeyMapPtr mData; + Vec3Interpolator mData; public: MaterialColorController(const Nif::NiPosData *data); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2b73f779f6..5c28bbc7e6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include // resource #include @@ -98,21 +100,20 @@ namespace virtual void traverse(osg::NodeVisitor& nv) { - const osg::FrameStamp* stamp = nv.getFrameStamp(); - if (!stamp || nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) + if (nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN && nv.getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR) osg::Group::traverse(nv); else { for (unsigned int i=0; igetFrameNumber()%2) + if (i%2 == nv.getTraversalNumber()%2) getChild(i)->accept(nv); } } } }; - // NodeCallback used to have a transform always oriented towards the camera. Can have translation and scale + // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. // Must be set as a cull callback. class BillboardCallback : public osg::NodeCallback @@ -131,8 +132,7 @@ namespace virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = dynamic_cast(nv); - osg::MatrixTransform* billboardNode = dynamic_cast(node); - if (billboardNode && cv) + if (node && cv) { osg::Matrix modelView = *cv->getModelViewMatrix(); @@ -180,9 +180,9 @@ namespace if (!geom) return false; - if (mLastFrameNumber == nv->getFrameStamp()->getFrameNumber()) + if (mLastFrameNumber == nv->getTraversalNumber()) return false; - mLastFrameNumber = nv->getFrameStamp()->getFrameNumber(); + mLastFrameNumber = nv->getTraversalNumber(); geom->transformSoftwareMethod(); return false; @@ -254,7 +254,7 @@ namespace nextpos = std::distance(str.begin(), ++last); } std::string result = str.substr(pos, nextpos-pos); - textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result))); + textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::lowerCase(result))); pos = nextpos; } @@ -423,6 +423,20 @@ namespace NifOsg // Need to make sure that won't break transparency sorting. Check what the original engine is doing? } + osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) + { + osg::ref_ptr lod (new osg::LOD); + lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER); + lod->setCenter(niLodNode->lodCenter); + for (unsigned int i=0; ilodLevels.size(); ++i) + { + const Nif::NiLODNode::LODRange& range = niLodNode->lodLevels[i]; + lod->setRange(i, range.minRange, range.maxRange); + } + lod->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT); + return lod; + } + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { @@ -536,7 +550,7 @@ namespace NifOsg handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) - handleMeshControllers(nifNode, composite, boundTextures, animflags); + handleMeshControllers(nifNode, node, composite, boundTextures, animflags); } if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) @@ -554,6 +568,15 @@ namespace NifOsg // Optimization pass optimize(nifNode, node, skipMeshes); + + if (nifNode->recType == Nif::RC_NiLODNode) + { + const Nif::NiLODNode* niLodNode = static_cast(nifNode); + osg::ref_ptr lod = handleLodNode(niLodNode); + node->addChild(lod); // unsure if LOD should be above or below this node's transform + node = lod; + } + const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { @@ -570,7 +593,7 @@ namespace NifOsg return node; } - void handleMeshControllers(const Nif::Node *nifNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector &boundTextures, int animflags) + void handleMeshControllers(const Nif::Node *nifNode, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, const std::vector &boundTextures, int animflags) { for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -587,6 +610,14 @@ namespace NifOsg setupController(uvctrl, ctrl, animflags); composite->addController(ctrl); } + else if (ctrl->recType == Nif::RC_NiVisController) + { + handleVisController(static_cast(ctrl.getPtr()), node, animflags); + } + else if(ctrl->recType == Nif::RC_NiGeomMorpherController) + {} // handled in handleTriShape + else + std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; } } @@ -609,15 +640,20 @@ namespace NifOsg } else if (ctrl->recType == Nif::RC_NiVisController) { - const Nif::NiVisController* visctrl = static_cast(ctrl.getPtr()); - - osg::ref_ptr callback(new VisController(visctrl->data.getPtr())); - setupController(visctrl, callback, animflags); - transformNode->addUpdateCallback(callback); + handleVisController(static_cast(ctrl.getPtr()), transformNode, animflags); } + else + std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; } } + void handleVisController(const Nif::NiVisController* visctrl, osg::Node* node, int animflags) + { + osg::ref_ptr callback(new VisController(visctrl->data.getPtr())); + setupController(visctrl, callback, animflags); + node->addUpdateCallback(callback); + } + void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) @@ -810,6 +846,8 @@ namespace NifOsg continue; if(ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) partctrl = static_cast(ctrl.getPtr()); + else + std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; } if (!partctrl) { @@ -827,6 +865,8 @@ namespace NifOsg partsys->getOrCreateUserDataContainer()->addDescription("worldspace"); } + partsys->setParticleScaleReferenceFrame(osgParticle::ParticleSystem::LOCAL_COORDINATES); + handleParticleInitialState(nifNode, partsys, partctrl); partsys->setQuota(partctrl->numParticles); @@ -847,7 +887,7 @@ namespace NifOsg // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. - FindRecIndexVisitor find (partctrl->emitter->recIndex); + FindGroupByRecIndex find (partctrl->emitter->recIndex); rootNode->accept(find); if (!find.mFound) { @@ -870,9 +910,6 @@ namespace NifOsg // localToWorldMatrix for transforming to particle space handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(partsys); - std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); @@ -892,13 +929,21 @@ namespace NifOsg updater->addParticleSystem(partsys); parentNode->addChild(updater); +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(partsys); + osg::Node* toAttach = geode.get(); +#else + osg::Node* toAttach = partsys.get(); +#endif + if (rf == osgParticle::ParticleProcessor::RELATIVE_RF) - parentNode->addChild(geode); + parentNode->addChild(toAttach); else { osg::MatrixTransform* trans = new osg::MatrixTransform; trans->setUpdateCallback(new InverseWorldMatrix); - trans->addChild(geode); + trans->addChild(toAttach); parentNode->addChild(trans); } } @@ -918,7 +963,9 @@ namespace NifOsg int uvSet = *it; if (uvSet >= (int)data->uvlist.size()) { - std::cerr << "Warning: using an undefined UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; + std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; + if (data->uvlist.size()) + geometry->setTexCoordArray(textureStage, data->uvlist[0]); continue; } @@ -943,44 +990,57 @@ namespace NifOsg void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { osg::ref_ptr geometry; - if(!triShape->controller.empty()) + for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next) { - Nif::ControllerPtr ctrl = triShape->controller; - do { - if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) - { - geometry = handleMorphGeometry(static_cast(ctrl.getPtr())); + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if(ctrl->recType == Nif::RC_NiGeomMorpherController) + { + geometry = handleMorphGeometry(static_cast(ctrl.getPtr())); - osg::ref_ptr morphctrl = new GeomMorpherController( - static_cast(ctrl.getPtr())->data.getPtr()); - setupController(ctrl.getPtr(), morphctrl, animflags); - geometry->setUpdateCallback(morphctrl); - break; - } - } while(!(ctrl=ctrl->next).empty()); + osg::ref_ptr morphctrl = new GeomMorpherController( + static_cast(ctrl.getPtr())->data.getPtr()); + setupController(ctrl.getPtr(), morphctrl, animflags); + geometry->setUpdateCallback(morphctrl); + break; + } } if (!geometry.get()) geometry = new osg::Geometry; - osg::ref_ptr geode (new osg::Geode); triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode (new osg::Geode); geode->addDrawable(geometry); +#endif if (geometry->getDataVariance() == osg::Object::DYNAMIC) { // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch // This is so we can set the DataVariance as STATIC, giving a huge performance boost geometry->setDataVariance(osg::Object::STATIC); - osg::ref_ptr geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); osg::ref_ptr frameswitch = new FrameSwitch; + +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(geode); frameswitch->addChild(geode2); +#else + osg::ref_ptr geom2 = static_cast(osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); + frameswitch->addChild(geometry); + frameswitch->addChild(geom2); +#endif + parentNode->addChild(frameswitch); } else +#if OSG_VERSION_LESS_THAN(3,3,3) parentNode->addChild(geode); +#else + parentNode->addChild(geometry); +#endif } osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher) @@ -1041,8 +1101,6 @@ namespace NifOsg void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - osg::ref_ptr geode (new osg::Geode); - osg::ref_ptr geometry (new osg::Geometry); triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); @@ -1075,17 +1133,27 @@ namespace NifOsg } rig->setInfluenceMap(map); - geode->addDrawable(rig); - // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch // This is so we can set the DataVariance as STATIC, giving a huge performance boost rig->setDataVariance(osg::Object::STATIC); + + osg::ref_ptr frameswitch = new FrameSwitch; + +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(rig); + osg::Geode* geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES| osg::CopyOp::DEEP_COPY_DRAWABLES)); - osg::ref_ptr frameswitch = new FrameSwitch; frameswitch->addChild(geode); frameswitch->addChild(geode2); +#else + SceneUtil::RigGeometry* rig2 = static_cast(osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES| + osg::CopyOp::DEEP_COPY_DRAWABLES)); + frameswitch->addChild(rig); + frameswitch->addChild(rig2); +#endif parentNode->addChild(frameswitch); } @@ -1298,7 +1366,7 @@ namespace NifOsg int wrapT = (clamp) & 0x1; int wrapS = (clamp >> 1) & 0x1; - osg::Texture2D* texture2d = textureManager->getTexture2D(filename, + osg::ref_ptr texture2d = textureManager->getTexture2D(filename, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); @@ -1365,7 +1433,7 @@ namespace NifOsg osg::StateSet* stateset = node->getOrCreateStateSet(); int specFlags = 0; // Specular is disabled by default, even if there's a specular color in the NiMaterialProperty - osg::Material* mat = new osg::Material; + osg::ref_ptr mat (new osg::Material); mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF); // NIF material defaults don't match OpenGL defaults @@ -1420,12 +1488,11 @@ namespace NifOsg case Nif::RC_NiAlphaProperty: { const Nif::NiAlphaProperty* alphaprop = static_cast(property); - osg::BlendFunc* blendfunc = new osg::BlendFunc; if (alphaprop->flags&1) { - blendfunc->setFunction(getBlendMode((alphaprop->flags>>1)&0xf), - getBlendMode((alphaprop->flags>>5)&0xf)); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(new osg::BlendFunc(getBlendMode((alphaprop->flags>>1)&0xf), + getBlendMode((alphaprop->flags>>5)&0xf)), + osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) @@ -1440,11 +1507,10 @@ namespace NifOsg stateset->setRenderBinToInherit(); } - osg::AlphaFunc* alphafunc = new osg::AlphaFunc; if((alphaprop->flags>>9)&1) { - alphafunc->setFunction(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f); - stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), + alphaprop->data.threshold/255.f), osg::StateAttribute::ON); } else { diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 54f067e984..e15df53028 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -37,11 +37,20 @@ namespace NifOsg }; - class KeyframeHolder : public osg::Referenced + class KeyframeHolder : public osg::Object { public: + KeyframeHolder() {} + KeyframeHolder(const KeyframeHolder& copy, const osg::CopyOp& copyop) + : mTextKeys(copy.mTextKeys) + , mKeyframeControllers(copy.mKeyframeControllers) + { + } + TextKeyMap mTextKeys; + META_Object(OpenMW, KeyframeHolder) + typedef std::map > KeyframeControllerMap; KeyframeControllerMap mKeyframeControllers; }; diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 68f3de8aa1..5003397222 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -23,7 +23,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem ©, const osg::CopyOp &co { // For some reason the osgParticle constructor doesn't copy the particles for (int i=0;isetMatrix(mat); } traverse(node,nv); @@ -127,7 +127,7 @@ void GrowFadeAffector::operate(osgParticle::Particle* particle, double /* dt */) } ParticleColorAffector::ParticleColorAffector(const Nif::NiColorData *clrdata) - : mData(*clrdata) + : mData(clrdata->mKeyMap, osg::Vec4f(1,1,1,1)) { } @@ -145,7 +145,7 @@ ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector ©, void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */) { float time = static_cast(particle->getAge()/particle->getLifeTime()); - osg::Vec4f color = interpKey(mData.mKeyMap->mKeys, time, osg::Vec4f(1,1,1,1)); + osg::Vec4f color = mData.interpKey(time); particle->setColorRange(osgParticle::rangev4(color, color)); } @@ -276,7 +276,7 @@ void Emitter::emitParticles(double dt) int randomRecIndex = mTargets[(std::rand() / (static_cast(RAND_MAX)+1.0)) * mTargets.size()]; // we could use a map here for faster lookup - FindRecIndexVisitor visitor(randomRecIndex); + FindGroupByRecIndex visitor(randomRecIndex); getParent(0)->accept(visitor); if (!visitor.mFound) @@ -306,21 +306,25 @@ void Emitter::emitParticles(double dt) } } -FindRecIndexVisitor::FindRecIndexVisitor(int recIndex) +FindGroupByRecIndex::FindGroupByRecIndex(int recIndex) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mFound(NULL) , mRecIndex(recIndex) { } -void FindRecIndexVisitor::apply(osg::Node &searchNode) +void FindGroupByRecIndex::apply(osg::Node &searchNode) { if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects()) { NodeUserData* holder = dynamic_cast(searchNode.getUserDataContainer()->getUserObject(0)); if (holder && holder->mIndex == mRecIndex) { - mFound = static_cast(&searchNode); + osg::Group* group = searchNode.asGroup(); + if (!group) + group = searchNode.getParent(0); + + mFound = group; mFoundPath = getNodePath(); return; } diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index c7d5d585d4..a1ed3f3d0f 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -30,7 +30,7 @@ namespace NifOsg ParticleSystem(); ParticleSystem(const ParticleSystem& copy, const osg::CopyOp& copyop); - META_Object(NifOsg, NifOsg::ParticleSystem) + META_Object(NifOsg, ParticleSystem) virtual osgParticle::Particle* createParticle(const osgParticle::Particle *ptemplate); @@ -60,7 +60,7 @@ namespace NifOsg InverseWorldMatrix() { } - InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& op = osg::CopyOp::SHALLOW_COPY) + InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& op) : osg::Object(), osg::NodeCallback() { } @@ -130,7 +130,8 @@ namespace NifOsg float mCachedDefaultSize; }; - class ParticleColorAffector : public osgParticle::Operator, public ValueInterpolator + typedef ValueInterpolator Vec4Interpolator; + class ParticleColorAffector : public osgParticle::Operator { public: ParticleColorAffector(const Nif::NiColorData* clrdata); @@ -139,13 +140,10 @@ namespace NifOsg META_Object(NifOsg, ParticleColorAffector) - // TODO: very similar to vec3 version, refactor to a template - osg::Vec4f interpolate(const float time, const Nif::Vector4KeyMap::MapType& keys); - virtual void operate(osgParticle::Particle* particle, double dt); private: - Nif::NiColorData mData; + Vec4Interpolator mData; }; class GravityAffector : public osgParticle::Operator @@ -174,11 +172,12 @@ namespace NifOsg osg::Vec3f mCachedWorldDirection; }; - // NodeVisitor to find a child node with the given record index, stored in the node's user data container. - class FindRecIndexVisitor : public osg::NodeVisitor + // NodeVisitor to find a Group node with the given record index, stored in the node's user data container. + // Alternatively, returns the node's parent Group if that node is not a Group (i.e. a leaf node). + class FindGroupByRecIndex : public osg::NodeVisitor { public: - FindRecIndexVisitor(int recIndex); + FindGroupByRecIndex(int recIndex); virtual void apply(osg::Node &searchNode); @@ -196,7 +195,7 @@ namespace NifOsg Emitter(); Emitter(const Emitter& copy, const osg::CopyOp& copyop); - META_Object(NifOsg, NifOsg::Emitter) + META_Object(NifOsg, Emitter) virtual void emitParticles(double dt); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp new file mode 100644 index 0000000000..968dbe6c67 --- /dev/null +++ b/components/resource/bulletshape.cpp @@ -0,0 +1,111 @@ +#include "bulletshape.hpp" + +#include + +#include +#include +#include +#include + +namespace Resource +{ + +BulletShape::BulletShape() + : mCollisionShape(NULL) +{ + +} + +BulletShape::~BulletShape() +{ + deleteShape(mCollisionShape); +} + +void BulletShape::deleteShape(btCollisionShape* shape) +{ + if(shape!=NULL) + { + if(shape->isCompound()) + { + btCompoundShape* ms = static_cast(shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + delete shape; + } +} + +btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) const +{ + if(shape->isCompound()) + { + btCompoundShape *comp = static_cast(shape); + btCompoundShape *newShape = new btCompoundShape; + + int numShapes = comp->getNumChildShapes(); + for(int i = 0;i < numShapes;++i) + { + btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); + btTransform trans = comp->getChildTransform(i); + newShape->addChildShape(trans, child); + } + + return newShape; + } + + if(btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) + { +#if BT_BULLET_VERSION >= 283 + btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(trishape, btVector3(1.f, 1.f, 1.f)); +#else + // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions + btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); + btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); + + // Do not build a new bvh (not needed, since it's the same as the original shape's bvh) + bool buildBvh = true; + if (trishape->getOptimizedBvh()) + buildBvh = false; + TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, buildBvh); + // Set original shape's bvh via pointer + // The pointer is safe because the BulletShapeInstance keeps a ref_ptr to the original BulletShape + if (!buildBvh) + newShape->setOptimizedBvh(trishape->getOptimizedBvh()); +#endif + return newShape; + } + + if (btBoxShape* boxshape = dynamic_cast(shape)) + { + return new btBoxShape(*boxshape); + } + + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); +} + +btCollisionShape *BulletShape::getCollisionShape() +{ + return mCollisionShape; +} + +osg::ref_ptr BulletShape::makeInstance() +{ + osg::ref_ptr instance (new BulletShapeInstance(this)); + return instance; +} + +BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) + : BulletShape() + , mSource(source) +{ + mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents; + mCollisionBoxTranslate = source->mCollisionBoxTranslate; + + mAnimatedShapes = source->mAnimatedShapes; + + if (source->mCollisionShape) + mCollisionShape = duplicateCollisionShape(source->mCollisionShape); +} + +} diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp new file mode 100644 index 0000000000..cfae27eac4 --- /dev/null +++ b/components/resource/bulletshape.hpp @@ -0,0 +1,78 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H +#define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H + +#include + +#include +#include +#include + +#include + +class btCollisionShape; + +namespace Resource +{ + + class BulletShapeInstance; + class BulletShape : public osg::Referenced + { + public: + BulletShape(); + virtual ~BulletShape(); + + btCollisionShape* mCollisionShape; + + // Used for actors. Note, ideally actors would use a separate loader - as it is + // we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used. + // For now, use one file <-> one resource for simplicity. + osg::Vec3f mCollisionBoxHalfExtents; + osg::Vec3f mCollisionBoxTranslate; + + // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape + // will be a btCompoundShape (which consists of one or more child shapes). + // In this map, for each animated collision shape, + // we store the node's record index mapped to the child index of the shape in the btCompoundShape. + std::map mAnimatedShapes; + + osg::ref_ptr makeInstance(); + + btCollisionShape* duplicateCollisionShape(btCollisionShape* shape) const; + + btCollisionShape* getCollisionShape(); + + private: + void deleteShape(btCollisionShape* shape); + }; + + + // An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape. + // Vertex data is shallow-copied where possible. A ref_ptr to the original shape is held to keep vertex pointers intact. + class BulletShapeInstance : public BulletShape + { + public: + BulletShapeInstance(osg::ref_ptr source); + + private: + osg::ref_ptr mSource; + }; + + // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface + struct TriangleMeshShape : public btBvhTriangleMeshShape + { + TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression, bool buildBvh = true) + : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression, buildBvh) + { + } + + virtual ~TriangleMeshShape() + { + delete getTriangleInfoMap(); + delete m_meshInterface; + } + }; + + +} + +#endif diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp new file mode 100644 index 0000000000..4cbe62f3c3 --- /dev/null +++ b/components/resource/bulletshapemanager.cpp @@ -0,0 +1,152 @@ +#include "bulletshapemanager.hpp" + +#include +#include +#include + +#include + +#include + +#include + +#include "bulletshape.hpp" +#include "scenemanager.hpp" +#include "niffilemanager.hpp" + + +namespace Resource +{ + +struct GetTriangleFunctor +{ + GetTriangleFunctor() + : mTriMesh(NULL) + { + } + + void setTriMesh(btTriangleMesh* triMesh) + { + mTriMesh = triMesh; + } + + void setMatrix(const osg::Matrixf& matrix) + { + mMatrix = matrix; + } + + inline btVector3 toBullet(const osg::Vec3f& vec) + { + return btVector3(vec.x(), vec.y(), vec.z()); + } + + void inline operator()( const osg::Vec3 v1, const osg::Vec3 v2, const osg::Vec3 v3, bool _temp ) + { + if (mTriMesh) + mTriMesh->addTriangle( toBullet(mMatrix.preMult(v1)), toBullet(mMatrix.preMult(v2)), toBullet(mMatrix.preMult(v3))); + } + + btTriangleMesh* mTriMesh; + osg::Matrixf mMatrix; +}; + +/// Creates a BulletShape out of a Node hierarchy. +class NodeToShapeVisitor : public osg::NodeVisitor +{ +public: + NodeToShapeVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mTriangleMesh(NULL) + { + + } + + virtual void apply(osg::Geode& geode) + { + for (unsigned int i=0; i functor; + functor.setTriMesh(mTriangleMesh); + functor.setMatrix(worldMat); + drawable.accept(functor); + } + + osg::ref_ptr getShape() + { + if (!mTriangleMesh) + return osg::ref_ptr(); + + osg::ref_ptr shape (new BulletShape); + TriangleMeshShape* meshShape = new TriangleMeshShape(mTriangleMesh, true); + shape->mCollisionShape = meshShape; + mTriangleMesh = NULL; + return shape; + } + +private: + btTriangleMesh* mTriangleMesh; +}; + +BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) + : mVFS(vfs) + , mSceneManager(sceneMgr) + , mNifFileManager(nifFileManager) +{ + +} + +BulletShapeManager::~BulletShapeManager() +{ + +} + +osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) +{ + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr shape; + Index::iterator it = mIndex.find(normalized); + if (it == mIndex.end()) + { + size_t extPos = normalized.find_last_of('.'); + std::string ext; + if (extPos != std::string::npos && extPos+1 < normalized.size()) + ext = normalized.substr(extPos+1); + + if (ext == "nif") + { + NifBullet::BulletNifLoader loader; + shape = loader.load(mNifFileManager->get(normalized)); + } + else + { + // TODO: support .bullet shape files + + osg::ref_ptr constNode (mSceneManager->getTemplate(normalized)); + osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor + NodeToShapeVisitor visitor; + node->accept(visitor); + shape = visitor.getShape(); + if (!shape) + return osg::ref_ptr(); + } + + mIndex[normalized] = shape; + } + else + shape = it->second; + + osg::ref_ptr instance = shape->makeInstance(); + return instance; +} + +} diff --git a/components/nifbullet/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp similarity index 71% rename from components/nifbullet/bulletshapemanager.hpp rename to components/resource/bulletshapemanager.hpp index 6b9ec60de1..ac1523495c 100644 --- a/components/nifbullet/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -6,6 +6,8 @@ #include +#include "bulletshape.hpp" + namespace VFS { class Manager; @@ -14,10 +16,7 @@ namespace VFS namespace Resource { class SceneManager; -} - -namespace NifBullet -{ + class NifFileManager; class BulletShape; class BulletShapeInstance; @@ -25,13 +24,15 @@ namespace NifBullet class BulletShapeManager { public: - BulletShapeManager(const VFS::Manager* vfs); + BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); ~BulletShapeManager(); osg::ref_ptr createInstance(const std::string& name); private: const VFS::Manager* mVFS; + SceneManager* mSceneManager; + NifFileManager* mNifFileManager; typedef std::map > Index; Index mIndex; diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp new file mode 100644 index 0000000000..7e948dcb03 --- /dev/null +++ b/components/resource/keyframemanager.cpp @@ -0,0 +1,41 @@ +#include "keyframemanager.hpp" + +#include +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + KeyframeManager::KeyframeManager(const VFS::Manager* vfs) + : mCache(new osgDB::ObjectCache) + , mVFS(vfs) + { + } + + KeyframeManager::~KeyframeManager() + { + } + + osg::ref_ptr KeyframeManager::get(const std::string &name) + { + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + return osg::ref_ptr(static_cast(obj.get())); + else + { + osg::ref_ptr loaded (new NifOsg::KeyframeHolder); + NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); + + mCache->addEntryToObjectCache(normalized, loaded); + return loaded; + } + } + + + +} diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp new file mode 100644 index 0000000000..5032d0e389 --- /dev/null +++ b/components/resource/keyframemanager.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_COMPONENTS_KEYFRAMEMANAGER_H +#define OPENMW_COMPONENTS_KEYFRAMEMANAGER_H + +#include +#include + +namespace VFS +{ + class Manager; +} + +namespace osgDB +{ + class ObjectCache; +} + +namespace NifOsg +{ + class KeyframeHolder; +} + +namespace Resource +{ + + /// @brief Managing of keyframe resources + class KeyframeManager + { + public: + KeyframeManager(const VFS::Manager* vfs); + ~KeyframeManager(); + + void clearCache(); + + /// Retrieve a read-only keyframe resource by name (case-insensitive). + /// @note This method is safe to call from any thread. + /// @note Throws an exception if the resource is not found. + osg::ref_ptr get(const std::string& name); + + private: + osg::ref_ptr mCache; + + const VFS::Manager* mVFS; + }; + +} + +#endif diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp new file mode 100644 index 0000000000..1d8019b69d --- /dev/null +++ b/components/resource/niffilemanager.cpp @@ -0,0 +1,63 @@ +#include "niffilemanager.hpp" + +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + class NifFileHolder : public osg::Object + { + public: + NifFileHolder(const Nif::NIFFilePtr& file) + : mNifFile(file) + { + } + NifFileHolder(const NifFileHolder& copy, const osg::CopyOp& copyop) + : mNifFile(copy.mNifFile) + { + } + + NifFileHolder() + { + } + + META_Object(Resource, NifFileHolder) + + Nif::NIFFilePtr mNifFile; + }; + + NifFileManager::NifFileManager(const VFS::Manager *vfs) + : mVFS(vfs) + { + mCache = new osgDB::ObjectCache; + } + + NifFileManager::~NifFileManager() + { + + } + + void NifFileManager::clearCache() + { + // NIF files aren't needed any more when the converted objects are cached in SceneManager / BulletShapeManager, + // so we'll simply drop all nif files here, unlikely to need them again + mCache->clear(); + } + + Nif::NIFFilePtr NifFileManager::get(const std::string &name) + { + osg::ref_ptr obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(obj.get())->mNifFile; + else + { + Nif::NIFFilePtr file (new Nif::NIFFile(mVFS->getNormalized(name), name)); + obj = new NifFileHolder(file); + mCache->addEntryToObjectCache(name, obj); + return file; + } + } + +} diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp new file mode 100644 index 0000000000..90ad9fc294 --- /dev/null +++ b/components/resource/niffilemanager.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_NIFFILEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_NIFFILEMANAGER_H + +#include + +#include + +namespace VFS +{ + class Manager; +} + +namespace osgDB +{ + class ObjectCache; +} + +namespace Resource +{ + + /// @brief Handles caching of NIFFiles. + /// @note The NifFileManager is completely thread safe. + class NifFileManager + { + public: + NifFileManager(const VFS::Manager* vfs); + ~NifFileManager(); + + void clearCache(); + + /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. + /// @note For performance reasons the NifFileManager does not handle case folding, needs + /// to be done in advance by other managers accessing the NifFileManager. + Nif::NIFFilePtr get(const std::string& name); + + private: + // Use the osgDB::ObjectCache so objects are retrieved in thread safe way + osg::ref_ptr mCache; + + const VFS::Manager* mVFS; + }; + +} + +#endif diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp new file mode 100644 index 0000000000..8c2c524165 --- /dev/null +++ b/components/resource/objectcache.cpp @@ -0,0 +1,141 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include + +#if OSG_VERSION_LESS_THAN(3,4,0) + +#include "objectcache.hpp" + +using namespace osgDB; + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// ObjectCache +// +ObjectCache::ObjectCache(): + osg::Referenced(true) +{ +// OSG_NOTICE<<"Constructed ObjectCache"< lock1(_objectCacheMutex); + OpenThreads::ScopedLock lock2(objectCache->_objectCacheMutex); + + // OSG_NOTICE<<"Inserting objects to main ObjectCache "<_objectCache.size()<_objectCache.begin(), objectCache->_objectCache.end()); +} + + +void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache[filename]=ObjectTimeStampPair(object,timestamp); +} + +osg::Object* ObjectCache::getFromObjectCache(const std::string& fileName) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator itr = _objectCache.find(fileName); + if (itr!=_objectCache.end()) return itr->second.first.get(); + else return 0; +} + +osg::ref_ptr ObjectCache::getRefFromObjectCache(const std::string& fileName) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator itr = _objectCache.find(fileName); + if (itr!=_objectCache.end()) + { + // OSG_NOTICE<<"Found "<second.first; + } + else return 0; +} + +void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // look for objects with external references and update their time stamp. + for(ObjectCacheMap::iterator itr=_objectCache.begin(); + itr!=_objectCache.end(); + ++itr) + { + // if ref count is greater the 1 the object has an external reference. + if (itr->second.first->referenceCount()>1) + { + // so update it time stamp. + itr->second.second = referenceTime; + } + } +} + +void ObjectCache::removeExpiredObjectsInCache(double expiryTime) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // Remove expired entries from object cache + ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) + { + if (oitr->second.second<=expiryTime) + { + _objectCache.erase(oitr++); + } + else + { + ++oitr; + } + } +} + +void ObjectCache::removeFromObjectCache(const std::string& fileName) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator itr = _objectCache.find(fileName); + if (itr!=_objectCache.end()) _objectCache.erase(itr); +} + +void ObjectCache::clear() +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache.clear(); +} + +void ObjectCache::releaseGLObjects(osg::State* state) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.first.get(); + object->releaseGLObjects(state); + } +} + +#endif diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp new file mode 100644 index 0000000000..0a342f27f6 --- /dev/null +++ b/components/resource/objectcache.hpp @@ -0,0 +1,92 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +// Wrapper for osgDB/ObjectCache. Works around ObjectCache not being available in old OSG 3.2. +// Use "#include objectcache.hpp" in place of "#include + +#if OSG_VERSION_GREATER_OR_EQUAL(3,4,0) +#include +#else + +#include + +#include +#include + +#include + +namespace osgDB { + +class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced +{ + public: + + ObjectCache(); + + /** For each object in the cache which has an reference count greater than 1 + * (and therefore referenced by elsewhere in the application) set the time stamp + * for that object in the cache to specified time. + * This would typically be called once per frame by applications which are doing database paging, + * and need to prune objects that are no longer required. + * The time used should be taken from the FrameStamp::getReferenceTime().*/ + void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime); + + /** Removed object in the cache which have a time stamp at or before the specified expiry time. + * This would typically be called once per frame by applications which are doing database paging, + * and need to prune objects that are no longer required, and called after the a called + * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ + void removeExpiredObjectsInCache(double expiryTime); + + /** Remove all objects in the cache regardless of having external references or expiry times.*/ + void clear(); + + /** Add contents of specified ObjectCache to this object cache.*/ + void addObjectCache(ObjectCache* object); + + /** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/ + void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0); + + /** Remove Object from cache.*/ + void removeFromObjectCache(const std::string& fileName); + + /** Get an Object from the object cache*/ + osg::Object* getFromObjectCache(const std::string& fileName); + + /** Get an ref_ptr from the object cache*/ + osg::ref_ptr getRefFromObjectCache(const std::string& fileName); + + /** call rleaseGLObjects on all objects attached to the object cache.*/ + void releaseGLObjects(osg::State* state); + + protected: + + virtual ~ObjectCache(); + + typedef std::pair, double > ObjectTimeStampPair; + typedef std::map ObjectCacheMap; + + ObjectCacheMap _objectCache; + OpenThreads::Mutex _objectCacheMutex; + +}; + +} + +#endif + +#endif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index bd6824079e..2ce8d22e6a 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -2,6 +2,8 @@ #include "scenemanager.hpp" #include "texturemanager.hpp" +#include "niffilemanager.hpp" +#include "keyframemanager.hpp" namespace Resource { @@ -9,8 +11,10 @@ namespace Resource ResourceSystem::ResourceSystem(const VFS::Manager *vfs) : mVFS(vfs) { + mNifFileManager.reset(new NifFileManager(vfs)); + mKeyframeManager.reset(new KeyframeManager(vfs)); mTextureManager.reset(new TextureManager(vfs)); - mSceneManager.reset(new SceneManager(vfs, mTextureManager.get())); + mSceneManager.reset(new SceneManager(vfs, mTextureManager.get(), mNifFileManager.get())); } ResourceSystem::~ResourceSystem() @@ -28,6 +32,21 @@ namespace Resource return mTextureManager.get(); } + NifFileManager* ResourceSystem::getNifFileManager() + { + return mNifFileManager.get(); + } + + KeyframeManager* ResourceSystem::getKeyframeManager() + { + return mKeyframeManager.get(); + } + + void ResourceSystem::clearCache() + { + mNifFileManager->clearCache(); + } + const VFS::Manager* ResourceSystem::getVFS() const { return mVFS; diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 7c00a11eef..3e1a793cac 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -13,8 +13,10 @@ namespace Resource class SceneManager; class TextureManager; + class NifFileManager; + class KeyframeManager; - /// @brief Wrapper class that constructs and provides access to the various resource subsystems. + /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but /// are built around the use of a single virtual file system. class ResourceSystem @@ -25,12 +27,19 @@ namespace Resource SceneManager* getSceneManager(); TextureManager* getTextureManager(); + NifFileManager* getNifFileManager(); + KeyframeManager* getKeyframeManager(); + + /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer referenced. + void clearCache(); const VFS::Manager* getVFS() const; private: std::auto_ptr mSceneManager; std::auto_ptr mTextureManager; + std::auto_ptr mNifFileManager; + std::auto_ptr mKeyframeManager; const VFS::Manager* mVFS; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 08fa7bc9b3..1d1c5a3656 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -19,48 +20,70 @@ #include #include +#include "texturemanager.hpp" +#include "niffilemanager.hpp" + namespace { class InitWorldSpaceParticlesVisitor : public osg::NodeVisitor { public: - InitWorldSpaceParticlesVisitor() + /// @param mask The node mask to set on ParticleSystem nodes. + InitWorldSpaceParticlesVisitor(unsigned int mask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mMask(mask) + { + } + + bool isWorldSpaceParticleSystem(osgParticle::ParticleSystem* partsys) { + // HACK: ParticleSystem has no getReferenceFrame() + return (partsys->getUserDataContainer() + && partsys->getUserDataContainer()->getNumDescriptions() > 0 + && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } - void apply(osg::Node& node) + void apply(osg::Geode& geode) { - if (osg::Geode* geode = node.asGeode()) + for (unsigned int i=0;igetNumDrawables();++i) + if (osgParticle::ParticleSystem* partsys = dynamic_cast(geode.getDrawable(i))) { - if (osgParticle::ParticleSystem* partsys = dynamic_cast(geode->getDrawable(i))) + if (isWorldSpaceParticleSystem(partsys)) { - // HACK: ParticleSystem has no getReferenceFrame() - if (partsys->getUserDataContainer() - && partsys->getUserDataContainer()->getNumDescriptions() > 0 - && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace") - { - // HACK: Ignore the InverseWorldMatrix transform the geode is attached to - if (geode->getNumParents() && geode->getParent(0)->getNumParents()) - transformInitialParticles(partsys, geode->getParent(0)->getParent(0)); - } - geode->setNodeMask((1<<10)); //MWRender::Mask_ParticleSystem + // HACK: Ignore the InverseWorldMatrix transform the geode is attached to + if (geode.getNumParents() && geode.getParent(0)->getNumParents()) + transformInitialParticles(partsys, geode.getParent(0)->getParent(0)); } + geode.setNodeMask(mMask); } } + } - traverse(node); +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. + void apply(osg::Drawable& drw) + { + if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) + { + if (isWorldSpaceParticleSystem(partsys)) + { + // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to + if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) + transformInitialParticles(partsys, partsys->getParent(0)->getParent(0)); + } + partsys->setNodeMask(mMask); + } } +#endif void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { osg::MatrixList mats = node->getWorldMatrices(); if (mats.empty()) return; - osg::Matrix worldMat = mats[0]; + osg::Matrixf worldMat = mats[0]; worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node for (int i=0; inumParticles(); ++i) { @@ -74,22 +97,91 @@ namespace box.expandBy(sphere); partsys->setInitialBound(box); } + private: + unsigned int mMask; }; - } namespace Resource { - SceneManager::SceneManager(const VFS::Manager *vfs, Resource::TextureManager* textureManager) + SceneManager::SceneManager(const VFS::Manager *vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager) : mVFS(vfs) , mTextureManager(textureManager) + , mNifFileManager(nifFileManager) + , mParticleSystemMask(~0u) { } SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types + + } + + /// @brief Callback to read image files from the VFS. + class ImageReadCallback : public osgDB::ReadFileCallback + { + public: + ImageReadCallback(Resource::TextureManager* textureMgr) + : mTextureManager(textureMgr) + { + } + + virtual osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options) + { + try + { + return osgDB::ReaderWriter::ReadResult(mTextureManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); + } + catch (std::exception& e) + { + return osgDB::ReaderWriter::ReadResult(e.what()); + } + } + + private: + Resource::TextureManager* mTextureManager; + }; + + std::string getFileExtension(const std::string& file) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + return file.substr(extPos+1); + return std::string(); + } + + osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::TextureManager* textureMgr, Resource::NifFileManager* nifFileManager) + { + std::string ext = getFileExtension(normalizedFilename); + if (ext == "nif") + return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), textureMgr); + else + { + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + if (!reader) + { + std::stringstream errormsg; + errormsg << "Error loading " << normalizedFilename << ": no readerwriter for '" << ext << "' found" << std::endl; + throw std::runtime_error(errormsg.str()); + } + + osg::ref_ptr options (new osgDB::Options); + // Set a ReadFileCallback so that image files referenced in the model are read from our virtual file system instead of the osgDB. + // Note, for some formats (.obj/.mtl) that reference other (non-image) files a findFileCallback would be necessary. + // but findFileCallback does not support virtual files, so we can't implement it. + options->setReadFileCallback(new ImageReadCallback(textureMgr)); + + osgDB::ReaderWriter::ReadResult result = reader->readNode(*file, options); + if (!result.success()) + { + std::stringstream errormsg; + errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; + throw std::runtime_error(errormsg.str()); + } + return result.getNode(); + } } osg::ref_ptr SceneManager::getTemplate(const std::string &name) @@ -100,23 +192,22 @@ namespace Resource Index::iterator it = mIndex.find(normalized); if (it == mIndex.end()) { - // TODO: add support for non-NIF formats osg::ref_ptr loaded; try { Files::IStreamPtr file = mVFS->get(normalized); - loaded = NifOsg::Loader::load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)), mTextureManager); + loaded = load(file, normalized, mTextureManager, mNifFileManager); } catch (std::exception& e) { std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error.nif instead" << std::endl; Files::IStreamPtr file = mVFS->get("meshes/marker_error.nif"); - loaded = NifOsg::Loader::load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)), mTextureManager); + normalized = "meshes/marker_error.nif"; + loaded = load(file, normalized, mTextureManager, mNifFileManager); } osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); - // TODO: run SharedStateManager::prune on unload if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); @@ -142,26 +233,6 @@ namespace Resource return cloned; } - osg::ref_ptr SceneManager::getKeyframes(const std::string &name) - { - std::string normalized = name; - mVFS->normalizeFilename(normalized); - - KeyframeIndex::iterator it = mKeyframeIndex.find(normalized); - if (it == mKeyframeIndex.end()) - { - Files::IStreamPtr file = mVFS->get(normalized); - - osg::ref_ptr loaded (new NifOsg::KeyframeHolder); - NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)), *loaded.get()); - - mKeyframeIndex[normalized] = loaded; - return loaded; - } - else - return it->second; - } - void SceneManager::attachTo(osg::Node *instance, osg::Group *parentNode) const { parentNode->addChild(instance); @@ -183,7 +254,7 @@ namespace Resource void SceneManager::notifyAttached(osg::Node *node) const { - InitWorldSpaceParticlesVisitor visitor; + InitWorldSpaceParticlesVisitor visitor (mParticleSystemMask); node->accept(visitor); } @@ -197,4 +268,9 @@ namespace Resource return mTextureManager; } + void SceneManager::setParticleSystemMask(unsigned int mask) + { + mParticleSystemMask = mask; + } + } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 168247a156..67fa2ab433 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -10,6 +10,7 @@ namespace Resource { class TextureManager; + class NifFileManager; } namespace VFS @@ -17,11 +18,6 @@ namespace VFS class Manager; } -namespace NifOsg -{ - class KeyframeHolder; -} - namespace osgUtil { class IncrementalCompileOperation; @@ -34,7 +30,7 @@ namespace Resource class SceneManager { public: - SceneManager(const VFS::Manager* vfs, Resource::TextureManager* textureManager); + SceneManager(const VFS::Manager* vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); /// Get a read-only copy of this scene "template" @@ -56,9 +52,6 @@ namespace Resource /// @note Assumes the given instance was not attached to any parents before. void attachTo(osg::Node* instance, osg::Group* parentNode) const; - /// Get a read-only copy of the given keyframe file. - osg::ref_ptr getKeyframes(const std::string& name); - /// Manually release created OpenGL objects for the given graphics context. This may be required /// in cases where multiple contexts are used over the lifetime of the application. void releaseGLObjects(osg::State* state); @@ -66,26 +59,29 @@ namespace Resource /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); - /// @note If you used SceneManager::attachTo, this was called automatically. + /// @note SceneManager::attachTo calls this method automatically, only needs to be called by users if manually attaching void notifyAttached(osg::Node* node) const; const VFS::Manager* getVFS() const; Resource::TextureManager* getTextureManager(); + /// @param mask The node mask to apply to loaded particle system nodes. + void setParticleSystemMask(unsigned int mask); + private: const VFS::Manager* mVFS; Resource::TextureManager* mTextureManager; + Resource::NifFileManager* mNifFileManager; osg::ref_ptr mIncrementalCompileOperation; + unsigned int mParticleSystemMask; + // observer_ptr? typedef std::map > Index; Index mIndex; - typedef std::map > KeyframeIndex; - KeyframeIndex mKeyframeIndex; - SceneManager(const SceneManager&); void operator = (const SceneManager&); }; diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp index c2f76a527f..d7f3fc61a1 100644 --- a/components/resource/texturemanager.cpp +++ b/components/resource/texturemanager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -65,6 +66,45 @@ namespace Resource mUnRefImageDataAfterApply = unref; } + void TextureManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy, + osgViewer::Viewer *viewer) + { + osg::Texture::FilterMode min = osg::Texture::LINEAR; + osg::Texture::FilterMode mag = osg::Texture::LINEAR; + + if(magfilter == "nearest") + mag = osg::Texture::NEAREST; + else if(magfilter != "linear") + std::cerr<< "Invalid texture mag filter: "<stopThreading(); + setFilterSettings(min, mag, maxAnisotropy); + if(viewer) viewer->startThreading(); + } + void TextureManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) { mMinFilter = minFilter; @@ -119,7 +159,7 @@ namespace Resource case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { -#if OSG_MIN_VERSION_REQUIRED(3,3,3) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); if (exts && !exts->isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. @@ -143,6 +183,57 @@ namespace Resource return true; } + osg::ref_ptr TextureManager::getImage(const std::string &filename) + { + std::string normalized = filename; + mVFS->normalizeFilename(normalized); + std::map >::iterator found = mImages.find(normalized); + if (found != mImages.end()) + return found->second; + else + { + Files::IStreamPtr stream; + try + { + stream = mVFS->get(normalized.c_str()); + } + catch (std::exception& e) + { + std::cerr << "Failed to open image: " << e.what() << std::endl; + return NULL; + } + + osg::ref_ptr opts (new osgDB::Options); + opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option + size_t extPos = normalized.find_last_of('.'); + std::string ext; + if (extPos != std::string::npos && extPos+1 < normalized.size()) + ext = normalized.substr(extPos+1); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + if (!reader) + { + std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; + return NULL; + } + + osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); + if (!result.success()) + { + std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; + return NULL; + } + + osg::Image* image = result.getImage(); + if (!checkSupported(image, filename)) + { + return NULL; + } + + mImages.insert(std::make_pair(normalized, image)); + return image; + } + } + osg::ref_ptr TextureManager::getTexture2D(const std::string &filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT) { std::string normalized = filename; diff --git a/components/resource/texturemanager.hpp b/components/resource/texturemanager.hpp index 2ee3baa773..e12dfa0900 100644 --- a/components/resource/texturemanager.hpp +++ b/components/resource/texturemanager.hpp @@ -8,6 +8,11 @@ #include #include +namespace osgViewer +{ + class Viewer; +} + namespace VFS { class Manager; @@ -23,18 +28,20 @@ namespace Resource TextureManager(const VFS::Manager* vfs); ~TextureManager(); - /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! - void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); + void setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy, + osgViewer::Viewer *view); /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, /// otherwise should be disabled to reduce memory usage. void setUnRefImageDataAfterApply(bool unref); /// Create or retrieve a Texture2D using the specified image filename, and wrap parameters. + /// Returns the dummy texture if the given texture is not found. osg::ref_ptr getTexture2D(const std::string& filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT); /// Create or retrieve an Image - //osg::ref_ptr getImage(const std::string& filename); + osg::ref_ptr getImage(const std::string& filename); const VFS::Manager* getVFS() { return mVFS; } @@ -49,7 +56,7 @@ namespace Resource typedef std::pair, std::string> MapKey; - std::map > mImages; + std::map > mImages; std::map > mTextures; @@ -57,6 +64,9 @@ namespace Resource bool mUnRefImageDataAfterApply; + /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! + void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); + TextureManager(const TextureManager&); void operator = (const TextureManager&); }; diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index e4b4f63bb1..a372b1ebd8 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -1,6 +1,7 @@ #include "clone.hpp" #include +#include #include #include @@ -53,6 +54,7 @@ namespace SceneUtil osg::CopyOp copyop = *this; copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS); +#if OSG_VERSION_LESS_THAN(3,5,0) /* Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor: @@ -71,12 +73,12 @@ namespace SceneUtil In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from, causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation. - Possible fix submitted to osg-submissions ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ). + Fixed in OSG 3.5 ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ). */ copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES); - +#endif osg::Drawable* cloned = osg::clone(drawable, copyop); if (cloned->getUpdateCallback()) diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index a2c1cdcd39..7762b48d07 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -65,7 +65,7 @@ namespace SceneUtil void ControllerVisitor::apply(osg::Node &node) { -#if OSG_MIN_VERSION_REQUIRED(3,3,3) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); #else osg::NodeCallback* callback = node.getUpdateCallback(); @@ -96,7 +96,7 @@ namespace SceneUtil { osg::Drawable* drw = geode.getDrawable(i); -#if OSG_MIN_VERSION_REQUIRED(3,3,3) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = drw->getUpdateCallback(); #else osg::Drawable::UpdateCallback* callback = drw->getUpdateCallback(); diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index d31e3d1075..511937a282 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -61,8 +61,10 @@ namespace SceneUtil void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) { double time = nv->getFrameStamp()->getSimulationTime(); - if (time == mLastTime) - return; + + // disabled early out, light state needs to be set every frame regardless of change, due to the double buffering + //if (time == mLastTime) + // return; float dt = static_cast(time - mLastTime); mLastTime = time; @@ -76,7 +78,7 @@ namespace SceneUtil if(mType == LT_Pulse || mType == LT_PulseSlow) { cycle_time = 2.0f * pi; - time_distortion = 20.0f; + time_distortion = mType == LT_Pulse ? 20.0f : 4.f; } else { @@ -114,11 +116,13 @@ namespace SceneUtil else if(mType == LT_FlickerSlow) brightness = 0.75f + flickerAmplitude(mDeltaCount*slow)*0.25f; else if(mType == LT_Pulse) - brightness = 1.0f + pulseAmplitude(mDeltaCount*fast)*0.25f; + brightness = 0.7f + pulseAmplitude(mDeltaCount*fast)*0.3f; else if(mType == LT_PulseSlow) - brightness = 1.0f + pulseAmplitude(mDeltaCount*slow)*0.25f; + brightness = 0.7f + pulseAmplitude(mDeltaCount*slow)*0.3f; + + static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * brightness); - static_cast(node)->getLight()->setDiffuse(mDiffuseColor * brightness); + traverse(node, nv); } void LightController::setDiffuse(osg::Vec4f color) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 18d7ddd46b..1706bb2b1e 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -98,7 +98,7 @@ namespace SceneUtil throw std::runtime_error("can't find parent LightManager"); } - mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath())); + mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber()); traverse(node, nv); } @@ -131,6 +131,7 @@ namespace SceneUtil LightManager::LightManager() : mStartLight(0) + , mLightingMask(~0u) { setUpdateCallback(new LightManagerUpdateCallback); } @@ -138,47 +139,63 @@ namespace SceneUtil LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) + , mLightingMask(copy.mLightingMask) { } + void LightManager::setLightingMask(unsigned int mask) + { + mLightingMask = mask; + } + + unsigned int LightManager::getLightingMask() const + { + return mLightingMask; + } + void LightManager::update() { mLights.clear(); mLightsInViewSpace.clear(); // do an occasional cleanup for orphaned lights - if (mStateSetCache.size() > 5000) - mStateSetCache.clear(); + for (int i=0; i<2; ++i) + { + if (mStateSetCache[i].size() > 5000) + mStateSetCache[i].clear(); + } } - void LightManager::addLight(LightSource* lightSource, osg::Matrix worldMat) + void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum) { LightSourceTransform l; l.mLightSource = lightSource; l.mWorldMatrix = worldMat; - lightSource->getLight()->setPosition(osg::Vec4f(worldMat.getTrans().x(), + lightSource->getLight(frameNum)->setPosition(osg::Vec4f(worldMat.getTrans().x(), worldMat.getTrans().y(), worldMat.getTrans().z(), 1.f)); mLights.push_back(l); } - osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList) + osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; for (unsigned int i=0; imLightSource->getId()); - LightStateSetMap::iterator found = mStateSetCache.find(hash); - if (found != mStateSetCache.end()) + LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; + + LightStateSetMap::iterator found = stateSetCache.find(hash); + if (found != stateSetCache.end()) return found->second; else { std::vector > lights; for (unsigned int i=0; imLightSource->getLight()); + lights.push_back(lightList[i]->mLightSource->getLight(frameNum)); osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); @@ -188,7 +205,7 @@ namespace SceneUtil stateset->setAttribute(attr, osg::StateAttribute::ON); stateset->setAssociatedModes(attr, osg::StateAttribute::ON); - mStateSetCache.insert(std::make_pair(hash, stateset)); + stateSetCache.insert(std::make_pair(hash, stateset)); return stateset; } } @@ -209,7 +226,7 @@ namespace SceneUtil for (std::vector::iterator lightIt = mLights.begin(); lightIt != mLights.end(); ++lightIt) { - osg::Matrix worldViewMat = lightIt->mWorldMatrix * (*viewMatrix); + osg::Matrixf worldViewMat = lightIt->mWorldMatrix * (*viewMatrix); osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), lightIt->mLightSource->getRadius()); transformBoundingSphere(worldViewMat, viewBound); @@ -237,17 +254,18 @@ namespace SceneUtil LightSource::LightSource() : mRadius(0.f) { - setNodeMask(Mask_Lit); setUpdateCallback(new CollectLightCallback); mId = sLightId++; } LightSource::LightSource(const LightSource ©, const osg::CopyOp ©op) : osg::Node(copy, copyop) - , mLight(copy.mLight) , mRadius(copy.mRadius) { mId = sLightId++; + + for (int i=0; i<2; ++i) + mLight[i] = osg::clone(copy.mLight[i].get(), copyop); } @@ -260,12 +278,6 @@ namespace SceneUtil { osgUtil::CullVisitor* cv = static_cast(nv); - if (!(cv->getCurrentCamera()->getCullMask()&Mask_Lit)) - { - traverse(node, nv); - return; - } - if (!mLightManager) { mLightManager = findLightManager(nv->getNodePath()); @@ -276,41 +288,50 @@ namespace SceneUtil } } + if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask())) + { + traverse(node, nv); + return; + } + // Possible optimizations: // - cull list of lights by the camera frustum // - organize lights in a quad tree - // Don't use Camera::getViewMatrix, that one might be relative to another camera! - const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); - const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); - - if (lights.size()) + // update light list if necessary + // makes sure we don't update it more than once per frame when rendering with multiple cameras + if (mLastFrameNumber != nv->getTraversalNumber()) { + mLastFrameNumber = nv->getTraversalNumber(); + + // Don't use Camera::getViewMatrix, that one might be relative to another camera! + const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); + const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); + // we do the intersections in view space osg::BoundingSphere nodeBound = node->getBound(); osg::Matrixf mat = *cv->getModelViewMatrix(); transformBoundingSphere(mat, nodeBound); - LightManager::LightList lightList; + mLightList.clear(); for (unsigned int i=0; i (8 - mLightManager->getStartLight()); - if (lightList.size() > maxLights) + osg::StateSet* stateset = NULL; + + if (mLightList.size() > maxLights) { // remove lights culled by this camera + LightManager::LightList lightList = mLightList; for (LightManager::LightList::iterator it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); @@ -334,9 +355,11 @@ namespace SceneUtil while (lightList.size() > maxLights) lightList.pop_back(); } + stateset = mLightManager->getLightListStateSet(lightList, nv->getTraversalNumber()); } + else + stateset = mLightManager->getLightListStateSet(mLightList, nv->getTraversalNumber()); - osg::StateSet* stateset = mLightManager->getLightListStateSet(lightList); cv->pushStateSet(stateset); diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 3e0329c8bf..3e6d3251bb 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -9,15 +9,20 @@ namespace SceneUtil { - // This mask should be included in the Cull and Update visitor's traversal mask if lighting is desired. - const int Mask_Lit = (1<<16); - /// LightSource managed by a LightManager. + /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene + /// so do not need to be managed by a LightManager - so for directional lights use a plain osg::LightSource instead. + /// @note LightSources must be decorated by a LightManager node in order to have an effect. Typical use would + /// be one LightManager as the root of the scene graph. + /// @note One needs to attach LightListCallback's to the scene to have objects receive lighting from LightSources. + /// See the documentation of LightListCallback for more information. + /// @note The position of the contained osg::Light is automatically updated based on the LightSource's world position. class LightSource : public osg::Node { - osg::ref_ptr mLight; + // double buffered osg::Light's, since one of them may be in use by the draw thread at any given time + osg::ref_ptr mLight[2]; - // The activation radius + // LightSource will affect objects within this radius float mRadius; int mId; @@ -35,29 +40,38 @@ namespace SceneUtil return mRadius; } + /// The LightSource will affect objects within this radius. void setRadius(float radius) { mRadius = radius; } - osg::Light* getLight() + /// Get the osg::Light safe for modification in the given frame. + /// @par May be used externally to animate the light's color/attenuation properties, + /// and is used internally to synchronize the light's position with the position of the LightSource. + osg::Light* getLight(unsigned int frame) { - return mLight; + return mLight[frame % 2]; } + /// @warning It is recommended not to replace an existing osg::Light, because there might still be + /// references to it in the light StateSet cache that are associated with this LightSource's ID. + /// These references will stay valid due to ref_ptr but will point to the old object. + /// @warning Do not modify the \a light after you've called this function. void setLight(osg::Light* light) { - mLight = light; + mLight[0] = light; + mLight[1] = osg::clone(light); } - int getId() + /// Get the unique ID for this light source. + int getId() const { return mId; } }; - /// All light sources must be a child of the LightManager node. The LightManager can be anywhere in the scene graph, - /// but would be typically somewhere near the top. + /// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the subgraph. class LightManager : public osg::Group { public: @@ -68,16 +82,29 @@ namespace SceneUtil LightManager(const LightManager& copy, const osg::CopyOp& copyop); - // Called automatically by the UpdateCallback + /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. + /// By default, it's ~0u i.e. always on. + /// If you have some views that do not require lighting, then set the Camera's cull mask to not include + /// the lightingMask for a much faster cull and rendering. + void setLightingMask (unsigned int mask); + + unsigned int getLightingMask() const; + + /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. + void setStartLight(int start); + + int getStartLight() const; + + /// Internal use only, called automatically by the LightManager's UpdateCallback void update(); - // Called automatically by the LightSource's UpdateCallback - void addLight(LightSource* lightSource, osg::Matrix worldMat); + /// Internal use only, called automatically by the LightSource's UpdateCallback + void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum); struct LightSourceTransform { LightSource* mLightSource; - osg::Matrix mWorldMatrix; + osg::Matrixf mWorldMatrix; }; const std::vector& getLights() const; @@ -92,12 +119,7 @@ namespace SceneUtil typedef std::vector LightList; - osg::ref_ptr getLightListStateSet(const LightList& lightList); - - /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. - void setStartLight(int start); - - int getStartLight() const; + osg::ref_ptr getLightListStateSet(const LightList& lightList, unsigned int frameNum); private: // Lights collected from the scene graph. Only valid during the cull traversal. @@ -108,27 +130,42 @@ namespace SceneUtil // < Light list hash , StateSet > typedef std::map > LightStateSetMap; - LightStateSetMap mStateSetCache; + LightStateSetMap mStateSetCache[2]; int mStartLight; + + unsigned int mLightingMask; }; + /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via + /// node->addCullCallback(new LightListCallback). Once a light list callback is added to a node, that node and all + /// its child nodes can receive lighting. + /// @par The placement of these LightListCallbacks affects the granularity of light lists. Having too fine grained + /// light lists can result in degraded performance. Too coarse grained light lists can result in lights no longer + /// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good + /// starting point is to attach a LightListCallback to each game object's base node. + /// @note Not thread safe for CullThreadPerCamera threading mode. class LightListCallback : public osg::NodeCallback { public: LightListCallback() : mLightManager(NULL) + , mLastFrameNumber(0) {} - LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) - : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop), mLightManager(copy.mLightManager) + LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop) + , mLightManager(copy.mLightManager) + , mLastFrameNumber(0) {} - META_Object(NifOsg, LightListCallback) + META_Object(SceneUtil, LightListCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv); private: LightManager* mLightManager; + unsigned int mLastFrameNumber; + LightManager::LightList mLightList; }; /// @brief Configures a light's attenuation according to vanilla Morrowind attenuation settings. diff --git a/components/sceneutil/positionattitudetransform.cpp b/components/sceneutil/positionattitudetransform.cpp new file mode 100644 index 0000000000..5f6b57e979 --- /dev/null +++ b/components/sceneutil/positionattitudetransform.cpp @@ -0,0 +1,51 @@ +#include "positionattitudetransform.hpp" + +#include + +namespace SceneUtil +{ + +PositionAttitudeTransform::PositionAttitudeTransform(): + _scale(1.0,1.0,1.0) +{ +} + +bool PositionAttitudeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const +{ + if (_referenceFrame==RELATIVE_RF) + { + matrix.preMultTranslate(_position); + matrix.preMultRotate(_attitude); + matrix.preMultScale(_scale); + } + else // absolute + { + matrix.makeRotate(_attitude); + matrix.postMultTranslate(_position); + matrix.preMultScale(_scale); + } + return true; +} + + +bool PositionAttitudeTransform::computeWorldToLocalMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const +{ + if (_scale.x() == 0.0 || _scale.y() == 0.0 || _scale.z() == 0.0) + return false; + + if (_referenceFrame==RELATIVE_RF) + { + matrix.postMultTranslate(-_position); + matrix.postMultRotate(_attitude.inverse()); + matrix.postMultScale(osg::Vec3f(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z())); + } + else // absolute + { + matrix.makeRotate(_attitude.inverse()); + matrix.preMultTranslate(-_position); + matrix.postMultScale(osg::Vec3f(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z())); + } + return true; +} + +} diff --git a/components/sceneutil/positionattitudetransform.hpp b/components/sceneutil/positionattitudetransform.hpp new file mode 100644 index 0000000000..b6f92ee84e --- /dev/null +++ b/components/sceneutil/positionattitudetransform.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_COMPONENTS_POSITIONATTITUDE_TRANSFORM_H +#define OPENMW_COMPONENTS_POSITIONATTITUDE_TRANSFORM_H + +#include + +namespace SceneUtil +{ + +/// @brief A customized version of osg::PositionAttitudeTransform optimized for speed. +/// Uses single precision values. Also removed _pivotPoint which we don't need. +class PositionAttitudeTransform : public osg::Transform +{ + public : + PositionAttitudeTransform(); + + PositionAttitudeTransform(const PositionAttitudeTransform& pat,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY): + Transform(pat,copyop), + _position(pat._position), + _attitude(pat._attitude), + _scale(pat._scale){} + + + META_Node(SceneUtil, PositionAttitudeTransform) + + inline void setPosition(const osg::Vec3f& pos) { _position = pos; dirtyBound(); } + inline const osg::Vec3f& getPosition() const { return _position; } + + + inline void setAttitude(const osg::Quat& quat) { _attitude = quat; dirtyBound(); } + inline const osg::Quat& getAttitude() const { return _attitude; } + + + inline void setScale(const osg::Vec3f& scale) { _scale = scale; dirtyBound(); } + inline const osg::Vec3f& getScale() const { return _scale; } + + + + virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,osg::NodeVisitor* nv) const; + virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,osg::NodeVisitor* nv) const; + + + protected : + + virtual ~PositionAttitudeTransform() {} + + osg::Vec3f _position; + osg::Quat _attitude; + osg::Vec3f _scale; +}; + +} + +#endif diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 8eb08f546d..88b907fafa 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -2,9 +2,9 @@ #include #include - #include +#include #include #include "skeleton.hpp" @@ -58,6 +58,14 @@ public: } }; +// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix. +// So we return nothing. Bounds are updated every frame in the UpdateCallback. +class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback +{ +public: + virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); } +}; + RigGeometry::RigGeometry() : mSkeleton(NULL) , mLastFrameNumber(0) @@ -66,6 +74,7 @@ RigGeometry::RigGeometry() setCullCallback(new UpdateRigGeometry); setUpdateCallback(new UpdateRigBounds); setSupportsDisplayList(false); + setComputeBoundingBoxCallback(new DummyComputeBoundCallback); } RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) @@ -73,7 +82,7 @@ RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) , mSkeleton(NULL) , mInfluenceMap(copy.mInfluenceMap) , mLastFrameNumber(0) - , mBoundsFirstFrame(copy.mBoundsFirstFrame) + , mBoundsFirstFrame(true) { setSourceGeometry(copy.mSourceGeometry); } @@ -176,7 +185,7 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) return true; } -void accummulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, float weight, osg::Matrixf& result) +void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, float weight, osg::Matrixf& result) { osg::Matrixf m = invBindMatrix * matrix; float* ptr = m.ptr(); @@ -202,6 +211,8 @@ void RigGeometry::update(osg::NodeVisitor* nv) { if (!mSkeleton) { + std::cerr << "RigGeometry rendering with no skeleton, should have been initialized by UpdateVisitor" << std::endl; + // try to recover anyway, though rendering is likely to be incorrect. if (!initFromParentSkeleton(nv)) return; } @@ -209,14 +220,12 @@ void RigGeometry::update(osg::NodeVisitor* nv) if (!mSkeleton->getActive() && mLastFrameNumber != 0) return; - if (mLastFrameNumber == nv->getFrameStamp()->getFrameNumber()) + if (mLastFrameNumber == nv->getTraversalNumber()) return; - mLastFrameNumber = nv->getFrameStamp()->getFrameNumber(); + mLastFrameNumber = nv->getTraversalNumber(); mSkeleton->updateBoneMatrices(nv); - osg::Matrixf geomToSkel = getGeomToSkelMatrix(nv); - // skinning osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); @@ -237,9 +246,9 @@ void RigGeometry::update(osg::NodeVisitor* nv) const osg::Matrix& invBindMatrix = weightIt->first.second; float weight = weightIt->second; const osg::Matrixf& boneMatrix = bone->mMatrixInSkeletonSpace; - accummulateMatrix(invBindMatrix, boneMatrix, weight, resultMat); + accumulateMatrix(invBindMatrix, boneMatrix, weight, resultMat); } - resultMat = resultMat * geomToSkel; + resultMat = resultMat * mGeomToSkelMatrix; for (std::vector::const_iterator vertexIt = it->second.begin(); vertexIt != it->second.end(); ++vertexIt) { @@ -267,24 +276,31 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) mSkeleton->updateBoneMatrices(nv); - osg::Matrixf geomToSkel = getGeomToSkelMatrix(nv); + updateGeomToSkelMatrix(nv); + osg::BoundingBox box; for (BoneSphereMap::const_iterator it = mBoneSphereMap.begin(); it != mBoneSphereMap.end(); ++it) { Bone* bone = it->first; osg::BoundingSpheref bs = it->second; - transformBoundingSphere(bone->mMatrixInSkeletonSpace * geomToSkel, bs); + transformBoundingSphere(bone->mMatrixInSkeletonSpace * mGeomToSkelMatrix, bs); box.expandBy(bs); } _boundingBox = box; + _boundingBoxComputed = true; +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + // in OSG 3.3.3 and up Drawable inherits from Node, so has a bounding sphere as well. + _boundingSphere = osg::BoundingSphere(_boundingBox); + _boundingSphereComputed = true; +#endif for (unsigned int i=0; idirtyBound(); } -osg::Matrixf RigGeometry::getGeomToSkelMatrix(osg::NodeVisitor *nv) +void RigGeometry::updateGeomToSkelMatrix(osg::NodeVisitor *nv) { - osg::NodePath path; + mSkelToGeomPath.clear(); bool foundSkel = false; for (osg::NodePath::const_iterator it = nv->getNodePath().begin(); it != nv->getNodePath().end(); ++it) { @@ -294,10 +310,9 @@ osg::Matrixf RigGeometry::getGeomToSkelMatrix(osg::NodeVisitor *nv) foundSkel = true; } else - path.push_back(*it); + mSkelToGeomPath.push_back(*it); } - return osg::computeWorldToLocal(path); - + mGeomToSkelMatrix = osg::computeWorldToLocal(mSkelToGeomPath); } void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index e51fc0cf69..03c287b812 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -19,7 +19,7 @@ namespace SceneUtil RigGeometry(); RigGeometry(const RigGeometry& copy, const osg::CopyOp& copyop); - META_Object(NifOsg, RigGeometry) + META_Object(SceneUtil, RigGeometry) struct BoneInfluence { @@ -48,6 +48,9 @@ namespace SceneUtil osg::ref_ptr mSourceGeometry; Skeleton* mSkeleton; + osg::NodePath mSkelToGeomPath; + osg::Matrixf mGeomToSkelMatrix; + osg::ref_ptr mInfluenceMap; typedef std::pair BoneBindMatrixPair; @@ -69,7 +72,7 @@ namespace SceneUtil bool initFromParentSkeleton(osg::NodeVisitor* nv); - osg::Matrixf getGeomToSkelMatrix(osg::NodeVisitor* nv); + void updateGeomToSkelMatrix(osg::NodeVisitor* nv); }; } diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 5c2af4397e..d1299c0587 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include namespace SceneUtil @@ -23,7 +25,7 @@ public: if (!bone) return; - mCache[bone->getName()] = std::make_pair(getNodePath(), bone); + mCache[Misc::StringUtils::lowerCase(bone->getName())] = std::make_pair(getNodePath(), bone); traverse(node); } @@ -59,7 +61,7 @@ Bone* Skeleton::getBone(const std::string &name) mBoneCacheInit = true; } - BoneCache::iterator found = mBoneCache.find(name); + BoneCache::iterator found = mBoneCache.find(Misc::StringUtils::lowerCase(name)); if (found == mBoneCache.end()) return NULL; @@ -104,10 +106,10 @@ Bone* Skeleton::getBone(const std::string &name) void Skeleton::updateBoneMatrices(osg::NodeVisitor* nv) { - if (nv->getFrameStamp()->getFrameNumber() != mLastFrameNumber) + if (nv->getTraversalNumber() != mLastFrameNumber) mNeedToUpdateBoneMatrices = true; - mLastFrameNumber = nv->getFrameStamp()->getFrameNumber(); + mLastFrameNumber = nv->getTraversalNumber(); if (mNeedToUpdateBoneMatrices) { @@ -135,7 +137,10 @@ bool Skeleton::getActive() const void Skeleton::traverse(osg::NodeVisitor& nv) { - if (!mActive && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0) + if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR + // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized + // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node + && mLastFrameNumber != 0 && mLastFrameNumber+2 <= nv.getTraversalNumber()) return; osg::Group::traverse(nv); } @@ -157,6 +162,7 @@ void Bone::update(const osg::Matrixf* parentMatrixInSkeletonSpace) if (!mNode) { std::cerr << "Bone without node " << std::endl; + return; } if (parentMatrixInSkeletonSpace) mMatrixInSkeletonSpace = mNode->getMatrix() * (*parentMatrixInSkeletonSpace); diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index d4418fa272..d98d367518 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -39,7 +39,7 @@ namespace SceneUtil Skeleton(); Skeleton(const Skeleton& copy, const osg::CopyOp& copyop); - META_Node(NifOsg, Skeleton) + META_Node(SceneUtil, Skeleton) /// Retrieve a bone by name. Bone* getBone(const std::string& name); diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 36aa683dbe..0e325082e1 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -20,11 +20,10 @@ namespace SceneUtil } } - // Swap to make the StateSet in [0] writable, [1] is now the StateSet that was queued by the last frame - std::swap(mStateSets[0], mStateSets[1]); - node->setStateSet(mStateSets[0]); + osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2]; + node->setStateSet(stateset); - apply(mStateSets[0], nv); + apply(stateset, nv); traverse(node, nv); } diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index 37d08e025c..51398844cf 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -12,9 +12,9 @@ namespace SceneUtil /// DYNAMIC data variance but that would undo all the benefits of the threading model - having the cull and draw /// traversals run in parallel can yield up to 200% framerates. /// @par Race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns, - /// the first StateSet is the one we can write to, the second is the one currently in use by the draw traversal of the last frame. - /// After a frame is completed the places are swapped. + /// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame. /// @par Must be set as UpdateCallback on a Node. + /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater. class StateSetUpdater : public osg::NodeCallback { diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 52f9c9e54e..3add3bb236 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -3,7 +3,7 @@ namespace SceneUtil { -void transformBoundingSphere (const osg::Matrix& matrix, osg::BoundingSphere& bsphere) +void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) { osg::BoundingSphere::vec_type xdash = bsphere._center; xdash.x() += bsphere._radius; diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index c99771c5ea..d8fefdb291 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -11,7 +11,7 @@ namespace SceneUtil // Transform a bounding sphere by a matrix // based off private code in osg::Transform // TODO: patch osg to make public - void transformBoundingSphere (const osg::Matrix& matrix, osg::BoundingSphere& bsphere); + void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere); osg::Vec4f colourFromRGB (unsigned int clr); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp new file mode 100644 index 0000000000..0a5ad2d006 --- /dev/null +++ b/components/sceneutil/visitor.cpp @@ -0,0 +1,34 @@ +#include "visitor.hpp" + +#include + +#include + +#include + +namespace SceneUtil +{ + + void FindByNameVisitor::apply(osg::Group &group) + { + if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) + { + mFoundNode = &group; + return; + } + traverse(group); + } + + void DisableFreezeOnCullVisitor::apply(osg::Geode &geode) + { + for (unsigned int i=0; i(&drw)) + partsys->setFreezeOnCull(false); + } + +} diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index b9342b884e..dcfefe9cd6 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -3,12 +3,12 @@ #include -#include - // Commonly used scene graph visitors namespace SceneUtil { + // Find a Group by name, case-insensitive + // If not found, mFoundNode will be NULL class FindByNameVisitor : public osg::NodeVisitor { public: @@ -19,20 +19,25 @@ namespace SceneUtil { } - virtual void apply(osg::Group& group) - { - if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) - { - mFoundNode = &group; - return; - } - traverse(group); - } + virtual void apply(osg::Group& group); std::string mNameToFind; osg::Group* mFoundNode; }; + // Disable freezeOnCull for all visited particlesystems + class DisableFreezeOnCullVisitor : public osg::NodeVisitor + { + public: + DisableFreezeOnCullVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + virtual void apply(osg::Geode &geode); + virtual void apply(osg::Drawable& drw); + }; + } #endif diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index e1a67aff81..9ecef04839 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -52,7 +53,7 @@ namespace if (!_gc) { - osg::notify(osg::NOTICE)<<"Failed to create pbuffer, failing back to normal graphics window."<pbuffer = false; _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); @@ -217,10 +218,18 @@ namespace SDLUtil void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) { + osg::ref_ptr decompressed; + if (mCursorMap.find(name) != mCursorMap.end()) return; - osg::ref_ptr decompressed = decompress(image, static_cast(rotDegrees)); + try { + decompressed = decompress(image, static_cast(rotDegrees)); + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + std::cerr <<"Using default cursor."< -#include #include namespace SDLUtil @@ -79,15 +78,13 @@ void GraphicsWindowSDL2::init() if(!_traits.valid()) return; - // getEventQueue()->setCurrentEventState(osgGA::GUIEventAdapter::getAccumulatedEventState().get()); - WindowData *inheritedWindowData = dynamic_cast(_traits->inheritedWindowData.get()); mWindow = inheritedWindowData ? inheritedWindowData->mWindow : NULL; mOwnsWindow = (mWindow == 0); if(mOwnsWindow) { - OSG_NOTICE<<"Error: No SDL window provided."<syncWindowRectangleWithGraphicsContext(); #else getEventQueue()->syncWindowRectangleWithGraphcisContext(); @@ -130,7 +133,7 @@ bool GraphicsWindowSDL2::realizeImplementation() SDL_ShowWindow(mWindow); -#if OSG_MIN_VERSION_REQUIRED(3,3,4) +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); #else getEventQueue()->syncWindowRectangleWithGraphcisContext(); @@ -145,7 +148,7 @@ bool GraphicsWindowSDL2::makeCurrentImplementation() { if(!mRealized) { - OSG_NOTICE<<"Warning: GraphicsWindow not realized, cannot do makeCurrent."< v mMouseInWindow(true) { _setupOISKeys(); + + Uint32 flags = SDL_GetWindowFlags(mSDLWindow); + mWindowHasFocus = (flags & SDL_WINDOW_INPUT_FOCUS); + mMouseInWindow = (flags & SDL_WINDOW_MOUSE_FOCUS); } InputWrapper::~InputWrapper() diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 90fd300ecf..0e5324bd97 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -58,6 +59,7 @@ CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); +typedef std::map< CategorySetting, bool > CategorySettingStatusMap; class SettingsFileParser { @@ -69,6 +71,7 @@ public: mFile = file; boost::filesystem::ifstream stream; stream.open(boost::filesystem::path(file)); + std::cout << "Loading settings file: " << file << std::endl; std::string currentCategory; mLine = 0; while (!stream.eof() && !stream.fail()) @@ -117,6 +120,215 @@ public: } } + void saveSettingsFile (const std::string& file, CategorySettingValueMap& settings) + { + // No options have been written to the file yet. + CategorySettingStatusMap written; + for (CategorySettingValueMap::iterator it = settings.begin(); it != settings.end(); ++it) { + written[it->first] = false; + } + + // Have we substantively changed the settings file? + bool changed = false; + + // Were there any lines at all in the file? + bool existing = false; + + // The category/section we're currently in. + std::string currentCategory; + + // Open the existing settings.cfg file to copy comments. This might not be the same file + // as the output file if we're copying the setting from the default settings.cfg for the + // first time. A minor change in API to pass the source file might be in order here. + boost::filesystem::ifstream istream; + boost::filesystem::path ipath(file); + istream.open(ipath); + + // Create a new string stream to write the current settings to. It's likely that the + // input file and the output file are the same, so this stream serves as a temporary file + // of sorts. The setting files aren't very large so there's no performance issue. + std::stringstream ostream; + + // For every line in the input file... + while (!istream.eof() && !istream.fail()) { + std::string line; + std::getline(istream, line); + + // The current character position in the line. + size_t i = 0; + + // Don't add additional newlines at the end of the file. + if (istream.eof()) continue; + + // Copy entirely blank lines. + if (!skipWhiteSpace(i, line)) { + ostream << line << std::endl; + continue; + } + + // There were at least some comments in the input file. + existing = true; + + // Copy comments. + if (line[i] == '#') { + ostream << line << std::endl; + continue; + } + + // Category heading. + if (line[i] == '[') { + size_t end = line.find(']', i); + // This should never happen unless the player edited the file while playing. + if (end == std::string::npos) { + ostream << "# unterminated category: " << line << std::endl; + changed = true; + continue; + } + + // Ensure that all options in the current category have been written. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + std::cout << "Added new setting: [" << currentCategory << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // Update the current category. + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + + // Write the (new) current category to the file. + ostream << "[" << currentCategory << "]" << std::endl; + //std::cout << "Wrote category: " << currentCategory << std::endl; + + // A setting can apparently follow the category on an input line. That's rather + // inconvenient, since it makes it more likely to have duplicative sections, + // which our algorithm doesn't like. Do the best we can. + i = end+1; + } + + // Truncate trailing whitespace, since we're changing the file anayway. + if (!skipWhiteSpace(i, line)) + continue; + + // If we've found settings before the first category, something's wrong. This + // should never happen unless the player edited the file while playing, since + // the loadSettingsFile() logic rejects it. + if (currentCategory.empty()) { + ostream << "# empty category name: " << line << std::endl; + changed = true; + continue; + } + + // Which setting was at this location in the input file? + size_t settingEnd = line.find('=', i); + // This should never happen unless the player edited the file while playing. + if (settingEnd == std::string::npos) { + ostream << "# unterminated setting name: " << line << std::endl; + changed = true; + continue; + } + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + // Get the existing value so we can see if we've changed it. + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + // Construct the setting map key to determine whether the setting has already been + // written to the file. + CategorySetting key = std::make_pair(currentCategory, setting); + CategorySettingStatusMap::iterator finder = written.find(key); + + // Settings not in the written map are definitely invalid. Currently, this can only + // happen if the player edited the file while playing, because loadSettingsFile() + // will accept anything and pass it along in the map, but in the future, we might + // want to handle invalid settings more gracefully here. + if (finder == written.end()) { + ostream << "# invalid setting: " << line << std::endl; + changed = true; + continue; + } + + // Write the current value of the setting to the file. The key must exist in the + // settings map because of how written was initialized and finder != end(). + ostream << setting << " = " << settings[key] << std::endl; + // Mark that setting as written. + finder->second = true; + // Did we really change it? + if (value != settings[key]) { + std::cout << "Changed setting: [" << currentCategory << "] " + << setting << " = " << settings[key] << std::endl; + changed = true; + } + // No need to write the current line, because we just emitted a replacement. + + // Curiously, it appears that comments at the ends of lines with settings are not + // allowed, and the comment becomes part of the value. Was that intended? + } + + // We're done with the input stream file. + istream.close(); + + // Ensure that all options in the current category have been written. We must complete + // the current category at the end of the file before moving on to any new categories. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + std::cout << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // If there was absolutely nothing in the file (or more likely the file didn't + // exist), start the newly created file with a helpful comment. + if (!existing) { + ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; + ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; + ostream << "# to its default, simply remove it from this file. For available" << std::endl; + ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; + ostream << "#" << std::endl; + ostream << "# https://wiki.openmw.org/index.php?title=Settings" << std::endl; + } + + // We still have one more thing to do before we're completely done writing the file. + // It's possible that there are entirely new categories, or that the input file had + // disappeared completely, so we need ensure that all settings are written to the file + // regardless of those issues. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + // If the setting hasn't been written yet. + if (mit->second == false) { + // If the catgory has changed, write a new category header. + if (mit->first.first != currentCategory) { + currentCategory = mit->first.first; + std::cout << "Created new setting section: " << mit->first.first << std::endl; + ostream << std::endl; + ostream << "[" << currentCategory << "]" << std::endl; + } + std::cout << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + // Then write the setting. No need to mark it as written because we're done. + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + changed = true; + } + } + + // Now install the newly written file in the requested place. + if (changed) { + std::cout << "Updating settings file: " << ipath << std::endl; + boost::filesystem::ofstream ofstream; + ofstream.open(ipath); + ofstream << ostream.rdbuf(); + ofstream.close(); + } + } + private: /// Increment i until it longer points to a whitespace character /// in the string or has reached the end of the string. @@ -141,6 +353,13 @@ private: int mLine; }; +void Manager::clear() +{ + mDefaultSettings.clear(); + mUserSettings.clear(); + mChangedSettings.clear(); +} + void Manager::loadDefault(const std::string &file) { SettingsFileParser parser; @@ -155,18 +374,8 @@ void Manager::loadUser(const std::string &file) void Manager::saveUser(const std::string &file) { - boost::filesystem::ofstream stream; - stream.open(boost::filesystem::path(file)); - std::string currentCategory; - for (CategorySettingValueMap::iterator it = mUserSettings.begin(); it != mUserSettings.end(); ++it) - { - if (it->first.first != currentCategory) - { - currentCategory = it->first.first; - stream << "\n[" << currentCategory << "]\n"; - } - stream << it->first.second << " = " << it->second << "\n"; - } + SettingsFileParser parser; + parser.saveSettingsFile(file, mUserSettings); } std::string Manager::getString(const std::string &setting, const std::string &category) diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index c16ff5a1ef..7adcb9b396 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -23,6 +23,9 @@ namespace Settings static CategorySettingVector mChangedSettings; ///< tracks all the settings that were changed since the last apply() call + void clear(); + ///< clears all settings and default settings + void loadDefault (const std::string& file); ///< load file as the default settings (can be overridden by user settings) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index df45b6ec2a..59d06f2542 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -11,7 +11,7 @@ namespace Terrain { FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, - const std::vector >& blendmaps) + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { bool firstLayer = true; int i=0; @@ -36,7 +36,7 @@ namespace Terrain // This is to map corner vertices directly to the center of a blendmap texel. osg::Matrixf texMat; - float scale = (16/(16.f+1.f)); + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); @@ -57,8 +57,7 @@ namespace Terrain stateset->setTextureAttributeAndModes(texunit, tex.get()); osg::ref_ptr texMat (new osg::TexMat); - float scale = 16.f; - texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(scale,scale,1.f))); + texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON); firstLayer = false; @@ -67,9 +66,12 @@ namespace Terrain } } - Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps) + Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, + int blendmapScale, float layerTileSize) : mLayers(layers) , mBlendmaps(blendmaps) + , mBlendmapScale(blendmapScale) + , mLayerTileSize(layerTileSize) { osg::ref_ptr material (new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); @@ -80,7 +82,7 @@ namespace Terrain bool Effect::define_techniques() { - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps)); + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); return true; } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index b423aa8b0c..dd00e41ed4 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -19,7 +19,7 @@ namespace Terrain public: FixedFunctionTechnique( const std::vector >& layers, - const std::vector >& blendmaps); + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); protected: virtual void define_passes() {} @@ -30,7 +30,7 @@ namespace Terrain public: Effect( const std::vector >& layers, - const std::vector >& blendmaps); + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); virtual bool define_techniques(); @@ -50,6 +50,8 @@ namespace Terrain private: std::vector > mLayers; std::vector > mBlendmaps; + int mBlendmapScale; + float mLayerTileSize; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 5afb991768..aa82e0bd65 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -6,11 +6,14 @@ #include #include +#include + +#include -#include #include #include #include +#include #include @@ -45,8 +48,10 @@ namespace Terrain TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) + , mNumSplits(4) , mKdTreeBuilder(new osg::KdTreeBuilder) { + mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } TerrainGrid::~TerrainGrid() @@ -60,108 +65,149 @@ TerrainGrid::~TerrainGrid() class GridElement { public: - osg::ref_ptr mNode; + osg::ref_ptr mNode; }; -void TerrainGrid::loadCell(int x, int y) +osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { - if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) - return; // already loaded - - osg::Vec2f center(x+0.5f, y+0.5f); - float minH, maxH; - if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) - return; // no terrain defined - - std::auto_ptr element (new GridElement); - - osg::Vec2f worldCenter = center*mStorage->getCellWorldSize(); - element->mNode = new osg::PositionAttitudeTransform; - element->mNode->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); - mTerrainRoot->addChild(element->mNode); - - osg::ref_ptr positions (new osg::Vec3Array); - osg::ref_ptr normals (new osg::Vec3Array); - osg::ref_ptr colors (new osg::Vec4Array); + if (chunkSize * mNumSplits > 1.f) + { + // keep splitting + osg::ref_ptr group (new osg::Group); + if (parent) + parent->addChild(group); + + float newChunkSize = chunkSize/2.f; + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, -newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, -newChunkSize/2.f)); + return group; + } + else + { + float minH, maxH; + if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH)) + return NULL; // no terrain defined + + osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); + osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); + transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); + + if (parent) + parent->addChild(transform); + + osg::ref_ptr positions (new osg::Vec3Array); + osg::ref_ptr normals (new osg::Vec3Array); + osg::ref_ptr colors (new osg::Vec4Array); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors); + + osg::ref_ptr geometry (new osg::Geometry); + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + geometry->setUseDisplayList(false); + geometry->setUseVertexBufferObjects(true); + + geometry->addPrimitiveSet(mCache.getIndexBuffer(0)); + + // we already know the bounding box, so no need to let OSG compute it. + osg::Vec3f min(-0.5f*mStorage->getCellWorldSize()*chunkSize, + -0.5f*mStorage->getCellWorldSize()*chunkSize, + minH); + osg::Vec3f max (0.5f*mStorage->getCellWorldSize()*chunkSize, + 0.5f*mStorage->getCellWorldSize()*chunkSize, + maxH); + osg::BoundingBox bounds(min, max); + geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds)); + + std::vector layerList; + std::vector > blendmaps; + mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList); + + // For compiling textures, I don't think the osgFX::Effect does it correctly + osg::ref_ptr textureCompileDummy (new osg::Node); + + std::vector > layerTextures; + for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) + { + layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + } - osg::ref_ptr vbo (new osg::VertexBufferObject); - positions->setVertexBufferObject(vbo); - normals->setVertexBufferObject(vbo); - colors->setVertexBufferObject(vbo); + std::vector > blendmapTextures; + for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + osg::ref_ptr texture (new osg::Texture2D); + texture->setImage(*it); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setResizeNonPowerOfTwoHint(false); + blendmapTextures.push_back(texture); + + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + } - mStorage->fillVertexBuffers(0, 1, center, positions, normals, colors); + // use texture coordinates for both texture units, the layer texture and blend texture + for (unsigned int i=0; i<2; ++i) + geometry->setTexCoordArray(i, mCache.getUVBuffer()); - osg::ref_ptr geometry (new osg::Geometry); - geometry->setVertexArray(positions); - geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); - geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); - geometry->setUseDisplayList(false); - geometry->setUseVertexBufferObjects(true); + float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; + osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); - geometry->addPrimitiveSet(mCache.getIndexBuffer(0)); + effect->addCullCallback(new SceneUtil::LightListCallback); - // we already know the bounding box, so no need to let OSG compute it. - osg::Vec3f min(-0.5f*mStorage->getCellWorldSize(), - -0.5f*mStorage->getCellWorldSize(), - minH); - osg::Vec3f max (0.5f*mStorage->getCellWorldSize(), - 0.5f*mStorage->getCellWorldSize(), - maxH); - osg::BoundingBox bounds(min, max); - geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds)); + transform->addChild(effect); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + osg::Node* toAttach = geometry.get(); +#else + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(geometry); + osg::Node* toAttach = geode.get(); +#endif - // build a kdtree to speed up intersection tests with the terrain - // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree - geode->accept(*mKdTreeBuilder); + effect->addChild(toAttach); - std::vector layerList; - std::vector > blendmaps; - mStorage->getBlendmaps(1.f, center, false, blendmaps, layerList); - - // For compiling textures, I don't think the osgFX::Effect does it correctly - osg::ref_ptr textureCompileDummy (new osg::Node); + if (mIncrementalCompileOperation) + { + mIncrementalCompileOperation->add(toAttach); + mIncrementalCompileOperation->add(textureCompileDummy); + } - std::vector > layerTextures; - for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) - { - layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); - } - - std::vector > blendmapTextures; - for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) - { - osg::ref_ptr texture (new osg::Texture2D); - texture->setImage(*it); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setResizeNonPowerOfTwoHint(false); - blendmapTextures.push_back(texture); - - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + return transform; } +} - // use texture coordinates for both texture units, the layer texture and blend texture - for (unsigned int i=0; i<2; ++i) - geometry->setTexCoordArray(i, mCache.getUVBuffer()); +void TerrainGrid::loadCell(int x, int y) +{ + if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) + return; // already loaded - osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures)); + osg::Vec2f center(x+0.5f, y+0.5f); - effect->addCullCallback(new SceneUtil::LightListCallback); + osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + if (!terrainNode) + return; // no terrain defined - effect->addChild(geode); - element->mNode->addChild(effect); + std::auto_ptr element (new GridElement); + element->mNode = terrainNode; + mTerrainRoot->addChild(element->mNode); - if (mIncrementalCompileOperation) - { - mIncrementalCompileOperation->add(geode); - mIncrementalCompileOperation->add(textureCompileDummy); - } + // kdtree probably not needed with mNumSplits=4 + /* + // build a kdtree to speed up intersection tests with the terrain + // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree + geode->accept(*mKdTreeBuilder); + */ mGrid[std::make_pair(x,y)] = element.release(); } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 832b952e89..169a9a6228 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,6 +1,8 @@ #ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H +#include + #include "world.hpp" #include "material.hpp" @@ -26,6 +28,11 @@ namespace Terrain virtual void unloadCell(int x, int y); private: + osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); + + // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks + unsigned int mNumSplits; + typedef std::map, GridElement*> Grid; Grid mGrid; diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index d7f9c3a382..8af0bc5ed8 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -85,10 +85,10 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) assert(input[size] == 0); // Note: The rest of this function is designed for single-character - // input encodings only. It also assumes that the input the input - // encoding shares its first 128 values (0-127) with ASCII. There are - // no plans to add more encodings to this module (we are using utf8 - // for new content files), so that shouldn't be an issue. + // input encodings only. It also assumes that the input encoding + // shares its first 128 values (0-127) with ASCII. There are no plans + // to add more encodings to this module (we are using utf8 for new + // content files), so that shouldn't be an issue. // Compute output length, and check for pure ascii input at the same // time. diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 6be6dca9e6..457947d40d 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -1,7 +1,9 @@ #include "manager.hpp" +#include #include -#include + +#include #include "archive.hpp" @@ -15,7 +17,7 @@ namespace char nonstrict_normalize_char(char ch) { - return ch == '\\' ? '/' : std::tolower(ch,std::locale::classic()); + return ch == '\\' ? '/' : Misc::StringUtils::toLower(ch); } void normalize_path(std::string& path, bool strict) diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index 1cd8829751..8e3f8ed690 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -42,18 +42,31 @@ namespace Gui ImageBox::onMouseButtonPressed(_left, _top, _id); } - MyGUI::IntSize ImageButton::getRequestedSize(bool logError) + MyGUI::IntSize ImageButton::getRequestedSize() { MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(mImageNormal); if (!texture) { - if (logError) - std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; + std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; return MyGUI::IntSize(0,0); } return MyGUI::IntSize (texture->getWidth(), texture->getHeight()); } + void ImageButton::setImage(const std::string &image) + { + size_t extpos = image.find_last_of("."); + std::string imageNoExt = image.substr(0, extpos); + + std::string ext = image.substr(extpos); + + mImageNormal = imageNoExt + "_idle" + ext; + mImageHighlighted = imageNoExt + "_over" + ext; + mImagePushed = imageNoExt + "_pressed" + ext; + + setImageTexture(mImageNormal); + } + void ImageButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) { if (_id == MyGUI::MouseButton::Left) diff --git a/components/widgets/imagebutton.hpp b/components/widgets/imagebutton.hpp index 10150c6b1e..a539f15c97 100644 --- a/components/widgets/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -14,7 +14,10 @@ namespace Gui MYGUI_RTTI_DERIVED(ImageButton) public: - MyGUI::IntSize getRequestedSize(bool logError = true); + MyGUI::IntSize getRequestedSize(); + + /// Set mImageNormal, mImageHighlighted and mImagePushed based on file convention (image_idle.ext, image_over.ext and image_pressed.ext) + void setImage(const std::string& image); protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 614a0804cf..6009f69de5 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -6,7 +6,6 @@ set(OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES videoplayer.cpp videostate.cpp videodefs.hpp - libavwrapper.cpp audiodecoder.cpp audiofactory.hpp ) diff --git a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp index 77e6b4b6c0..f095d1617a 100644 --- a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp @@ -6,24 +6,12 @@ extern "C" #include - #ifdef HAVE_LIBSWRESAMPLE #include - #else - // FIXME: remove this section once libswresample is packaged for Debian - #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 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) #define av_frame_alloc avcodec_alloc_frame #endif - } #include "videostate.hpp" diff --git a/extern/osg-ffmpeg-videoplayer/libavwrapper.cpp b/extern/osg-ffmpeg-videoplayer/libavwrapper.cpp deleted file mode 100644 index 26a7b63706..0000000000 --- a/extern/osg-ffmpeg-videoplayer/libavwrapper.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// This file is a wrapper around the libavresample library (the API-incompatible swresample replacement in the libav fork of ffmpeg), to make it look like swresample to the user. - -#ifndef HAVE_LIBSWRESAMPLE -extern "C" -{ -#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 packaged for Debian */ - -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/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index f143088e81..3c9279ea25 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -85,21 +85,14 @@ void VideoState::setAudioFactory(MovieAudioFactory *factory) void PacketQueue::put(AVPacket *pkt) { AVPacketList *pkt1; + if(pkt != &flush_pkt && !pkt->buf && av_dup_packet(pkt) < 0) + throw std::runtime_error("Failed to duplicate packet"); + pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList)); if(!pkt1) throw std::bad_alloc(); pkt1->pkt = *pkt; pkt1->next = NULL; - if(pkt->data != flush_pkt.data && pkt1->pkt.destruct == NULL) - { - if(av_dup_packet(&pkt1->pkt) < 0) - { - av_free(pkt1); - throw std::runtime_error("Failed to duplicate packet"); - } - av_free_packet(pkt); - } - this->mutex.lock (); if(!last_pkt) @@ -313,7 +306,7 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) int w = (*this->video_st)->codec->width; int h = (*this->video_st)->codec->height; this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt, - w, h, PIX_FMT_RGBA, SWS_BICUBIC, + w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, NULL, NULL); if(this->sws_context == NULL) throw std::runtime_error("Cannot initialize the conversion context!\n"); @@ -354,24 +347,28 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) return pts; } - +static void our_free_buffer(void *opaque, uint8_t *data); /* These are called whenever we allocate a frame * buffer. We use this to store the global_pts in * a frame at the time it is allocated. */ static int64_t global_video_pkt_pts = AV_NOPTS_VALUE; -static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) +static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic, int flags) { - int ret = avcodec_default_get_buffer(c, pic); + AVBufferRef *ref; + int ret = avcodec_default_get_buffer2(c, pic, flags); int64_t *pts = (int64_t*)av_malloc(sizeof(int64_t)); *pts = global_video_pkt_pts; pic->opaque = pts; + ref = av_buffer_create((uint8_t *)pic->opaque, sizeof(int64_t), our_free_buffer, pic->buf[0], flags); + pic->buf[0] = ref; return ret; } -static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) +static void our_free_buffer(void *opaque, uint8_t *data) { - if(pic) av_freep(&pic->opaque); - avcodec_default_release_buffer(c, pic); + AVBufferRef *ref = (AVBufferRef *)opaque; + av_buffer_unref(&ref); + av_free(data); } @@ -384,7 +381,7 @@ void VideoState::video_thread_loop(VideoState *self) pFrame = av_frame_alloc(); self->rgbaFrame = av_frame_alloc(); - avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); + avpicture_alloc((AVPicture*)self->rgbaFrame, AV_PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); while(self->videoq.get(packet, self) >= 0) { @@ -589,8 +586,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) case AVMEDIA_TYPE_VIDEO: this->video_st = pFormatCtx->streams + stream_index; - codecCtx->get_buffer = our_get_buffer; - codecCtx->release_buffer = our_release_buffer; + codecCtx->get_buffer2 = our_get_buffer; this->video_thread = boost::thread(video_thread_loop, this); break; diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 00cae86d26..75cb6a9b0d 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(mygui) +add_subdirectory(shaders) diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index dc9e8ea848..7494d3ba2d 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -80,6 +80,7 @@ set(MYGUI_FILES openmw_savegame_dialog.layout openmw_recharge_dialog.layout openmw_screen_fader.layout + openmw_screen_fader_hit.layout openmw_edit_note.layout openmw_debug_window.layout openmw_debug_window.skin.xml diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index 2336d5b2ca..7c158af8dc 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 97b0184696..03c05260f3 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -125,11 +125,5 @@ - - - - - - diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index 5524f55202..9b530b3796 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -2,7 +2,7 @@ - + @@ -26,9 +26,7 @@ - - - + @@ -66,15 +64,11 @@ - - - + - - - + @@ -90,9 +84,7 @@ - - - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index cf577aec52..cd8a9f7608 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -1,10 +1,14 @@ - + + - + + + + @@ -12,6 +16,6 @@ - + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 305cb0c0da..ab68993417 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -101,7 +101,7 @@ - + diff --git a/files/mygui/openmw_screen_fader.layout b/files/mygui/openmw_screen_fader.layout index 13234792f7..e9009d32a6 100644 --- a/files/mygui/openmw_screen_fader.layout +++ b/files/mygui/openmw_screen_fader.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_screen_fader_hit.layout b/files/mygui/openmw_screen_fader_hit.layout new file mode 100644 index 0000000000..7e60f0ff5a --- /dev/null +++ b/files/mygui/openmw_screen_fader_hit.layout @@ -0,0 +1,7 @@ + + + + + + + diff --git a/files/mygui/openmw_scroll.layout b/files/mygui/openmw_scroll.layout index 194700f36e..b2e62f28d8 100644 --- a/files/mygui/openmw_scroll.layout +++ b/files/mygui/openmw_scroll.layout @@ -2,24 +2,24 @@ - + - + - + - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 19e3bcf886..6d2424aa5b 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -255,13 +255,9 @@ - - - - - - + + @@ -271,6 +267,12 @@ + + + + + + @@ -278,7 +280,7 @@ - + @@ -365,53 +367,38 @@ - + - + - + - - + + - - - - - - - - - - - - - - + + + + - - + + - - - - - - - - - - + + + + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index e459f22fab..b7a893580f 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -18,15 +18,6 @@ color_misc=0,205,205 # ???? - - - - - - - - - diff --git a/files/opencs.ini b/files/openmw-cs.cfg similarity index 100% rename from files/opencs.ini rename to files/openmw-cs.cfg diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 274a31315c..49c9c54191 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,252 +1,368 @@ -# WARNING: Editing this file might have no effect, as these -# settings are overwritten by your user settings file. +# WARNING: If this file is named settings-default.cfg, then editing +# this file might have no effect, as these settings may be overwritten +# by your user settings.cfg file (see documentation for its location). +# +# This file provides minimal documentation for each setting, and +# ranges of recommended values. For detailed explanations of the +# significance of each setting, interaction with other settings, hard +# limits on value ranges and more information in general, please read +# the detailed documentation at the OpenMW Wiki page: +# +# https://wiki.openmw.org/index.php?title=Settings +# -[Video] -resolution x = 800 -resolution y = 600 +[Camera] -fullscreen = false -window border = true -screen = 0 +# Near clipping plane (>0.0, e.g. 0.01 to 18.0). +near clip = 5.0 -# Minimize the window if it loses key focus? -minimize on focus loss = true +# Cull objects smaller than one pixel. +small feature culling = true -# Valid values: 0 for no antialiasing, or any power of two -antialiasing = 0 +# Maximum visible distance (e.g. 2000.0 to 6666.0). Caution: this setting +# can dramatically affect performance, see documentation for details. +viewing distance = 6666.0 -vsync = false +# Camera field of view in degrees (e.g. 30.0 to 110.0). +# Does not affect the player's hands in the first person camera. +field of view = 55.0 + +# Field of view for first person meshes (i.e. the player's hands) +# Best to leave this at the default since vanilla assets are not complete enough to adapt to high FoV's. Too low FoV would clip the hands off screen. +first person field of view = 55.0 + +[Cells] -gamma = 1.00 -contrast = 1.00 +# Adjacent exterior cells loaded (>0). Caution: this setting can +# dramatically affect performance, see documentation for details. +exterior cell load distance = 1 + +[Map] + +# Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). +# Warning: affects explored areas in save files, see documentation. +global map cell size = 18 + +# Zoom level in pixels for HUD map widget. 64 is one cell, 128 is 1/4 +# cell, 256 is 1/8 cell. See documentation for details. (e.g. 64 to 256). +local map hud widget size = 256 -# Maximum framerate in frames per second, 0 = unlimited -framerate limit = 0 +# Resolution of local map in GUI window in pixels. See documentation +# for details which may affect cell load performance. (e.g. 128 to 1024). +local map resolution = 256 + +# Size of local map in GUI window in pixels. (e.g. 256 to 1024). +local map widget size = 512 [GUI] + +# Scales GUI window and widget size. (<1.0 is smaller, >1.0 is larger). scaling factor = 1.0 -# 1 is fully opaque +# Transparency of GUI windows (0.0 to 1.0, transparent to opaque). menu transparency = 0.84 -# 0 - instantly, 1 - max. delay -tooltip delay = 0 +# Time until tool tip appears when hovering over an object (0.0 is +# instantly, 1.0 is the maximum delay of about 1.5 seconds). +tooltip delay = 0.0 +# Stretch menus, load screens, etc. to the window aspect ratio. +stretch menu background = false + +# Subtitles for NPC spoken dialog and some sound effects. subtitles = false +# Red flash visually showing player damage. hit fader = true + +# Werewolf overlay border around screen or window. werewolf overlay = true -stretch menu background = false +# Color for tool tips and crosshair when owned by an NPC (R G B A). +color background owned = 0.15 0.0 0.0 1.0 +color crosshair owned = 1.0 0.15 0.15 1.0 -# colour definitions (red green blue alpha) -color background owned = 0.15 0 0 1 -color crosshair owned = 1 0.15 0.15 1 +[HUD] -[General] -# Camera field of view -field of view = 55 +# Displays the crosshair or reticle when not in GUI mode. +crosshair = true + +[Game] + +# Color crosshair and tool tip when object is owned by an NPC. (O is +# no color, 1 is tool tip only, 2 is crosshair only, and 3 is both). +show owned = 0 + +# Always use the best mode of attack: e.g. chop, slash or thrust. +best attack = false -# Texture filtering mode. valid values: -# bilinear -# trilinear -texture filtering = +# Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100). +difficulty = 0 +# Show duration of magic effect and lights in the spells window. +show effect duration = false + +[General] + +# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). anisotropy = 4 +# File format for screenshots. (jpg, png, tga, and possibly more). screenshot format = png -[Shadows] -# Shadows are only supported when object shaders are on! -enabled = false +# Texture magnification filter type. (nearest or linear). +texture mag filter = linear -# Split the shadow maps, allows for a larger shadow distance -split = false +# Texture minification filter type. (nearest or linear). +texture min filter = linear -# Increasing shadow distance will lower the shadow quality. -# Uses "shadow distance" or "split shadow distance" depending on "split" setting. -shadow distance = 1300 -# This one shouldn't be too low, otherwise you'll see artifacts. Use at least 2x max viewing distance. -split shadow distance = 14000 +# Texture mipmap type. (none, nearest, or linear). +texture mipmap = nearest -# Size of the shadow textures, higher means higher quality -texture size = 1024 +[Input] -# Turn on/off various shadow casters -actor shadows = true -misc shadows = true -statics shadows = true -terrain shadows = true +# Capture control of the cursor prevent movement outside the window. +grab cursor = true -# Fraction of the total shadow distance after which the shadow starts to fade out -fade start = 0.8 +# Key controlling sneak toggles setting instead of being held down. +toggle sneak = false -debug = false +# Player is running by default. +always run = false -[HUD] -# FPS counter -# 0: not visible -# 1: FPS display -fps = 0 +# Zoom in and out from player in third person view with mouse wheel. +allow third person zoom = false -crosshair = true +# Camera sensitivity when not in GUI mode. (>0.0, e.g. 0.1 to 5.0). +camera sensitivity = 1.0 -[Objects] -shaders = true +# Vertical camera sensitivity multiplier when not in GUI mode. +# (>0.0, Because it's a multiplier values should be near 1.0) +camera y multiplier = 1.0 -[Map] -# Adjusts the scale of the global map -global map cell size = 18 +# Invert the vertical axis while not in GUI mode. +invert y axis = false -local map resolution = 256 +[Saves] -local map widget size = 512 -local map hud widget size = 256 +# Name of last character played, and default for loading save files. +character = -[Cells] -exterior cell load distance = 1 +# Automatically save the game whenever the player rests. +autosave = true -[Camera] -near clip = 5 +# Display the time played on each save file in the load menu. +timeplayed = false -# The maximum distance with no pop-in will be: (see RenderingManager::configureFog) -# viewing distance * view frustum factor <= cell size (8192) - loading threshold (1024) -# view frustum factor takes into account that the view frustum end is a plane, so at the edges of the screen you can see further than you should be able to. -# exact factor would depend on FOV -viewing distance = 6666 +[Sound] -# Culling of objects smaller than a pixel -small feature culling = true +# Name of audio device file. Blank means use the default device. +device = -[Terrain] -distant land = false +# Volumes are 0.0 for silent and 1.0 for the maximum volume. -shader = true +# Master volume. Controls all other volumes. +master volume = 1.0 + +# Footsteps volume. +footsteps volume = 0.2 + +# Music tracks volume. +music volume = 0.5 + +# Sound effects volume. +sfx volume = 1.0 + +# Voice dialog volume. +voice volume = 0.8 + +# Minimum size to use for the sound buffer cache, in MB. When the cache is +# filled, old buffers will be unloaded until it's using no more than this much +# memory. Must be less than or equal to 'buffer cache max'. +buffer cache min = 14 + +# Maximum size to use for the sound buffer cache, in MB. The cache can use up +# to this much memory until old buffers get purged. +buffer cache max = 16 + +# Specifies whether to enable HRTF processing. Valid values are: -1 = auto, +# 0 = off, 1 = on. +hrtf enable = -1 + +# Specifies which HRTF to use when HRTF is used. Blank means use the default. +hrtf = + +[Video] + +# Resolution of the OpenMW window or screen. +resolution x = 800 +resolution y = 600 + +# OpenMW takes complete control of the screen. +fullscreen = false + +# Determines which screen OpenMW is on. (>=0). +screen = 0 + +# Minimize OpenMW if it loses cursor or keyboard focus. +minimize on focus loss = true + +# An operating system border is drawn around the OpenMW window. +window border = true + +# Anti-aliasing reduces jagged polygon edges. (0, 2, 4, 8, 16). +antialiasing = 0 + +# Enable vertical syncing to reduce tearing defects. +vsync = false + +# Maximum frames per second. 0.0 is unlimited, or >0.0 to limit. +framerate limit = 0.0 + +# Game video contrast. (>0.0). No effect in Linux. +contrast = 1.0 + +# Video gamma setting. (>0.0). No effect in Linux. +gamma = 1.0 [Water] + +# Enable water shader with reflections and optionally refraction. shader = false +# Reflection and refraction texture size in pixels. (512, 1024, 2048). +rtt size = 512 + +# Enable refraction which affects visibility through water plane. refraction = false -rtt size = 512 -reflect terrain = true -reflect statics = false -reflect actors = false +[Objects] -[Sound] -# Device name. Blank means default -device = +# Enable shaders for objects other than water. Unused. +shaders = true -# Volumes. master volume affects all other volumes. -master volume = 1.0 -sfx volume = 1.0 -music volume = 0.5 -footsteps volume = 0.2 -voice volume = 0.8 +[Terrain] +# Use shaders for terrain? Unused. +shader = true -[Input] +# Distant land is rendered? Unused. +distant land = false -grab cursor = true +[Shadows] -invert y axis = false +# Enable shadows. Other shadow settings disabled if false. Unused. +enabled = false -camera sensitivity = 1.0 +# Size of the shadow textures in pixels. Unused. (e.g. 256 to 2048). +texture size = 1024 -ui sensitivity = 1.0 +# Actors cast shadows. Unused. +actor shadows = true -camera y multiplier = 1.0 +# Static objects cast shadows. Unused. +statics shadows = true -always run = false +# Terrain cast shadows. Unused. +terrain shadows = true -allow third person zoom = false +# Miscellaneous objects cast shadows. Unused. +misc shadows = true -toggle sneak = false +# Debugging of shadows. Unused. +debug = false -[Game] -# Always use the most powerful attack when striking with a weapon (chop, slash or thrust) -best attack = false +# Fraction of distance after which shadow starts to fade out. Unused. +fade start = 0.8 -difficulty = 0 +# Split shadow maps, allowing for a larger shadow distance. Unused. +split = false -# Change crosshair/toolTip color when pointing on owned object -#0: nothing changed -#1: tint toolTip -#2: tint crosshair -#3: both -show owned = 0 -# Show the remaining duration of magic effects and lights -show effect duration = false +# Distance for shadows if not split. Smaller is poorer. Unused. +shadow distance = 1300 -[Saves] -character = -# Save when resting -autosave = true -# display time played -timeplayed = false +# Distance for shadows if split. Unused. +split shadow distance = 14000 [Windows] -inventory x = 0 + +# Location and sizes of windows as a fraction of the OpenMW window or +# screen size. (0.0 to 1.0). X & Y, Width & Height. + +# Stats window displaying level, race, class, skills and stats. +stats x = 0.0 +stats y = 0.0 +stats w = 0.375 +stats h = 0.4275 + +# Spells window displaying powers, spells, and magical items. +spells x = 0.625 +spells y = 0.5725 +spells w = 0.375 +spells h = 0.4275 + +# Local and world map window. +map x = 0.625 +map y = 0.0 +map w = 0.375 +map h = 0.5725 + +# Dialog window for talking with NPCs. +dialogue x = 0.095 +dialogue y = 0.095 +dialogue w = 0.810 +dialogue h = 0.810 + +# Alchemy window for crafting potions. +alchemy x = 0.25 +alchemy y = 0.25 +alchemy w = 0.5 +alchemy h = 0.5 + +# Console command window for debugging commands. +console x = 0.0 +console y = 0.0 +console w = 1.0 +console h = 0.5 + +# Player inventory window when explicitly opened. +inventory x = 0.0 inventory y = 0.4275 inventory w = 0.6225 inventory h = 0.5725 -inventory container x = 0 +# Player inventory window when searching a container. +inventory container x = 0.0 inventory container y = 0.4275 inventory container w = 0.6225 inventory container h = 0.5725 -inventory barter x = 0 +# Player inventory window when bartering with a shopkeeper. +inventory barter x = 0.0 inventory barter y = 0.4275 inventory barter w = 0.6225 inventory barter h = 0.5725 -inventory companion x = 0 +# Player inventory window when trading with a companion. +inventory companion x = 0.0 inventory companion y = 0.4275 inventory companion w = 0.6225 inventory companion h = 0.5725 +# Container inventory when searching a container. container x = 0.25 -container y = 0 +container y = 0.0 container w = 0.75 container h = 0.375 -companion x = 0.25 -companion y = 0 -companion w = 0.75 -companion h = 0.375 - -map x = 0.625 -map y = 0 -map w = 0.375 -map h = 0.5725 - +# NPC inventory window when bartering with a shopkeeper. barter x = 0.25 -barter y = 0 +barter y = 0.0 barter w = 0.75 barter h = 0.375 -alchemy x = 0.25 -alchemy y = 0.25 -alchemy w = 0.5 -alchemy h = 0.5 - -stats x = 0 -stats y = 0 -stats w = 0.375 -stats h = 0.4275 - -spells x = 0.625 -spells y = 0.5725 -spells w = 0.375 -spells h = 0.4275 - -console x = 0 -console y = 0 -console w = 1 -console h = 0.5 - -dialogue h = 0.810 -dialogue w = 0.810 -dialogue x = 0.095 -dialogue y = 0.095 +# NPC inventory window when trading with a companion. +companion x = 0.25 +companion y = 0.0 +companion w = 0.75 +companion h = 0.375 diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt new file mode 100644 index 0000000000..fc4706c1f6 --- /dev/null +++ b/files/shaders/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copy resource files into the build directory +set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(DDIR ${OpenMW_BINARY_DIR}/resources/shaders) + +set(SHADER_FILES + water_vertex.glsl + water_fragment.glsl + water_nm.png +) + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl new file mode 100644 index 0000000000..c04233fcf0 --- /dev/null +++ b/files/shaders/water_fragment.glsl @@ -0,0 +1,191 @@ +#version 120 + +#define REFRACTION @refraction_enabled + +// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) + +// tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +const float VISIBILITY = 1200.0; // how far you can look through water + +const float BIG_WAVES_X = 0.1; // strength of big waves +const float BIG_WAVES_Y = 0.1; + +const float MID_WAVES_X = 0.1; // strength of middle sized waves +const float MID_WAVES_Y = 0.1; + +const float SMALL_WAVES_X = 0.1; // strength of small waves +const float SMALL_WAVES_Y = 0.1; + +const float WAVE_CHOPPYNESS = 0.05; // wave choppyness +const float WAVE_SCALE = 75.0; // overall wave scale + +const float BUMP = 0.5; // overall water surface bumpiness +const float REFL_BUMP = 0.10; // reflection distortion amount +const float REFR_BUMP = 0.07; // refraction distortion amount + +const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering +const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering + +const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction + +const float SPEC_HARDNESS = 256.0; // specular highlights hardness + +const vec2 WIND_DIR = vec2(0.5f, -0.8f); +const float WIND_SPEED = 0.2f; + +const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); + +// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta) +{ + float c = abs(dot(Incoming, Normal)); + float g = eta * eta - 1.0 + c * c; + float result; + + if(g > 0.0) { + g = sqrt(g); + float A =(g - c)/(g + c); + float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0); + result = 0.5 * A * A *(1.0 + B * B); + } + else + result = 1.0; /* TIR (no refracted component) */ + + return result; +} + +varying vec3 screenCoordsPassthrough; +varying vec4 position; +varying float depthPassthrough; + +uniform sampler2D normalMap; + +uniform sampler2D reflectionMap; +#if REFRACTION +uniform sampler2D refractionMap; +uniform sampler2D refractionDepthMap; +#endif + +uniform float osg_SimulationTime; + +uniform float near; +uniform float far; +uniform vec3 nodePosition; + +void main(void) +{ + vec3 worldPos = position.xyz + nodePosition.xyz; + vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; + UV.y *= -1.0; + + float shadow = 1.0; + + vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; + screenCoords.y = (1.0-screenCoords.y); + + vec2 nCoord = vec2(0.0,0.0); + + #define waterTimer osg_SimulationTime + + nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); + vec3 normal0 = 2.0 * texture2D(normalMap, nCoord + vec2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS; + vec3 normal1 = 2.0 * texture2D(normalMap, nCoord + vec2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS; + vec3 normal2 = 2.0 * texture2D(normalMap, nCoord + vec2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS; + vec3 normal3 = 2.0 * texture2D(normalMap, nCoord + vec2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS; + vec3 normal4 = 2.0 * texture2D(normalMap, nCoord + vec2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS; + vec3 normal5 = 2.0 * texture2D(normalMap, nCoord + vec2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0; + + + + vec3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); + + normal = normalize(vec3(normal.x * BUMP, normal.y * BUMP, normal.z)); + + normal = vec3(-normal.x, -normal.y, normal.z); + + // normal for sunlight scattering + vec3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + + normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xyz; + lNormal = normalize(vec3(lNormal.x * BUMP, lNormal.y * BUMP, lNormal.z)); + lNormal = vec3(-lNormal.x, -lNormal.y, lNormal.z); + + + vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(gl_LightSource[0].position.xyz, 0.0)).xyz); + + vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; + vec3 vVec = normalize(position.xyz - cameraPos.xyz); + + float isUnderwater = (cameraPos.z > 0.0) ? 0.0 : 1.0; + + // sunlight scattering + vec3 pNormal = vec3(0,0,1); + vec3 lR = reflect(lVec, lNormal); + vec3 llR = reflect(lVec, pNormal); + + float sunHeight = lVec.z; + float sunFade = length(gl_LightModel.ambient.xyz); + + float s = clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0); + float lightScatter = shadow * clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * s * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + vec3 scatterColour = mix(vec3(SCATTER_COLOUR)*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); + + // fresnel + float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); //air to water; water to air + float fresnel = fresnel_dielectric(vVec, normal, ior); + + fresnel = clamp(fresnel, 0.0, 1.0); + + // reflection + vec3 reflection = texture2D(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb; + + // refraction +#if REFRACTION + vec3 refraction = texture2D(refractionMap, screenCoords-(normal.xy*REFR_BUMP)).rgb; + + // brighten up the refraction underwater + refraction = (cameraPos.z < 0.0) ? clamp(refraction * 1.5, 0.0, 1.0) : refraction; +#endif + + // specular + vec3 R = reflect(vVec, normal); + float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS) * shadow; + + vec3 waterColor = WATER_COLOR; + waterColor = waterColor * length(gl_LightModel.ambient.xyz); +#if REFRACTION + float refractionDepth = texture2D(refractionDepthMap, screenCoords-(normal.xy*REFR_BUMP)).x; + float z_n = 2.0 * refractionDepth - 1.0; + refractionDepth = 2.0 * near * far / (far + near - z_n * (far - near)); + + float waterDepth = refractionDepth - depthPassthrough; + + if (cameraPos.z > 0.0) + refraction = mix(refraction, waterColor, clamp(waterDepth/VISIBILITY, 0.0, 1.0)); + + gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * gl_LightSource[0].specular.xyz; +#else + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * gl_LightSource[0].specular.xyz; +#endif + + // fog + float fogValue = clamp((depthPassthrough - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + +#if REFRACTION + gl_FragData[0].w = 1.0; +#else + gl_FragData[0].w = clamp(fresnel*2.0 + specular, 0.0, 1.0); +#endif +} diff --git a/files/shaders/water_nm.png b/files/shaders/water_nm.png new file mode 100644 index 0000000000..361431a0ef Binary files /dev/null and b/files/shaders/water_nm.png differ diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl new file mode 100644 index 0000000000..7d7b7b18aa --- /dev/null +++ b/files/shaders/water_vertex.glsl @@ -0,0 +1,22 @@ +#version 120 + +varying vec3 screenCoordsPassthrough; +varying vec4 position; +varying float depthPassthrough; + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + + vec4 texcoordProj = ((scalemat) * ( gl_Position)); + screenCoordsPassthrough = vec3(texcoordProj.x, texcoordProj.y, texcoordProj.w); + + position = gl_Vertex; + + depthPassthrough = gl_Position.z; +}