diff --git a/.gitignore b/.gitignore index e37fc7721..1d6b337a4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ Doxygen .settings .directory .idea +cmake-build-* files/windows/*.aps cmake-build-* ## qt-creator diff --git a/.travis.yml b/.travis.yml index 52793ed65..a833f721a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ addons: - llvm-toolchain-precise-3.6 packages: [ # Dev - clang-3.6, libunshield-dev, libtinyxml-dev, + cmake, clang-3.6, libunshield-dev, libtinyxml-dev, g++-6, # Tests libgtest-dev, google-mock, @@ -43,7 +43,7 @@ addons: name: "TES3MP/openmw-tes3mp" description: "" notification_email: stas5978@gmail.com - build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE" + build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE" build_command: "make -j3" branch_pattern: coverity_scan matrix: diff --git a/AUTHORS.md b/AUTHORS.md index bb773c4ef..155d017f3 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -34,11 +34,13 @@ Programmers Ben Shealy (bentsherman) Bret Curtis (psi29a) Britt Mathis (galdor557) + Capostrophic cc9cii Chris Boyce (slothlife) Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) + crussell187 darkf devnexen Dieho @@ -86,6 +88,7 @@ Programmers lazydev Leon Krieg (lkrieg) Leon Saunders (emoose) + Łukasz Gołębiewski (lukago) logzero lohikaarme Lukasz Gromanowski (lgro) @@ -104,6 +107,7 @@ Programmers Michael Papageorgiou (werdanith) Michał Bień (Glorf) Michał Moroz (dragonee) + Miloslav Číž (drummyfish) Miroslav Puda (pakanek) MiroslavR Mitchell Schwitzer (schwitzerm) @@ -125,6 +129,7 @@ Programmers Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) rdimesio + rexelion riothamus Rob Cutmore (rcutmore) Robert MacGregor (Ragora) @@ -148,6 +153,7 @@ Programmers Sylvain Thesnieres (Garvek) t6 terrorfisch + thegriglat Thomas Luppi (Digmaster) Will Herrmann (Thunderforge) Tom Mason (wheybags) diff --git a/CHANGELOG.md b/CHANGELOG.md index b390169c6..ac2a5472e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,140 @@ +0.43.0 +------ + + Bug #815: Different settings cause inconsistent underwater visibility + Bug #1452: autosave is not executed when waiting + Bug #1555: Closing containers with spacebar doesn't work after touching an item + Bug #1692: Can't close container when item is "held" + Bug #2405: Maximum distance for guards attacking hostile creatures is incorrect + Bug #2445: Spellcasting can be interrupted + Bug #2489: Keeping map open not persisted between saves + Bug #2594: 1st person view uses wrong body texture with Better bodies + Bug #2628: enablestatreviewmenu command doen't read race, class and sign values from current game + Bug #2639: Attacking flag isn't reset upon reloading + Bug #2698: Snow and rain VFX move with the player + Bug #2704: Some creature swim animations not being used + Bug #2789: Potential risk of misunderstanding using the colored "owned" crosshair feature + Bug #3045: Settings containing '#' cannot be loaded + Bug #3097: Drop() doesn't work when an item is held (with the mouse) + Bug #3110: GetDetected doesn't work without a reference + Bug #3126: Framerate nosedives when adjusting dialogue window size + Bug #3243: Ampersand in configuration files isn't escaped automatically + Bug #3365: Wrong water reflection along banks + Bug #3441: Golden saint always dispelling soul trap / spell priority issue + Bug #3528: Disposing of corpses breaks quests + Bug #3531: No FPS limit when playing bink videos even though "framerate limit" is set in settings.cfg + Bug #3647: Multi-effect spells play audio louder than in Vanilla + Bug #3656: NPCs forget where their place in the world is + Bug #3665: Music transitions are too abrupt + Bug #3679: Spell cast effect should disappear after using rest command + Bug #3684: Merchants do not restock empty soul gems if they acquire filled ones. + Bug #3694: Wrong magicka bonus applied on character creation + Bug #3706: Guards don't try to arrest the player if attacked + Bug #3709: Editor: Camera is not positioned correctly on mode switches related to orbital mode + Bug #3720: Death counter not cleaned of non-existing IDs when loading a game + Bug #3744: "Greater/lesser or equal" operators are not parsed when their signs are swapped + Bug #3749: Yagrum Bagarn moves to different position on encountering + Bug #3766: DisableLevitation does not remove visuals of preexisting effect + Bug #3787: Script commands in result box for voiced dialogue are ignored + Bug #3793: OpenMW tries to animate animated references even when they are disabled + Bug #3794: Default sound buffer size is too small for mods + Bug #3796: Mod 'Undress for me' doesn't work: NPCs re-equip everything + Bug #3798: tgm command behaviour differs from vanilla + Bug #3804: [Mod] Animated Morrowind: some animations do not loop correctly + Bug #3805: Slight enchant miscalculation + Bug #3826: Rendering problems with an image in a letter + Bug #3833: [Mod] Windows Glow: windows textures are much darker than in original game + Bug #3835: Bodyparts with multiple NiTriShapes are not handled correctly + Bug #3839: InventoryStore::purgeEffect() removes only first effect with argument ID + Bug #3843: Wrong jumping fatigue loss calculations + Bug #3850: Boethiah's voice is distorted underwater + Bug #3851: NPCs and player say things while underwater + Bug #3864: Crash when exiting to Khartag point from Ilunibi + Bug #3878: Swapping soul gems while enchanting allows constant effect enchantments using any soul gem + Bug #3879: Dialogue option: Go to jail, persists beyond quickload + Bug #3891: Journal displays empty entries + Bug #3892: Empty space before dialogue entry display + Bug #3898: (mod) PositionCell in dialogue results closes dialogue window + Bug #3906: "Could not find Data Files location" dialog can appear multiple times + Bug #3908: [Wizard] User gets stuck if they cancel out of installing from a CD + Bug #3909: Morrowind Content Language dropdown is the only element on the right half of the Settings window + Bug #3910: Launcher window can be resized so that it cuts off the scroll + Bug #3915: NC text key on nifs doesn't work + Bug #3919: Closing inventory while cursor hovers over spell (or other magic menu item) produces left click sound + Bug #3922: Combat AI should avoid enemy hits when casts Self-ranged spells + Bug #3934: [macOS] Copy/Paste from system clipboard uses Control key instead of Command key + Bug #3935: Incorrect attack strength for AI actors + Bug #3937: Combat AI: enchanted weapons have too high rating + Bug #3942: UI sounds are distorted underwater + Bug #3943: CPU/GPU usage should stop when the game is minimised + Bug #3944: Attempting to sell stolen items back to their owner does not remove them from your inventory + Bug #3955: Player's avatar rendering issues + Bug #3956: EditEffectDialog: Cancel button does not update a Range button and an Area slider properly + Bug #3957: Weird bodypart rendering if a node has reserved name + Bug #3960: Clothes with high cost (> 32768) are not handled properly + Bug #3963: When on edge of being burdened the condition doesn't lower as you run. + Bug #3971: Editor: Incorrect colour field in cell table + Bug #3974: Journal page turning doesn't produce sounds + Bug #3978: Instant opening and closing happens when using a Controller with Menus/Containers + Bug #3981: Lagging when spells are cast, especially noticeable on new landmasses such as Tamriel Rebuilt + Bug #3982: Down sounds instead of Up ones are played when trading + Bug #3987: NPCs attack after some taunting with no "Goodbye" + Bug #3991: Journal can still be opened at main menu + Bug #3995: Dispel cancels every temporary magic effect + Bug #4002: Build broken on OpenBSD with clang + Bug #4003: Reduce Render Area of Inventory Doll to Fit Within Border + Bug #4004: Manis Virmaulese attacks without saying anything + Bug #4010: AiWander: "return to the spawn position" feature does not work properly + Bug #4016: Closing menus with spacebar will still send certain assigned actions through afterwards + Bug #4017: GetPCRunning and GetPCSneaking should check that the PC is actually moving + Bug #4024: Poor music track distribution + Bug #4025: Custom spell with copy-pasted name always sorts to top of spell list + Bug #4027: Editor: OpenMW-CS misreports its own name as "OpenCS", under Mac OS + Bug #4033: Archers don't attack if the arrows have run out and there is no other weapon + Bug #4037: Editor: New greetings do not work in-game. + Bug #4049: Reloading a saved game while falling prevents damage + Bug #4056: Draw animation should not be played when player equips a new weapon + Bug #4074: Editor: Merging of LAND/LTEX records + Bug #4076: Disposition bar is not updated when "goodbye" selected in dialogue + Bug #4079: Alchemy skill increases do not take effect until next batch + Bug #4093: GetResistFire, getResistFrost and getResistShock doesn't work as in vanilla + Bug #4094: Level-up messages for levels past 20 are hardcoded not to be used + Bug #4095: Error in framelistener when take all items from a dead corpse + Bug #4096: Messagebox with the "%0.f" format should use 0 digit precision + Bug #4104: Cycling through weapons does not skip broken ones + Bug #4105: birthsign generation menu does not show full details + Bug #4107: Editor: Left pane in Preferences window is too narrow + Bug #4112: Inventory sort order is inconsistent + Bug #4113: 'Resolution not supported in fullscreen' message is inconvenient + Bug #4131: Pickpocketing behaviour is different from vanilla + Bug #4155: NPCs don't equip a second ring in some cases + Bug #4156: Snow doesn't create water ripples + Bug #4165: NPCs autoequip new clothing with the same price + Feature #452: Rain-induced water ripples + Feature #824: Fading for doors and teleport commands + Feature #933: Editor: LTEX record table + Feature #936: Editor: LAND record table + Feature #1374: AI: Resurface to breathe + Feature #2320: ess-Importer: convert projectiles + Feature #2509: Editor: highlighting occurrences of a word in a script + Feature #2748: Editor: Should use one resource manager per document + Feature #2834: Have openMW's UI remember what menu items were 'pinned' across boots. + Feature #2923: Option to show the damage of the arrows through tooltip. + Feature #3099: Disabling inventory while dragging an item forces you to drop it + Feature #3274: Editor: Script Editor - Shortcuts and context menu options for commenting code out and uncommenting code respectively + Feature #3275: Editor: User Settings- Add an option to reset settings to their default status (per category / all) + Feature #3400: Add keyboard shortcuts for menus + Feature #3492: Show success rate while enchanting + Feature #3530: Editor: Reload data files + Feature #3682: Editor: Default key binding reset + Feature #3921: Combat AI: aggro priorities + Feature #3941: Allow starting at an unnamed exterior cell with --start + Feature #3952: Add Visual Studio 2017 support + Feature #3953: Combat AI: use "WhenUsed" enchantments + Feature #4082: Leave the stack of ingredients or potions grabbed after using an ingredient/potion + Task #2258: Windows installer: launch OpenMW tickbox + Task #4152: The Windows CI script is moving files around that CMake should be dealing with + 0.42.0 ------ diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 25e1c9517..86e8fb81b 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake brew outdated pkgconfig || brew upgrade pkgconfig brew install $macos_qt_formula -curl https://downloads.openmw.org/osx/dependencies/openmw-deps-5e144e2.zip -o ~/openmw-deps.zip +curl https://downloads.openmw.org/osx/dependencies/openmw-deps-c40905f.zip -o ~/openmw-deps.zip unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 14998a3c6..ee48c1f68 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -320,9 +320,9 @@ if [ -z $SKIP_DOWNLOAD ]; then "ffmpeg-3.2.4-dev-win${BITS}.zip" # MyGUI - download "MyGUI 3.2.3-git" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" \ - "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" + download "MyGUI 3.2.2" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" # OpenAL download "OpenAL-Soft 1.17.2" \ @@ -330,9 +330,9 @@ if [ -z $SKIP_DOWNLOAD ]; then "OpenAL-Soft-1.17.2.zip" # OSG - download "OpenSceneGraph 3.4.0-scrawl" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \ - "OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" + download "OpenSceneGraph 3.4.1-scrawl" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" # Qt if [ -z $APPVEYOR ]; then @@ -350,9 +350,9 @@ if [ -z $SKIP_DOWNLOAD ]; then fi # SDL2 - download "SDL 2.0.4" \ - "https://www.libsdl.org/release/SDL2-devel-2.0.4-VC.zip" \ - "SDL2-2.0.4.zip" + download "SDL 2.0.7" \ + "https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \ + "SDL2-2.0.7.zip" fi cd .. #/.. @@ -474,20 +474,20 @@ cd $DEPS echo # MyGUI -printf "MyGUI 3.2.3-git... " +printf "MyGUI 3.2.2... " { cd $DEPS_INSTALL if [ -d MyGUI ] && \ grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ - grep "MYGUI_VERSION_PATCH 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null + grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf MyGUI - eval 7z x -y "${DEPS}/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP - mv "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}" MyGUI + eval 7z x -y "${DEPS}/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP + mv "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}" MyGUI fi export MYGUI_HOME="$(real_pwd)/MyGUI" @@ -527,20 +527,20 @@ cd $DEPS echo # OSG -printf "OSG 3.4.0-scrawl... " +printf "OSG 3.4.1-scrawl... " { cd $DEPS_INSTALL if [ -d OSG ] && \ grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ grep "OPENSCENEGRAPH_MINOR_VERSION 4" OSG/include/osg/Version > /dev/null && \ - grep "OPENSCENEGRAPH_PATCH_VERSION 0" OSG/include/osg/Version > /dev/null + grep "OPENSCENEGRAPH_PATCH_VERSION 1" OSG/include/osg/Version > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf OSG - eval 7z x -y "${DEPS}/OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP - mv "OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}" OSG + eval 7z x -y "${DEPS}/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP + mv "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}" OSG fi OSG_SDK="$(real_pwd)/OSG" @@ -556,8 +556,8 @@ printf "OSG 3.4.0-scrawl... " add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \ "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll - add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.0/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll - add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.0/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll echo Done. } @@ -632,18 +632,18 @@ cd $DEPS echo # SDL2 -printf "SDL 2.0.4... " +printf "SDL 2.0.7... " { - if [ -d SDL2-2.0.4 ]; then + if [ -d SDL2-2.0.7 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.4 - eval 7z x -y SDL2-2.0.4.zip $STRIP + rm -rf SDL2-2.0.7 + eval 7z x -y SDL2-2.0.7.zip $STRIP fi - export SDL2DIR="$(real_pwd)/SDL2-2.0.4" + export SDL2DIR="$(real_pwd)/SDL2-2.0.7" - add_runtime_dlls "$(pwd)/SDL2-2.0.4/lib/x${ARCHSUFFIX}/SDL2.dll" + add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll" echo Done. } @@ -717,10 +717,10 @@ if [ -z $CI ]; then echo echo "- OSG Plugin DLLs..." - mkdir -p $BUILD_CONFIG/osgPlugins-3.4.0 + mkdir -p $BUILD_CONFIG/osgPlugins-3.4.1 for DLL in $OSG_PLUGINS; do echo " $(basename $DLL)." - cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.0 + cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.1 done echo @@ -750,19 +750,4 @@ if [ -z $VERBOSE ]; then fi fi -if [ -z $CI ]; then - echo "- Copying Runtime Resources/Config Files" - echo " gamecontrollerdb.txt" - cp gamecontrollerdb.txt $BUILD_CONFIG/gamecontrollerdb.txt - echo " openmw.cfg" - cp openmw.cfg.install $BUILD_CONFIG/openmw.cfg - echo " openmw-cs.cfg" - cp openmw-cs.cfg $BUILD_CONFIG/openmw-cs.cfg - echo " settings-default.cfg" - cp settings-default.cfg $BUILD_CONFIG/settings-default.cfg - echo " resources/" - cp -r resources $BUILD_CONFIG/resources - echo -fi - exit $RET diff --git a/CMakeLists.txt b/CMakeLists.txt index b9f452c7a..ad590e3f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,35 @@ +# Apps and tools +option(BUILD_OPENMW "build OpenMW" ON) +option(BUILD_OPENMW_MP "build OpenMW-MP" ON) +option(BUILD_MASTER "build tes3mp master server" OFF) +option(BUILD_BSATOOL "build BSA extractor" ON) +option(BUILD_ESMTOOL "build ESM inspector" ON) +option(BUILD_LAUNCHER "build Launcher" ON) +option(BUILD_BROWSER "build tes3mp Server Browser" ON) +option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) +option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) +option(BUILD_OPENCS "build OpenMW Construction Set" ON) +option(BUILD_WIZARD "build Installation Wizard" ON) +option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) +option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) +option(BUILD_NIFTEST "build nif file tester" OFF) +option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) +option(BUILD_DOCS "build documentation." OFF ) + +if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) + set(USE_QT FALSE) +else() + set(USE_QT TRUE) +endif() + +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) +endif() + +# set the minimum required version across the board +cmake_minimum_required(VERSION 3.1.0) + project(OpenMW) # If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. @@ -25,7 +57,7 @@ endif() message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 42) +set(OPENMW_VERSION_MINOR 43) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -59,24 +91,6 @@ option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) -# Apps and tools -option(BUILD_OPENMW "build OpenMW" ON) -option(BUILD_OPENMW_MP "build OpenMW-MP" ON) -option(BUILD_MASTER "build tes3mp master server" OFF) -option(BUILD_BSATOOL "build BSA extractor" ON) -option(BUILD_ESMTOOL "build ESM inspector" ON) -option(BUILD_LAUNCHER "build Launcher" ON) -option(BUILD_BROWSER "build tes3mp Server Browser" ON) -option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) -option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) -option(BUILD_OPENCS "build OpenMW Construction Set" ON) -option(BUILD_WIZARD "build Installation Wizard" ON) -option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) -option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) -option(BUILD_NIFTEST "build nif file tester" OFF) -option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) -option(BUILD_DOCS "build documentation." OFF ) - # what is necessary to build documentation IF( BUILD_DOCS ) # Builds the documentation. @@ -126,16 +140,8 @@ endif() find_package(RakNet REQUIRED) include_directories(${RakNet_INCLUDES}) -if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) - set(USE_QT FALSE) -else() - set(USE_QT TRUE) -endif() - # Dependencies if (USE_QT) - set(DESIRED_QT_VERSION 5 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) @@ -150,17 +156,6 @@ if (USE_QT) endif() endif() -if (APPLE) - # OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db - cmake_minimum_required(VERSION 3.1.0) -elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5) - # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. - cmake_minimum_required(VERSION 2.8.11) -else() - # We probably support older versions than this. - cmake_minimum_required(VERSION 2.6) -endif() - IF(BUILD_OPENMW OR BUILD_OPENCS) # Sound setup find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) @@ -323,33 +318,34 @@ endif (APPLE) # Other files -configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-client-default.cfg - "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg") +configure_resource_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-client-default.cfg + "${OpenMW_BINARY_DIR}" "tes3mp-client-default.cfg") -configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-server-default.cfg - "${OpenMW_BINARY_DIR}/tes3mp-server-default.cfg") +configure_resource_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-server-default.cfg + "${OpenMW_BINARY_DIR}" "tes3mp-server-default.cfg") -configure_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg - "${OpenMW_BINARY_DIR}/settings-default.cfg") +configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg + "${OpenMW_BINARY_DIR}" "settings-default.cfg") if (NOT APPLE) - configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local - "${OpenMW_BINARY_DIR}/openmw.cfg") - configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg - "${OpenMW_BINARY_DIR}/openmw.cfg.install") + configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local + "${OpenMW_BINARY_DIR}" "openmw.cfg") + configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg + "${OpenMW_BINARY_DIR}" "openmw.cfg.install") else () configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg") endif () -configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg - "${OpenMW_BINARY_DIR}/openmw-cs.cfg") +configure_resource_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) +# Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate. +copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters + "${OpenMW_BINARY_DIR}" "resources/defaultfilters") -configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt - "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt") +configure_resource_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt + "${OpenMW_BINARY_DIR}" "gamecontrollerdb.txt") if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop @@ -363,6 +359,7 @@ if (NOT WIN32 AND NOT APPLE) endif() # CXX Compiler settings +set(CMAKE_CXX_STANDARD 11) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++11 -pedantic -Wno-long-long") add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) @@ -398,13 +395,16 @@ IF(NOT WIN32 AND NOT APPLE) # Install binaries IF(BUILD_OPENMW) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENMW) + IF(BUILD_OPENMW_MP) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp-server" DESTINATION "${BINDIR}") + ENDIF(BUILD_OPENMW_MP) IF(BUILD_LAUNCHER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) IF(BUILD_BROWSER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-browser" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp-browser" DESTINATION "${BINDIR}" ) ENDIF(BUILD_BROWSER) IF(BUILD_BSATOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) @@ -432,7 +432,7 @@ IF(NOT WIN32 AND NOT APPLE) #ENDIF(BUILD_MYGUI_PLUGIN) # Install licenses - INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "files/mygui/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") @@ -452,10 +452,11 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client-default" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-client.cfg" COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server-default" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp") - INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-server.cfg" COMPONENT "openmw-mp") + INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + #INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-client.cfg" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp") + #INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-server.cfg" COMPONENT "openmw-mp") + #They both do not exist IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") @@ -471,16 +472,20 @@ if(WIN32) FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll") INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug) INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt") INSTALL(FILES - "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" - "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" - "${OpenMW_BINARY_DIR}/settings-default.cfg" - "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg" - "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" + "${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) if(BUILD_MYGUI_PLUGIN) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Debug/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Debug) @@ -492,7 +497,9 @@ if(WIN32) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) ENDIF() - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*") FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug) @@ -530,6 +537,7 @@ if(WIN32) SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe") + SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe") SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico") SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico") SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5a7423d2..6c82d1dfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,7 @@ Furthermore, we advise to: * Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. * If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). * Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway. +* Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged. Guidelines for original engine "fixes" ================================= diff --git a/docs/license/GPL3.txt b/LICENSE similarity index 99% rename from docs/license/GPL3.txt rename to LICENSE index 94a9ed024..9cecc1d46 100644 --- a/docs/license/GPL3.txt +++ b/LICENSE @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md index 3e4df6472..47482b536 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,14 @@ TES3MP [![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=master)](https://travis-ci.org/TES3MP/openmw-tes3mp) -TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), a free and open source recreation of the popular Bethesda Softworks game "The Elder Scrolls III: Morrowind". +TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), a free and open source engine recreation of the popular Bethesda Softworks game "The Elder Scrolls III: Morrowind". -* Version: 0.6.2 -* License: GPLv3 (see docs/license/GPL3.txt for more information) +* TES3MP version: 0.6.2 +* OpenMW version: 0.43.0 +* License: GPLv3 (see [LICENSE](https://github.com/TES3MP/openmw-tes3mp/blob/master/LICENSE) for more information) Font Licenses: -* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) +* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information) Project Status -------------- @@ -18,24 +19,24 @@ Project Status TES3MP is now playable in most respects. Player and NPC movement, animations, combat and spell casting are properly synchronized with small exceptions, as is picking up and dropping items in the world, using doors and levers, and adding and removing items from containers. Journal entries, faction stats and dialogue topics are also synchronized, allowing the majority of quests to work fine. -[Serverside Lua scripts](https://github.com/TES3MP/PluginExamples) are used to save and load the state of most of the aforementioned. +[Serverside Lua scripts](https://github.com/TES3MP/CoreScripts) are used to save and load the state of most of the aforementioned. Contributing -------------- Development has been relatively fast, but any contribution regarding [code](https://github.com/TES3MP/openmw-tes3mp/blob/master/CONTRIBUTING.md), documentation, bug hunting or video showcases is greatly appreciated. -Test sessions are often advertised in [our Steam group](https://steamcommunity.com/groups/mwmulti) or [our Discord server](https://discord.gg/H8zhhuk). +Test sessions are often advertised on [our Discord server](https://discord.gg/ECJk293) or in [our Steam group](https://steamcommunity.com/groups/mwmulti). Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have. Getting Started --------------- +* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide) +* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](http://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/) * [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=44) -* [Steam group](https://steamcommunity.com/groups/mwmulti) * [Subreddit](https://www.reddit.com/r/tes3mp) -* [Installation and build instructions](https://github.com/TES3MP/openmw-tes3mp/wiki/Installation-and-build-instructions) * [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues) Donations diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index 27baff815..ec0615ff9 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -4,7 +4,7 @@ set(BSATOOL source_group(apps\\bsatool FILES ${BSATOOL}) # Main executable -add_executable(bsatool +openmw_add_executable(bsatool ${BSATOOL} ) diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 1d5e662d8..122ca2f3a 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -8,7 +8,7 @@ set(ESMTOOL source_group(apps\\esmtool FILES ${ESMTOOL}) # Main executable -add_executable(esmtool +openmw_add_executable(esmtool ${ESMTOOL} ) diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index 93f53d0e8..0e742ff54 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -16,6 +16,8 @@ set(ESSIMPORTER_FILES importjour.cpp importscri.cpp importscpt.cpp + importproj.cpp + importsplm.cpp importercontext.cpp converter.cpp convertacdt.cpp @@ -28,7 +30,7 @@ set(ESSIMPORTER_FILES convertplayer.cpp ) -add_executable(openmw-essimporter +openmw_add_executable(openmw-essimporter ${ESSIMPORTER_FILES} ) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 68ee93c89..2473daf95 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -1,6 +1,7 @@ #include "converter.hpp" #include +#include #include @@ -53,6 +54,36 @@ namespace return true; return false; } + + void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId) + { + std::stringstream stream; + stream << std::hex << indexedRefId.substr(indexedRefId.size()-8,8); + stream >> refIndex; + + refId = indexedRefId.substr(0,indexedRefId.size()-8); + } + + int convertActorId(const std::string& indexedRefId, ESSImport::Context& context) + { + if (isIndexedRefId(indexedRefId)) + { + int refIndex; + std::string refId; + splitIndexedRefId(indexedRefId, refIndex, refId); + + auto it = context.mActorIdMap.find(std::make_pair(refIndex, refId)); + if (it == context.mActorIdMap.end()) + return -1; + return it->second; + } + else if (indexedRefId == "PlayerSaveGame") + { + return context.mPlayer.mObject.mCreatureStats.mActorId; + } + + return -1; + } } namespace ESSImport @@ -322,12 +353,9 @@ namespace ESSImport } else { - std::stringstream stream; - stream << std::hex << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8); int refIndex; - stream >> refIndex; + splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID); - out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8); std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); std::map, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find( @@ -347,6 +375,10 @@ namespace ESSImport convertNpcData(cellref, objstate.mNpcStats); convertNPCC(npccIt->second, objstate); convertCellRef(cellref, objstate); + + objstate.mCreatureStats.mActorId = mContext->generateActorId(); + mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); + esm.writeHNT ("OBJE", ESM::REC_NPC_); objstate.save(esm); continue; @@ -383,6 +415,10 @@ namespace ESSImport convertACSC(cellref.mACSC, objstate.mCreatureStats); convertCREC(crecIt->second, objstate); convertCellRef(cellref, objstate); + + objstate.mCreatureStats.mActorId = mContext->generateActorId(); + mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); + esm.writeHNT ("OBJE", ESM::REC_CREA); objstate.save(esm); continue; @@ -413,4 +449,73 @@ namespace ESSImport } } + void ConvertPROJ::read(ESM::ESMReader& esm) + { + mProj.load(esm); + } + + void ConvertPROJ::write(ESM::ESMWriter& esm) + { + for (const PROJ::PNAM& pnam : mProj.mProjectiles) + { + if (!pnam.isMagic()) + { + ESM::ProjectileState out; + convertBaseState(out, pnam); + + out.mBowId = pnam.mBowId.toString(); + out.mVelocity = pnam.mVelocity; + out.mAttackStrength = pnam.mAttackStrength; + + esm.startRecord(ESM::REC_PROJ); + out.save(esm); + esm.endRecord(ESM::REC_PROJ); + } + else + { + ESM::MagicBoltState out; + convertBaseState(out, pnam); + + auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(), + [&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; }); + + if (it == mContext->mActiveSpells.end()) + { + std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl; + continue; + } + + out.mSpellId = it->mSPDT.mId.toString(); + out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from + + esm.startRecord(ESM::REC_MPRJ); + out.save(esm); + esm.endRecord(ESM::REC_MPRJ); + } + } + } + + void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) + { + base.mId = pnam.mArrowId.toString(); + base.mPosition = pnam.mPosition; + + osg::Quat orient; + orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity); + base.mOrientation = orient; + + base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext); + } + + void ConvertSPLM::read(ESM::ESMReader& esm) + { + mSPLM.load(esm); + mContext->mActiveSpells = mSPLM.mActiveSpells; + } + + void ConvertSPLM::write(ESM::ESMWriter& esm) + { + std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl; + } + } diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index e4985f993..621b85709 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "importcrec.hpp" #include "importcntc.hpp" @@ -35,6 +36,8 @@ #include "importques.hpp" #include "importjour.hpp" #include "importscpt.hpp" +#include "importproj.h" +#include "importsplm.h" #include "convertacdt.hpp" #include "convertnpcc.hpp" @@ -593,6 +596,27 @@ private: std::vector mScripts; }; +/// Projectile converter +class ConvertPROJ : public Converter +{ +public: + virtual int getStage() override { return 2; } + virtual void read(ESM::ESMReader& esm) override; + virtual void write(ESM::ESMWriter& esm) override; +private: + void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam); + PROJ mProj; +}; + +class ConvertSPLM : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) override; + virtual void write(ESM::ESMWriter& esm) override; +private: + SPLM mSPLM; +}; + } #endif diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index d38069d89..73b15baae 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -271,6 +271,7 @@ namespace ESSImport const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value; const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value; + const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value; std::map > converters; converters[ESM::REC_GLOB] = std::shared_ptr(new ConvertGlobal()); @@ -303,12 +304,13 @@ namespace ESSImport converters[ESM::REC_QUES] = std::shared_ptr(new ConvertQUES()); converters[recJOUR ] = std::shared_ptr(new ConvertJOUR()); converters[ESM::REC_SCPT] = std::shared_ptr(new ConvertSCPT()); + converters[ESM::REC_PROJ] = std::shared_ptr(new ConvertPROJ()); + converters[recSPLM] = std::shared_ptr(new ConvertSPLM()); // TODO: // - REGN (weather in certain regions?) // - VFXM // - SPLM (active spell effects) - // - PROJ (magic projectiles in air) std::set unknownRecords; @@ -420,6 +422,19 @@ namespace ESSImport context.mPlayer.save(writer); writer.endRecord(ESM::REC_PLAY); + writer.startRecord(ESM::REC_ACTC); + writer.writeHNT("COUN", context.mNextActorId); + writer.endRecord(ESM::REC_ACTC); + + // Stage 2 requires cell references to be written / actors IDs assigned + for (std::map >::const_iterator it = converters.begin(); + it != converters.end(); ++it) + { + if (it->second->getStage() != 2) + continue; + it->second->write(writer); + } + writer.startRecord (ESM::REC_DIAS); context.mDialogueState.save(writer); writer.endRecord(ESM::REC_DIAS); diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 0ad73c267..86501b3cb 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -15,7 +15,7 @@ #include "importcrec.hpp" #include "importcntc.hpp" #include "importplayer.hpp" - +#include "importsplm.h" @@ -48,14 +48,20 @@ namespace ESSImport std::map, NPCC> mNpcChanges; std::map, CNTC> mContainerChanges; + std::map, int> mActorIdMap; + int mNextActorId; + std::map mCreatures; std::map mNpcs; + std::vector mActiveSpells; + Context() : mDay(0) , mMonth(0) , mYear(0) , mHour(0.f) + , mNextActorId(0) { mPlayer.mAutoMove = 0; ESM::CellId playerCellId; @@ -67,16 +73,23 @@ namespace ESSImport = mPlayer.mLastKnownExteriorPosition[2] = 0.0f; mPlayer.mHasMark = 0; - mPlayer.mCurrentCrimeId = 0; // TODO + mPlayer.mCurrentCrimeId = -1; // TODO + mPlayer.mPaidCrimeId = -1; mPlayer.mObject.blank(); mPlayer.mObject.mEnabled = true; mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame + mPlayer.mObject.mCreatureStats.mActorId = generateActorId(); mGlobalMapState.mBounds.mMinX = 0; mGlobalMapState.mBounds.mMaxX = 0; mGlobalMapState.mBounds.mMinY = 0; mGlobalMapState.mBounds.mMaxY = 0; } + + int generateActorId() + { + return mNextActorId++; + } }; } diff --git a/apps/essimporter/importproj.cpp b/apps/essimporter/importproj.cpp new file mode 100644 index 000000000..b2dcf4e7d --- /dev/null +++ b/apps/essimporter/importproj.cpp @@ -0,0 +1,18 @@ +#include "importproj.h" + +#include + +namespace ESSImport +{ + +void ESSImport::PROJ::load(ESM::ESMReader& esm) +{ + while (esm.isNextSub("PNAM")) + { + PNAM pnam; + esm.getHT(pnam); + mProjectiles.push_back(pnam); + } +} + +} diff --git a/apps/essimporter/importproj.h b/apps/essimporter/importproj.h new file mode 100644 index 000000000..b8abab5fa --- /dev/null +++ b/apps/essimporter/importproj.h @@ -0,0 +1,47 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTPROJ_H +#define OPENMW_ESSIMPORT_IMPORTPROJ_H + +#include +#include +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + +struct PROJ +{ + +#pragma pack(push) +#pragma pack(1) + struct PNAM // 184 bytes + { + float mAttackStrength; + float mSpeed; + unsigned char mUnknown[4*2]; + float mFlightTime; + int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) + unsigned char mUnknown2[4]; + ESM::Vector3 mVelocity; + ESM::Vector3 mPosition; + unsigned char mUnknown3[4*9]; + ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame") + ESM::NAME32 mArrowId; + ESM::NAME32 mBowId; + + bool isMagic() const { return mSplmIndex != 0; } + }; +#pragma pack(pop) + + std::vector mProjectiles; + + void load(ESM::ESMReader& esm); +}; + +} + +#endif diff --git a/apps/essimporter/importsplm.cpp b/apps/essimporter/importsplm.cpp new file mode 100644 index 000000000..9fdba4ddb --- /dev/null +++ b/apps/essimporter/importsplm.cpp @@ -0,0 +1,43 @@ +#include "importsplm.h" + +#include + +namespace ESSImport +{ + +void SPLM::load(ESM::ESMReader& esm) +{ + while (esm.isNextSub("NAME")) + { + ActiveSpell spell; + esm.getHT(spell.mIndex); + esm.getHNT(spell.mSPDT, "SPDT"); + spell.mTarget = esm.getHNOString("TNAM"); + + while (esm.isNextSub("NPDT")) + { + ActiveEffect effect; + esm.getHT(effect.mNPDT); + + // Effect-specific subrecords can follow: + // - INAM for disintegration and bound effects + // - CNAM for summoning and command effects + // - VNAM for vampirism + // NOTE: There can be multiple INAMs per effect. + // TODO: Needs more research. + + esm.skipHSubUntil("NAM0"); // sentinel + esm.getSubName(); + esm.skipHSub(); + + spell.mActiveEffects.push_back(effect); + } + + unsigned char xnam; // sentinel + esm.getHNT(xnam, "XNAM"); + + mActiveSpells.push_back(spell); + } +} + +} diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h new file mode 100644 index 000000000..8fd5c2bb5 --- /dev/null +++ b/apps/essimporter/importsplm.h @@ -0,0 +1,81 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTSPLM_H +#define OPENMW_ESSIMPORT_IMPORTSPLM_H + +#include +#include +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + +struct SPLM +{ + +#pragma pack(push) +#pragma pack(1) + struct SPDT // 160 bytes + { + int mType; // 1 = spell, 2 = enchantment, 3 = potion + ESM::NAME32 mId; // base ID of a spell/enchantment/potion + unsigned char mUnknown[4*4]; + ESM::NAME32 mCasterId; + ESM::NAME32 mSourceId; // empty for spells + unsigned char mUnknown2[4*11]; + }; + + struct NPDT // 56 bytes + { + ESM::NAME32 mAffectedActorId; + unsigned char mUnknown[4*2]; + int mMagnitude; + float mSecondsActive; + unsigned char mUnknown2[4*2]; + }; + + struct INAM // 40 bytes + { + int mUnknown; + unsigned char mUnknown2; + ESM::FIXED_STRING<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration + }; + + struct CNAM // 36 bytes + { + int mUnknown; // seems to always be 0 + ESM::NAME32 mSummonedOrCommandedActor[32]; + }; + + struct VNAM // 4 bytes + { + int mUnknown; + }; + + +#pragma pack(pop) + + struct ActiveEffect + { + NPDT mNPDT; + }; + + struct ActiveSpell + { + int mIndex; + SPDT mSPDT; + std::string mTarget; + std::vector mActiveEffects; + }; + + std::vector mActiveSpells; + + void load(ESM::ESMReader& esm); +}; + +} + +#endif diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 8cbe18d51..aac076404 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -78,7 +78,7 @@ if(NOT WIN32) endif(NOT WIN32) # Main executable -add_executable(openmw-launcher +openmw_add_executable(openmw-launcher ${GUI_TYPE} ${LAUNCHER} ${LAUNCHER_HEADER} diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 4bd661685..e83656708 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -9,7 +9,7 @@ set(MWINIIMPORT_HEADER source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) -add_executable(openmw-iniimporter +openmw_add_executable(openmw-iniimporter ${MWINIIMPORT} ) diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index d7f0200d2..3cbee2b7e 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -4,7 +4,7 @@ set(NIFTEST source_group(components\\nif\\tests FILES ${NIFTEST}) # Main executable -add_executable(niftest +openmw_add_executable(niftest ${NIFTEST} ) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index fc8930b71..b9279bf91 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -18,7 +18,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world - idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel + idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel ) @@ -70,7 +70,7 @@ opencs_units (view/world cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator - bodypartcreator + bodypartcreator landtexturecreator landcreator ) opencs_units_noqt (view/world @@ -174,7 +174,7 @@ else() set (OPENCS_OPENMW_CFG "") endif(APPLE) -add_executable(openmw-cs +openmw_add_executable(openmw-cs MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} @@ -252,7 +252,8 @@ endif() if (WIN32) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) endif() if (MSVC) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 86c87962d..35ce51337 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -90,16 +90,16 @@ std::pair > CS::Editor::readConfi boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value("")) + ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) + ("data-local", boost::program_options::value()->default_value("")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value("resources")) - ("fallback-archive", boost::program_options::value >()-> - default_value(std::vector(), "fallback-archive")->multitoken()) + ("encoding", boost::program_options::value()->default_value("win1252")) + ("resources", boost::program_options::value()->default_value("resources")) + ("fallback-archive", boost::program_options::value()-> + default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") - ("script-blacklist", boost::program_options::value >()->default_value(std::vector(), "") + ("script-blacklist", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) ->default_value(true), "enable script blacklisting"); @@ -109,25 +109,29 @@ std::pair > CS::Editor::readConfi mCfgMgr.readConfiguration(variables, desc, quiet); mDocumentManager.setEncoding ( - ToUTF8::calculateEncoding (variables["encoding"].as())); + ToUTF8::calculateEncoding (variables["encoding"].as().toStdString())); - mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + mDocumentManager.setResourceDir (mResources = variables["resources"].as().toStdString()); mDocumentManager.setFallbackMap (variables["fallback"].as().mMap); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( - variables["script-blacklist"].as >()); + variables["script-blacklist"].as().toStdStringVector()); mFsStrict = variables["fs-strict"].as(); Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = Files::PathContainer(variables["data"].as()); + dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as())); } - std::string local = variables["data-local"].as(); - if (!local.empty()) { + std::string local = variables["data-local"].as().toStdString(); + if (!local.empty()) + { + if (local.front() == '\"') + local = local.substr(1, local.length() - 2); + dataLocal.push_back(Files::PathContainer::value_type(local)); } @@ -157,7 +161,7 @@ std::pair > CS::Editor::readConfi mFileDialog.addFiles(path); } - return std::make_pair (dataDirs, variables["fallback-archive"].as >()); + return std::make_pair (dataDirs, variables["fallback-archive"].as().toStdStringVector()); } void CS::Editor::createGame() diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 7257b2fe3..7a825ba39 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -288,7 +288,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); - if (!boost::filesystem::exists (mProjectPath)) + if (mNew || !boost::filesystem::exists (mProjectPath)) { boost::filesystem::path customFiltersPath (configuration.getUserDataPath()); customFiltersPath /= "defaultfilters"; diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index 6ae778fff..c4b46958d 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -132,7 +132,7 @@ namespace CSMPrefs if (mods && i == 0) { if (mods & Qt::ControlModifier) - result.append("Ctl+"); + result.append("Ctrl+"); if (mods & Qt::ShiftModifier) result.append("Shift+"); if (mods & Qt::AltModifier) @@ -196,7 +196,7 @@ namespace CSMPrefs std::string name = value.substr(start, end - start); - if (name == "Ctl") + if (name == "Ctrl") { mods |= Qt::ControlModifier; } diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 5c0b2e282..8a9dad7f3 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -169,18 +169,25 @@ void CSMPrefs::State::declare() "list go to the first/last item"); declareCategory ("3D Scene Input"); + + declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); + declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); + declareSeparator(); + declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false); - declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); - declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); - declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); - declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); - declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); + declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); + declareSeparator(); + + declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); + declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0); + declareBool ("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); declareSeparator(); + declareBool ("context-select", "Context Sensitive Selection", false); declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). setRange (0.001, 100.0); @@ -192,6 +199,14 @@ void CSMPrefs::State::declare() setRange (0.001, 100.0); declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); + declareCategory ("Rendering"); + declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170); + declareBool ("camera-ortho", "Orthographic projection for camera", false); + declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100). + setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world."). + setRange(10, 10000); + declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); + declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); @@ -240,6 +255,8 @@ void CSMPrefs::State::declare() declareShortcut ("document-world-cells", "Open Cell List", QKeySequence()); declareShortcut ("document-world-referencables", "Open Object List", QKeySequence()); declareShortcut ("document-world-references", "Open Instance List", QKeySequence()); + declareShortcut ("document-world-lands", "Open Lands List", QKeySequence()); + declareShortcut ("document-world-landtextures", "Open Land Textures List", QKeySequence()); declareShortcut ("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); declareShortcut ("document-world-regionmap", "Open Region Map", QKeySequence()); declareShortcut ("document-mechanics-globals", "Open Global List", QKeySequence()); @@ -276,6 +293,7 @@ void CSMPrefs::State::declare() declareShortcut ("table-edit", "Edit Record", QKeySequence()); declareShortcut ("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); declareShortcut ("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); + declareShortcut ("touch-record", "Touch Record", QKeySequence()); declareShortcut ("table-revert", "Revert Record", QKeySequence()); declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); declareShortcut ("table-moveup", "Move Record Up", QKeySequence()); diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 9b595046a..b15b4b83f 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -38,9 +38,10 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeRefIdsStage (mState)); appendStage (new MergeReferencesStage (mState)); appendStage (new MergeReferencesStage (mState)); - appendStage (new ListLandTexturesMergeStage (mState)); - appendStage (new MergeLandTexturesStage (mState)); + appendStage (new PopulateLandTexturesMergeStage (mState)); appendStage (new MergeLandStage (mState)); + appendStage (new FixLandsAndLandTexturesMergeStage (mState)); + appendStage (new CleanupLandTexturesMergeStage (mState)); appendStage (new FinishMergedDocumentStage (mState, encoding)); } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 176d35914..897c3329c 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -8,7 +8,9 @@ #include "mergestate.hpp" #include "../doc/document.hpp" +#include "../world/commands.hpp" #include "../world/data.hpp" +#include "../world/idtable.hpp" CSMTools::StartMergeStage::StartMergeStage (MergeState& state) @@ -109,149 +111,105 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa } -CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state) -: mState (state) -{} +CSMTools::PopulateLandTexturesMergeStage::PopulateLandTexturesMergeStage (MergeState& state) + : mState (state) +{ +} -int CSMTools::ListLandTexturesMergeStage::setup() +int CSMTools::PopulateLandTexturesMergeStage::setup() { - return mState.mSource.getData().getLand().getSize(); + return mState.mSource.getData().getLandTextures().getSize(); } -void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::PopulateLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = - mState.mSource.getData().getLand().getRecord (stage); + const CSMWorld::Record& record = + mState.mSource.getData().getLandTextures().getRecord (stage); if (!record.isDeleted()) { - const CSMWorld::Land& land = record.get(); - - // make sure record is loaded - land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | - ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX); - - if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX)) - { - // list texture indices - std::pair key; - key.second = land.mPlugin; - - for (int i=0; imTextures[i]; - - mState.mTextureIndices[key] = -1; - } - } + mState.mTarget->getData().getLandTextures().appendRecord(record); } } -CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state) -: mState (state), mNext (mState.mTextureIndices.end()) -{} - -int CSMTools::MergeLandTexturesStage::setup() +CSMTools::MergeLandStage::MergeLandStage (MergeState& state) + : mState (state) { - // Should use the size of mState.mTextureIndices instead, but that is not available at this - // point. Unless there are any errors in the land and land texture records this will not - // make a difference. - return mState.mSource.getData().getLandTextures().getSize(); } -void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages) +int CSMTools::MergeLandStage::setup() { - if (stage==0) - mNext = mState.mTextureIndices.begin(); + return mState.mSource.getData().getLand().getSize(); +} - bool found = false; +void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); - do + if (!record.isDeleted()) { - if (mNext==mState.mTextureIndices.end()) - return; - - mNext->second = stage+1; - - std::ostringstream stream; - stream << mNext->first.first-1 << "_" << mNext->first.second; - - int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); - - if (index!=-1) - { - CSMWorld::LandTexture texture = - mState.mSource.getData().getLandTextures().getRecord (index).get(); - - stream.clear(); - stream << mNext->second-1 << "_0"; - - texture.mIndex = mNext->second-1; - texture.mId = stream.str(); - - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); - - mState.mTarget->getData().getLandTextures().appendRecord (newRecord); - - found = true; - } - - ++mNext; + mState.mTarget->getData().getLand().appendRecord (record); } - while (!found); } -CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {} +CSMTools::FixLandsAndLandTexturesMergeStage::FixLandsAndLandTexturesMergeStage (MergeState& state) + : mState (state) +{ +} -int CSMTools::MergeLandStage::setup() +int CSMTools::FixLandsAndLandTexturesMergeStage::setup() { + // We will have no more than the source return mState.mSource.getData().getLand().getSize(); } -void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::FixLandsAndLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = - mState.mSource.getData().getLand().getRecord (stage); - - if (!record.isDeleted()) + if (stage < mState.mTarget->getData().getLand().getSize()) { - const CSMWorld::Land& land = record.get(); + CSMWorld::IdTable& landTable = dynamic_cast( + *mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); + + CSMWorld::IdTable& ltexTable = dynamic_cast( + *mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); - land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | - ESM::Land::DATA_VTEX); + std::string id = mState.mTarget->getData().getLand().getId(stage); - CSMWorld::Land newLand (land); + CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id); + cmd.redo(); - newLand.mPlugin = 0; + // Get rid of base data + const CSMWorld::Record& oldRecord = + mState.mTarget->getData().getLand().getRecord (stage); - if (land.mDataTypes & ESM::Land::DATA_VTEX) - { - // adjust land texture references - if (ESM::Land::LandData *data = newLand.getLandData()) - { - std::pair key; - key.second = land.mPlugin; + CSMWorld::Record newRecord(CSMWorld::RecordBase::State_ModifiedOnly, + nullptr, &oldRecord.get()); - for (int i=0; imTextures[i]; - std::map, int>::const_iterator iter = - mState.mTextureIndices.find (key); + mState.mTarget->getData().getLand().setRecord(stage, newRecord); + } +} - if (iter!=mState.mTextureIndices.end()) - data->mTextures[i] = iter->second; - else - data->mTextures[i] = 0; - } - } - } +CSMTools::CleanupLandTexturesMergeStage::CleanupLandTexturesMergeStage (MergeState& state) + : mState (state) +{ +} - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand); +int CSMTools::CleanupLandTexturesMergeStage::setup() +{ + return 1; +} - mState.mTarget->getData().getLand().appendRecord (newRecord); +void CSMTools::CleanupLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + auto& landTextures = mState.mTarget->getData().getLandTextures(); + for (int i = 0; i < landTextures.getSize(); ) + { + if (!landTextures.getRecord(i).isModified()) + landTextures.removeRows(i, 1); + else + ++i; } } diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index f88f5be9f..4b41c5a04 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -116,13 +116,14 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages. }; - class ListLandTexturesMergeStage : public CSMDoc::Stage + /// Adds all land texture records that could potentially be referenced when merging + class PopulateLandTexturesMergeStage : public CSMDoc::Stage { MergeState& mState; public: - ListLandTexturesMergeStage (MergeState& state); + PopulateLandTexturesMergeStage (MergeState& state); virtual int setup(); ///< \return number of steps @@ -131,14 +132,13 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages. }; - class MergeLandTexturesStage : public CSMDoc::Stage + class MergeLandStage : public CSMDoc::Stage { MergeState& mState; - std::map, int>::iterator mNext; public: - MergeLandTexturesStage (MergeState& state); + MergeLandStage (MergeState& state); virtual int setup(); ///< \return number of steps @@ -147,13 +147,32 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages. }; - class MergeLandStage : public CSMDoc::Stage + /// During this stage, the complex process of combining LandTextures from + /// potentially multiple plugins is undertaken. + class FixLandsAndLandTexturesMergeStage : public CSMDoc::Stage { MergeState& mState; public: - MergeLandStage (MergeState& state); + FixLandsAndLandTexturesMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + /// Removes base LandTexture records. This gets rid of the base records previously + /// needed in FixLandsAndLandTexturesMergeStage. + class CleanupLandTexturesMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + CleanupLandTexturesMergeStage (MergeState& state); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 16f5ce51f..80117d0c6 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -13,8 +14,9 @@ #include #include "columnbase.hpp" - #include "collectionbase.hpp" +#include "land.hpp" +#include "landtexture.hpp" namespace CSMWorld { @@ -22,15 +24,14 @@ namespace CSMWorld template struct IdAccessor { - std::string& getId (ESXRecordT& record); - + void setId(ESXRecordT& record, const std::string& id) const; const std::string getId (const ESXRecordT& record) const; }; template - std::string& IdAccessor::getId (ESXRecordT& record) + void IdAccessor::setId(ESXRecordT& record, const std::string& id) const { - return record.mId; + record.mId = id; } template @@ -39,6 +40,39 @@ namespace CSMWorld return record.mId; } + template<> + inline void IdAccessor::setId (Land& record, const std::string& id) const + { + int x=0, y=0; + + Land::parseUniqueRecordId(id, x, y); + record.mX = x; + record.mY = y; + } + + template<> + inline void IdAccessor::setId (LandTexture& record, const std::string& id) const + { + int plugin = 0; + int index = 0; + + LandTexture::parseUniqueRecordId(id, plugin, index); + record.mPluginIndex = plugin; + record.mIndex = index; + } + + template<> + inline const std::string IdAccessor::getId (const Land& record) const + { + return Land::createUniqueRecordId(record.mX, record.mY); + } + + template<> + inline const std::string IdAccessor::getId (const LandTexture& record) const + { + return LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex); + } + /// \brief Single-type record collection template > class Collection : public CollectionBase @@ -69,6 +103,13 @@ namespace CSMWorld /// /// \return Success? + int cloneRecordImp (const std::string& origin, const std::string& dest, + UniversalId::Type type); + ///< Returns the index of the clone. + + int touchRecordImp (const std::string& id); + ///< Returns the index of the record on success, -1 on failure. + public: Collection(); @@ -108,6 +149,10 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type); + virtual bool touchRecord(const std::string& id); + ///< Change the state of a record from base to modified, if it is not already. + /// \return True if the record was changed. + virtual int searchId (const std::string& id) const; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) @@ -205,17 +250,72 @@ namespace CSMWorld return true; } + template + int Collection::cloneRecordImp(const std::string& origin, + const std::string& destination, UniversalId::Type type) + { + Record copy; + copy.mModified = getRecord(origin).get(); + copy.mState = RecordBase::State_ModifiedOnly; + IdAccessorT().setId(copy.get(), destination); + + int index = getAppendIndex(destination, type); + insertRecord(copy, getAppendIndex(destination, type)); + + return index; + } + + template + int Collection::touchRecordImp(const std::string& id) + { + int index = getIndex(id); + Record& record = mRecords.at(index); + if (record.isDeleted()) + { + throw std::runtime_error("attempt to touch deleted record"); + } + + if (!record.isModified()) + { + record.setModified(record.get()); + return index; + } + + return -1; + } + template void Collection::cloneRecord(const std::string& origin, - const std::string& destination, - const UniversalId::Type type) + const std::string& destination, const UniversalId::Type type) { - Record copy; - copy.mModified = getRecord(origin).get(); - copy.mState = RecordBase::State_ModifiedOnly; - copy.get().mId = destination; + cloneRecordImp(origin, destination, type); + } - insertRecord(copy, getAppendIndex(destination, type)); + template<> + inline void Collection >::cloneRecord(const std::string& origin, + const std::string& destination, const UniversalId::Type type) + { + int index = cloneRecordImp(origin, destination, type); + mRecords.at(index).get().mPlugin = 0; + } + + template + bool Collection::touchRecord(const std::string& id) + { + return touchRecordImp(id) != -1; + } + + template<> + inline bool Collection >::touchRecord(const std::string& id) + { + int index = touchRecordImp(id); + if (index >= 0) + { + mRecords.at(index).get().mPlugin = 0; + return true; + } + + return false; } template @@ -366,7 +466,7 @@ namespace CSMWorld UniversalId::Type type) { ESXRecordT record; - IdAccessorT().getId (record) = id; + IdAccessorT().setId(record, id); record.blank(); Record record2; diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index ef826e31c..bac790c5d 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -78,6 +78,8 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type) = 0; + virtual bool touchRecord(const std::string& id) = 0; + virtual const RecordBase& getRecord (const std::string& id) const = 0; virtual const RecordBase& getRecord (int index) const = 0; diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index dc3d39edb..49e4bebe6 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -1,28 +1,341 @@ #include "columnimp.hpp" -CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) - : mMeshType(meshType) -{} +#include +#include -QVariant CSMWorld::BodyPartRaceColumn::get(const Record &record) const +namespace CSMWorld { - if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + /* LandTextureNicknameColumn */ + LandTextureNicknameColumn::LandTextureNicknameColumn() + : Column(Columns::ColumnId_TextureNickname, ColumnBase::Display_String) { - return QString::fromUtf8(record.get().mRace.c_str()); } - return QVariant(QVariant::UserType); -} -void CSMWorld::BodyPartRaceColumn::set(Record &record, const QVariant &data) -{ - ESM::BodyPart record2 = record.get(); + QVariant LandTextureNicknameColumn::get(const Record& record) const + { + return QString::fromUtf8(record.get().mId.c_str()); + } - record2.mRace = data.toString().toUtf8().constData(); + void LandTextureNicknameColumn::set(Record& record, const QVariant& data) + { + LandTexture copy = record.get(); + copy.mId = data.toString().toUtf8().constData(); + record.setModified(copy); + } - record.setModified(record2); -} + bool LandTextureNicknameColumn::isEditable() const + { + return true; + } -bool CSMWorld::BodyPartRaceColumn::isEditable() const -{ - return true; + /* LandTextureIndexColumn */ + LandTextureIndexColumn::LandTextureIndexColumn() + : Column(Columns::ColumnId_TextureIndex, ColumnBase::Display_Integer) + { + } + + QVariant LandTextureIndexColumn::get(const Record& record) const + { + return record.get().mIndex; + } + + bool LandTextureIndexColumn::isEditable() const + { + return false; + } + + /* LandPluginIndexColumn */ + LandPluginIndexColumn::LandPluginIndexColumn() + : Column(Columns::ColumnId_PluginIndex, ColumnBase::Display_Integer, 0) + { + } + + QVariant LandPluginIndexColumn::get(const Record& record) const + { + return record.get().mPlugin; + } + + bool LandPluginIndexColumn::isEditable() const + { + return false; + } + + /* LandTexturePluginIndexColumn */ + LandTexturePluginIndexColumn::LandTexturePluginIndexColumn() + : Column(Columns::ColumnId_PluginIndex, ColumnBase::Display_Integer, 0) + { + } + + QVariant LandTexturePluginIndexColumn::get(const Record& record) const + { + return record.get().mPluginIndex; + } + + bool LandTexturePluginIndexColumn::isEditable() const + { + return false; + } + + /* LandMapLodColumn */ + LandMapLodColumn::LandMapLodColumn() + : Column(Columns::ColumnId_LandMapLodIndex, ColumnBase::Display_String, 0) + { + } + + QVariant LandMapLodColumn::get(const Record& record) const + { + const int Size = Land::LAND_GLOBAL_MAP_LOD_SIZE; + const Land& land = record.get(); + + DataType values(Size, 0); + + if (land.isDataLoaded(Land::DATA_WNAM)) + { + for (int i = 0; i < Size; ++i) + values[i] = land.mWnam[i]; + } + + QVariant variant; + variant.setValue(values); + return variant; + } + + void LandMapLodColumn::set(Record& record, const QVariant& data) + { + DataType values = data.value(); + + if (values.size() != Land::LAND_GLOBAL_MAP_LOD_SIZE) + throw std::runtime_error("invalid land map LOD data"); + + Land copy = record.get(); + copy.add(Land::DATA_WNAM); + + for (int i = 0; i < values.size(); ++i) + { + copy.mWnam[i] = values[i]; + } + + record.setModified(copy); + } + + bool LandMapLodColumn::isEditable() const + { + return true; + } + + /* LandNormalsColumn */ + LandNormalsColumn::LandNormalsColumn() + : Column(Columns::ColumnId_LandNormalsIndex, ColumnBase::Display_String, 0) + { + } + + QVariant LandNormalsColumn::get(const Record& record) const + { + const int Size = Land::LAND_NUM_VERTS * 3; + const Land& land = record.get(); + + DataType values(Size, 0); + + if (land.isDataLoaded(Land::DATA_VNML)) + { + for (int i = 0; i < Size; ++i) + values[i] = land.getLandData()->mNormals[i]; + } + + QVariant variant; + variant.setValue(values); + return variant; + } + + void LandNormalsColumn::set(Record& record, const QVariant& data) + { + DataType values = data.value(); + + if (values.size() != Land::LAND_NUM_VERTS * 3) + throw std::runtime_error("invalid land normals data"); + + Land copy = record.get(); + copy.add(Land::DATA_VNML); + + for (int i = 0; i < values.size(); ++i) + { + copy.getLandData()->mNormals[i] = values[i]; + } + + record.setModified(copy); + } + + bool LandNormalsColumn::isEditable() const + { + return true; + } + + /* LandHeightsColumn */ + LandHeightsColumn::LandHeightsColumn() + : Column(Columns::ColumnId_LandHeightsIndex, ColumnBase::Display_String, 0) + { + } + + QVariant LandHeightsColumn::get(const Record& record) const + { + const int Size = Land::LAND_NUM_VERTS; + const Land& land = record.get(); + + DataType values(Size, 0); + + if (land.isDataLoaded(Land::DATA_VHGT)) + { + for (int i = 0; i < Size; ++i) + values[i] = land.getLandData()->mHeights[i]; + } + + QVariant variant; + variant.setValue(values); + return variant; + } + + void LandHeightsColumn::set(Record& record, const QVariant& data) + { + DataType values = data.value(); + + if (values.size() != Land::LAND_NUM_VERTS) + throw std::runtime_error("invalid land heights data"); + + Land copy = record.get(); + copy.add(Land::DATA_VHGT); + + for (int i = 0; i < values.size(); ++i) + { + copy.getLandData()->mHeights[i] = values[i]; + } + + record.setModified(copy); + } + + bool LandHeightsColumn::isEditable() const + { + return true; + } + + /* LandColoursColumn */ + LandColoursColumn::LandColoursColumn() + : Column(Columns::ColumnId_LandColoursIndex, ColumnBase::Display_String, 0) + { + } + + QVariant LandColoursColumn::get(const Record& record) const + { + const int Size = Land::LAND_NUM_VERTS * 3; + const Land& land = record.get(); + + DataType values(Size, 0); + + if (land.isDataLoaded(Land::DATA_VCLR)) + { + for (int i = 0; i < Size; ++i) + values[i] = land.getLandData()->mColours[i]; + } + + QVariant variant; + variant.setValue(values); + return variant; + } + + void LandColoursColumn::set(Record& record, const QVariant& data) + { + DataType values = data.value(); + + if (values.size() != Land::LAND_NUM_VERTS * 3) + throw std::runtime_error("invalid land colours data"); + + Land copy = record.get(); + copy.add(Land::DATA_VCLR); + + for (int i = 0; i < values.size(); ++i) + { + copy.getLandData()->mColours[i] = values[i]; + } + + record.setModified(copy); + } + + bool LandColoursColumn::isEditable() const + { + return true; + } + + /* LandTexturesColumn */ + LandTexturesColumn::LandTexturesColumn() + : Column(Columns::ColumnId_LandTexturesIndex, ColumnBase::Display_String, 0) + { + } + + QVariant LandTexturesColumn::get(const Record& record) const + { + const int Size = Land::LAND_NUM_TEXTURES; + const Land& land = record.get(); + + DataType values(Size, 0); + + if (land.isDataLoaded(Land::DATA_VTEX)) + { + for (int i = 0; i < Size; ++i) + values[i] = land.getLandData()->mTextures[i]; + } + + QVariant variant; + variant.setValue(values); + return variant; + } + + void LandTexturesColumn::set(Record& record, const QVariant& data) + { + DataType values = data.value(); + + if (values.size() != Land::LAND_NUM_TEXTURES) + throw std::runtime_error("invalid land textures data"); + + Land copy = record.get(); + copy.add(Land::DATA_VTEX); + + for (int i = 0; i < values.size(); ++i) + { + copy.getLandData()->mTextures[i] = values[i]; + } + + record.setModified(copy); + } + + bool LandTexturesColumn::isEditable() const + { + return true; + } + + /* BodyPartRaceColumn */ + BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) + : mMeshType(meshType) + {} + + QVariant BodyPartRaceColumn::get(const Record &record) const + { + if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + { + return QString::fromUtf8(record.get().mRace.c_str()); + } + return QVariant(QVariant::UserType); + } + + void BodyPartRaceColumn::set(Record &record, const QVariant &data) + { + ESM::BodyPart record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified(record2); + } + + bool BodyPartRaceColumn::isEditable() const + { + return true; + } } diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 154bdab82..f1025acb9 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -2,10 +2,12 @@ #define CSM_WOLRD_COLUMNIMP_H #include +#include #include #include #include +#include #include #include @@ -15,6 +17,9 @@ #include "columns.hpp" #include "info.hpp" +#include "land.hpp" +#include "landtexture.hpp" + namespace CSMWorld { /// \note Shares ID with VarValueColumn. A table can not have both. @@ -60,6 +65,20 @@ namespace CSMWorld } }; + template<> + inline QVariant StringIdColumn::get(const Record& record) const + { + const Land& land = record.get(); + return QString::fromUtf8(Land::createUniqueRecordId(land.mX, land.mY).c_str()); + } + + template<> + inline QVariant StringIdColumn::get(const Record& record) const + { + const LandTexture& ltex = record.get(); + return QString::fromUtf8(LandTexture::createUniqueRecordId(ltex.mPluginIndex, ltex.mIndex).c_str()); + } + template struct RecordStateColumn : public Column { @@ -673,32 +692,23 @@ namespace CSMWorld } }; - /// \todo QColor is a GUI class and should not be in model. Need to think of an alternative - /// solution. template struct MapColourColumn : public Column { - /// \todo Replace Display_Integer with something that displays the colour value more directly. MapColourColumn() : Column (Columns::ColumnId_MapColour, ColumnBase::Display_Colour) {} virtual QVariant get (const Record& record) const { - int colour = record.get().mMapColor; - - return QColor (colour & 0xff, (colour>>8) & 0xff, (colour>>16) & 0xff); + return record.get().mMapColor; } virtual void set (Record& record, const QVariant& data) { - ESXRecordT record2 = record.get(); - - QColor colour = data.value(); - - record2.mMapColor = (colour.blue() << 16) | (colour.green() << 8) | colour.red(); - - record.setModified (record2); + ESXRecordT copy = record.get(); + copy.mMapColor = data.toInt(); + record.setModified (copy); } virtual bool isEditable() const @@ -1499,9 +1509,9 @@ namespace CSMWorld template struct TopicColumn : public Column { - TopicColumn (bool journal) + TopicColumn (bool journal) : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, - journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic) + journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic) {} virtual QVariant get (const Record& record) const @@ -1755,7 +1765,7 @@ namespace CSMWorld return true; } }; - + template struct GenderNpcColumn : public Column { @@ -2198,8 +2208,8 @@ namespace CSMWorld struct EffectTextureColumn : public Column { EffectTextureColumn (Columns::ColumnId columnId) - : Column (columnId, - columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture + : Column (columnId, + columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture : ColumnBase::Display_Icon) { assert (this->mColumnId==Columns::ColumnId_Icon || @@ -2417,7 +2427,95 @@ namespace CSMWorld return true; } }; - + + struct LandTextureNicknameColumn : public Column + { + LandTextureNicknameColumn(); + + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; + bool isEditable() const override; + }; + + struct LandTextureIndexColumn : public Column + { + LandTextureIndexColumn(); + + QVariant get(const Record& record) const override; + bool isEditable() const override; + }; + + struct LandPluginIndexColumn : public Column + { + LandPluginIndexColumn(); + + QVariant get(const Record& record) const override; + bool isEditable() const override; + }; + + struct LandTexturePluginIndexColumn : public Column + { + LandTexturePluginIndexColumn(); + + QVariant get(const Record& record) const override; + bool isEditable() const override; + }; + + struct LandMapLodColumn : public Column + { + using DataType = QVector; + + LandMapLodColumn(); + + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; + bool isEditable() const override; + }; + + struct LandNormalsColumn : public Column + { + using DataType = QVector; + + LandNormalsColumn(); + + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; + bool isEditable() const override; + }; + + struct LandHeightsColumn : public Column + { + using DataType = QVector; + + LandHeightsColumn(); + + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; + bool isEditable() const override; + }; + + struct LandColoursColumn : public Column + { + using DataType = QVector; + + LandColoursColumn(); + + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; + bool isEditable() const override; + }; + + struct LandTexturesColumn : public Column + { + using DataType = QVector; + + LandTexturesColumn(); + + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; + bool isEditable() const override; + }; + struct BodyPartRaceColumn : public RaceColumn { const MeshTypeColumn *mMeshType; @@ -2430,4 +2528,11 @@ namespace CSMWorld }; } +// This is required to access the type as a QVariant. +Q_DECLARE_METATYPE(CSMWorld::LandMapLodColumn::DataType) +//Q_DECLARE_METATYPE(CSMWorld::LandNormalsColumn::DataType) // Same as LandMapLodColumn::DataType +Q_DECLARE_METATYPE(CSMWorld::LandHeightsColumn::DataType) +Q_DECLARE_METATYPE(CSMWorld::LandColoursColumn::DataType) +Q_DECLARE_METATYPE(CSMWorld::LandTexturesColumn::DataType) + #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index aeee0d208..ec010ba36 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -124,7 +124,7 @@ namespace CSMWorld { ColumnId_Portable, "Portable" }, { ColumnId_NegativeLight, "Negative Light" }, { ColumnId_EmitterType, "Emitter Type" }, - + { ColumnId_Fire, "Fire" }, { ColumnId_OffByDefault, "Off by default" }, { ColumnId_IsKey, "Is Key" }, @@ -330,6 +330,14 @@ namespace CSMWorld { ColumnId_WeatherChance, "Percent Chance" }, { ColumnId_Text, "Text" }, + { ColumnId_TextureNickname, "Texture Nickname" }, + { ColumnId_PluginIndex, "Plugin Index" }, + { ColumnId_TextureIndex, "Texture Index" }, + { ColumnId_LandMapLodIndex, "Land map height LOD" }, + { ColumnId_LandNormalsIndex, "Land normals" }, + { ColumnId_LandHeightsIndex, "Land heights" }, + { ColumnId_LandColoursIndex, "Land colors" }, + { ColumnId_LandTexturesIndex, "Land textures" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index b23d86367..15018795c 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -329,6 +329,15 @@ namespace CSMWorld ColumnId_Text = 297, + ColumnId_TextureNickname = 298, + ColumnId_PluginIndex = 299, + ColumnId_TextureIndex = 300, + ColumnId_LandMapLodIndex = 301, + ColumnId_LandNormalsIndex = 302, + ColumnId_LandHeightsIndex = 303, + ColumnId_LandColoursIndex = 304, + ColumnId_LandTexturesIndex = 305, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 5f9422376..79900c6c4 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -15,6 +16,175 @@ #include "nestedtablewrapper.hpp" #include "pathgrid.hpp" +CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUndoCommand* parent) + : QUndoCommand(parent) + , mTable(table) + , mId(id) + , mOld(nullptr) + , mChanged(false) +{ + setText(("Touch " + mId).c_str()); + mOld.reset(mTable.getRecord(mId).clone()); +} + +void CSMWorld::TouchCommand::redo() +{ + mChanged = mTable.touchRecord(mId); +} + +void CSMWorld::TouchCommand::undo() +{ + if (mChanged) + { + mTable.setRecord(mId, *mOld); + mChanged = false; + } +} + +CSMWorld::ImportLandTexturesCommand::ImportLandTexturesCommand(IdTable& landTable, + IdTable& ltexTable, QUndoCommand* parent) + : QUndoCommand(parent) + , mLands(landTable) + , mLtexs(ltexTable) + , mOldState(0) +{ + setText("Copy land textures to current plugin"); +} + +void CSMWorld::ImportLandTexturesCommand::redo() +{ + int pluginColumn = mLands.findColumnIndex(Columns::ColumnId_PluginIndex); + int oldPlugin = mLands.data(mLands.getModelIndex(getOriginId(), pluginColumn)).toInt(); + + // Original data + int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); + mOld = mLands.data(mLands.getModelIndex(getOriginId(), textureColumn)).value(); + + // Need to make a copy so the old values can be looked up + DataType copy(mOld); + + // Perform touch/copy/etc... + onRedo(); + + // Find all indices used + std::unordered_set texIndices; + for (int i = 0; i < mOld.size(); ++i) + { + // All indices are offset by 1 for a default texture + if (mOld[i] > 0) + texIndices.insert(mOld[i] - 1); + } + + std::vector oldTextures; + for (int index : texIndices) + { + oldTextures.push_back(LandTexture::createUniqueRecordId(oldPlugin, index)); + } + + // Import the textures, replace old values + LandTextureIdTable::ImportResults results = dynamic_cast(mLtexs).importTextures(oldTextures); + mCreatedTextures = std::move(results.createdRecords); + for (const auto& it : results.recordMapping) + { + int plugin = 0, newIndex = 0, oldIndex = 0; + LandTexture::parseUniqueRecordId(it.first, plugin, oldIndex); + LandTexture::parseUniqueRecordId(it.second, plugin, newIndex); + + if (newIndex != oldIndex) + { + for (int i = 0; i < Land::LAND_NUM_TEXTURES; ++i) + { + // All indices are offset by 1 for a default texture + if (mOld[i] == oldIndex + 1) + copy[i] = newIndex + 1; + } + } + } + + // Apply modification + int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification); + mOldState = mLands.data(mLands.getModelIndex(getDestinationId(), stateColumn)).toInt(); + + QVariant variant; + variant.setValue(copy); + mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant); +} + +void CSMWorld::ImportLandTexturesCommand::undo() +{ + // Restore to previous + int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); + QVariant variant; + variant.setValue(mOld); + mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant); + + int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification); + mLands.setData(mLands.getModelIndex(getDestinationId(), stateColumn), mOldState); + + // Undo copy/touch/etc... + onUndo(); + + for (const std::string& id : mCreatedTextures) + { + int row = mLtexs.getModelIndex(id, 0).row(); + mLtexs.removeRows(row, 1); + } + mCreatedTextures.clear(); +} + +CSMWorld::CopyLandTexturesCommand::CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, + const std::string& origin, const std::string& dest, QUndoCommand* parent) + : ImportLandTexturesCommand(landTable, ltexTable, parent) + , mOriginId(origin) + , mDestId(dest) +{ +} + +const std::string& CSMWorld::CopyLandTexturesCommand::getOriginId() const +{ + return mOriginId; +} + +const std::string& CSMWorld::CopyLandTexturesCommand::getDestinationId() const +{ + return mDestId; +} + +CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTable, + const std::string& id, QUndoCommand* parent) + : ImportLandTexturesCommand(landTable, ltexTable, parent) + , mId(id) + , mOld(nullptr) + , mChanged(false) +{ + setText(("Touch " + mId).c_str()); + mOld.reset(mLands.getRecord(mId).clone()); +} + +const std::string& CSMWorld::TouchLandCommand::getOriginId() const +{ + return mId; +} + +const std::string& CSMWorld::TouchLandCommand::getDestinationId() const +{ + return mId; +} + +void CSMWorld::TouchLandCommand::onRedo() +{ + mChanged = mLands.touchRecord(mId); +} + +void CSMWorld::TouchLandCommand::onUndo() +{ + if (mChanged) + { + mLands.setRecord(mId, *mOld); + mChanged = false; + } +} + CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index b54a1d5ac..58a1b1d1c 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -5,12 +5,14 @@ #include #include +#include #include #include #include #include +#include "columnimp.hpp" #include "universalid.hpp" #include "nestedtablewrapper.hpp" @@ -24,6 +26,106 @@ namespace CSMWorld struct RecordBase; struct NestedTableWrapperBase; + class TouchCommand : public QUndoCommand + { + public: + + TouchCommand(IdTable& model, const std::string& id, QUndoCommand* parent=nullptr); + + void redo() override; + void undo() override; + + private: + + IdTable& mTable; + std::string mId; + std::unique_ptr mOld; + + bool mChanged; + }; + + /// \brief Adds LandTexture records and modifies texture indices as needed. + /// + /// LandTexture records are different from other types of records, because + /// they only effect the current plugin. Thus, when modifying or copying + /// a Land record, all of the LandTexture records referenced need to be + /// added to the current plugin. Since these newly added LandTextures could + /// have indices that conflict with pre-existing LandTextures in the current + /// plugin, the indices might have to be changed, both for the newly added + /// LandRecord and within the Land record. + class ImportLandTexturesCommand : public QUndoCommand + { + public: + + ImportLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, + QUndoCommand* parent); + + void redo() override; + void undo() override; + + protected: + + using DataType = LandTexturesColumn::DataType; + + virtual const std::string& getOriginId() const = 0; + virtual const std::string& getDestinationId() const = 0; + + virtual void onRedo() = 0; + virtual void onUndo() = 0; + + IdTable& mLands; + IdTable& mLtexs; + DataType mOld; + int mOldState; + std::vector mCreatedTextures; + }; + + /// \brief This command is used to fix LandTexture records and texture + /// indices after cloning a Land. See ImportLandTexturesCommand for + /// details. + class CopyLandTexturesCommand : public ImportLandTexturesCommand + { + public: + + CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, const std::string& origin, + const std::string& dest, QUndoCommand* parent = nullptr); + + private: + + const std::string& getOriginId() const override; + const std::string& getDestinationId() const override; + + void onRedo() override {} + void onUndo() override {} + + std::string mOriginId; + std::string mDestId; + }; + + /// \brief This command brings a land record into the current plugin, adding + /// LandTexture records and modifying texture indices as needed. + /// \note See ImportLandTextures for more details. + class TouchLandCommand : public ImportLandTexturesCommand + { + public: + + TouchLandCommand(IdTable& landTable, IdTable& ltexTable, + const std::string& id, QUndoCommand* parent = nullptr); + + private: + + const std::string& getOriginId() const override; + const std::string& getDestinationId() const override; + + void onRedo() override; + void onUndo() override; + + std::string mId; + std::unique_ptr mOld; + + bool mChanged; + }; + class ModifyCommand : public QUndoCommand { QAbstractItemModel *mModel; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 007190e4a..2216d5ca6 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -412,6 +412,24 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat Columns::ColumnId_NegativeLight, ESM::MagicEffect::NegativeLight)); mMagicEffects.addColumn (new DescriptionColumn); + mLand.addColumn (new StringIdColumn); + mLand.addColumn (new RecordStateColumn); + mLand.addColumn (new FixedRecordTypeColumn(UniversalId::Type_Land)); + mLand.addColumn (new LandPluginIndexColumn); + mLand.addColumn (new LandMapLodColumn); + mLand.addColumn (new LandNormalsColumn); + mLand.addColumn (new LandHeightsColumn); + mLand.addColumn (new LandColoursColumn); + mLand.addColumn (new LandTexturesColumn); + + mLandTextures.addColumn (new StringIdColumn(true)); + mLandTextures.addColumn (new RecordStateColumn); + mLandTextures.addColumn (new FixedRecordTypeColumn(UniversalId::Type_LandTexture)); + mLandTextures.addColumn (new LandTextureNicknameColumn); + mLandTextures.addColumn (new LandTexturePluginIndexColumn); + mLandTextures.addColumn (new LandTextureIndexColumn); + mLandTextures.addColumn (new TextureColumn); + mPathgrids.addColumn (new StringIdColumn); mPathgrids.addColumn (new RecordStateColumn); mPathgrids.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Pathgrid)); @@ -531,6 +549,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat addModel (new IdTable (&mBodyParts), UniversalId::Type_BodyPart); addModel (new IdTable (&mSoundGens), UniversalId::Type_SoundGen); addModel (new IdTable (&mMagicEffects), UniversalId::Type_MagicEffect); + addModel (new IdTable (&mLand, IdTable::Feature_AllowTouch), UniversalId::Type_Land); + addModel (new LandTextureIdTable (&mLandTextures), UniversalId::Type_LandTexture); addModel (new IdTree (&mPathgrids, &mPathgrids), UniversalId::Type_Pathgrid); addModel (new IdTable (&mStartScripts), UniversalId::Type_StartScript); addModel (new IdTree (&mReferenceables, &mReferenceables, IdTable::Feature_Preview), @@ -993,19 +1013,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) case ESM::REC_LTEX: mLandTextures.load (*mReader, mBase); break; - case ESM::REC_LAND: - { - int index = mLand.load(*mReader, mBase); - - // Load all land data for now. A future optimisation may only load non-base data - // if a suitable mechanism for avoiding race conditions can be established. - if (index!=-1/* && !mBase*/) - mLand.getRecord (index).get().loadData ( - ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | - ESM::Land::DATA_VTEX); - - break; - } + case ESM::REC_LAND: mLand.load(*mReader, mBase); break; case ESM::REC_CELL: { diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index ea6eefb88..7849aab92 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -4,6 +4,7 @@ #include #include "collection.hpp" +#include "land.hpp" namespace CSMWorld { @@ -39,6 +40,22 @@ namespace CSMWorld record.load (reader, isDeleted); } + template<> + inline void IdCollection >::loadRecord (Land& record, + ESM::ESMReader& reader, bool& isDeleted) + { + record.load (reader, isDeleted); + + // Load all land data for now. A future optimisation may only load non-base data + // if a suitable mechanism for avoiding race conditions can be established. + int flags = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; + record.loadData (flags); + + // Prevent data from being reloaded. + record.mContext.filename.clear(); + } + template int IdCollection::load (ESM::ESMReader& reader, bool base) { diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 7975e05ea..3e503a80c 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -1,11 +1,17 @@ #include "idtable.hpp" +#include +#include +#include +#include +#include #include #include #include "collectionbase.hpp" #include "columnbase.hpp" +#include "landtexture.hpp" CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features) : IdTableBase (features), mIdCollection (idCollection) @@ -179,6 +185,26 @@ void CSMWorld::IdTable::cloneRecord(const std::string& origin, endInsertRows(); } +bool CSMWorld::IdTable::touchRecord(const std::string& id) +{ + bool changed = mIdCollection->touchRecord(id); + + int row = mIdCollection->getIndex(id); + int column = mIdCollection->searchColumnIndex(Columns::ColumnId_RecordType); + if (changed && column != -1) + { + QModelIndex modelIndex = index(row, column); + emit dataChanged(modelIndex, modelIndex); + } + + return changed; +} + +std::string CSMWorld::IdTable::getId(int row) const +{ + return mIdCollection->getId(row); +} + ///This method can return only indexes to the top level table cells QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { @@ -281,3 +307,72 @@ CSMWorld::CollectionBase *CSMWorld::IdTable::idCollection() const { return mIdCollection; } + +CSMWorld::LandTextureIdTable::LandTextureIdTable(CollectionBase* idCollection, unsigned int features) + : IdTable(idCollection, features) +{ +} + +CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::importTextures(const std::vector& ids) +{ + ImportResults results; + + // Map existing textures to ids + std::map reverseLookupMap; + for (int i = 0; i < idCollection()->getSize(); ++i) + { + auto& record = static_cast&>(idCollection()->getRecord(i)); + std::string texture = record.get().mTexture; + std::transform(texture.begin(), texture.end(), texture.begin(), tolower); + if (record.isModified()) + reverseLookupMap.emplace(texture, idCollection()->getId(i)); + } + + for (const std::string& id : ids) + { + int plugin, index; + + LandTexture::parseUniqueRecordId(id, plugin, index); + int oldRow = idCollection()->searchId(id); + + // If it does not exist or it is in the current plugin, it can be skipped. + if (oldRow < 0 || plugin == 0) + { + results.recordMapping.push_back(std::make_pair(id, id)); + continue; + } + + // Look for a pre-existing record + auto& record = static_cast&>(idCollection()->getRecord(oldRow)); + std::string texture = record.get().mTexture; + std::transform(texture.begin(), texture.end(), texture.begin(), tolower); + auto searchIt = reverseLookupMap.find(texture); + if (searchIt != reverseLookupMap.end()) + { + results.recordMapping.push_back(std::make_pair(id, searchIt->second)); + continue; + } + + // Iterate until an unused index or found, or the index has completely wrapped around. + int startIndex = index; + do { + std::string newId = LandTexture::createUniqueRecordId(0, index); + int newRow = idCollection()->searchId(newId); + + if (newRow < 0) + { + // Id not taken, clone it + cloneRecord(id, newId, UniversalId::Type_LandTexture); + results.createdRecords.push_back(newId); + results.recordMapping.push_back(std::make_pair(id, newId)); + reverseLookupMap.emplace(texture, newId); + break; + } + + const size_t MaxIndex = std::numeric_limits::max() - 1; + index = (index + 1) % MaxIndex; + } while (index != startIndex); + } + + return results; +} diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 9faf64d71..4136061e4 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -61,6 +61,11 @@ namespace CSMWorld const std::string& destination, UniversalId::Type type = UniversalId::Type_None); + bool touchRecord(const std::string& id); + ///< Will change the record state to modified, if it is not already. + + std::string getId(int row) const; + virtual QModelIndex getModelIndex (const std::string& id, int column) const; void setRecord (const std::string& id, const RecordBase& record, @@ -93,6 +98,29 @@ namespace CSMWorld virtual CollectionBase *idCollection() const; }; + + /// An IdTable customized to handle the more unique needs of LandTextureId's which behave + /// differently from other records. The major difference is that base records cannot be + /// modified. + class LandTextureIdTable : public IdTable + { + public: + + struct ImportResults + { + using StringPair = std::pair; + + /// The newly added records + std::vector createdRecords; + /// The 1st string is the original id, the 2nd is the mapped id + std::vector recordMapping; + }; + + LandTextureIdTable(CollectionBase* idCollection, unsigned int features=0); + + /// Finds and maps/recreates the specified ids. + ImportResults importTextures(const std::vector& ids); + }; } #endif diff --git a/apps/opencs/model/world/idtablebase.hpp b/apps/opencs/model/world/idtablebase.hpp index 0d77d48ef..8b82f984b 100644 --- a/apps/opencs/model/world/idtablebase.hpp +++ b/apps/opencs/model/world/idtablebase.hpp @@ -32,7 +32,9 @@ namespace CSMWorld Feature_Preview = 8, /// Table can not be modified through ordinary means. - Feature_Constant = 16 + Feature_Constant = 16, + + Feature_AllowTouch = 32 }; private: @@ -61,7 +63,7 @@ namespace CSMWorld virtual bool isDeleted (const std::string& id) const = 0; virtual int getColumnId (int column) const = 0; - + unsigned int getFeatures() const; }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 80f86c746..bfa927444 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -1,15 +1,30 @@ #include "land.hpp" #include +#include namespace CSMWorld { void Land::load(ESM::ESMReader &esm, bool &isDeleted) { ESM::Land::load(esm, isDeleted); + } + std::string Land::createUniqueRecordId(int x, int y) + { std::ostringstream stream; - stream << "#" << mX << " " << mY; - mId = stream.str(); + stream << "#" << x << " " << y; + return stream.str(); + } + + void Land::parseUniqueRecordId(const std::string& id, int& x, int& y) + { + size_t mid = id.find(' '); + + if (mid == std::string::npos || id[0] != '#') + throw std::runtime_error("Invalid Land ID"); + + x = std::stoi(id.substr(1, mid - 1)); + y = std::stoi(id.substr(mid + 1)); } } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e5f25c1d3..e604f1311 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -12,10 +12,11 @@ namespace CSMWorld /// \todo Add worldspace support to the Land record. struct Land : public ESM::Land { - std::string mId; - /// Loads the metadata and ID void load (ESM::ESMReader &esm, bool &isDeleted); + + static std::string createUniqueRecordId(int x, int y); + static void parseUniqueRecordId(const std::string& id, int& x, int& y); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 266377d0f..43deb64a4 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -1,5 +1,8 @@ #include "landtexture.hpp" +#include +#include + #include namespace CSMWorld @@ -11,4 +14,21 @@ namespace CSMWorld mPluginIndex = esm.getIndex(); } + std::string LandTexture::createUniqueRecordId(int plugin, int index) + { + std::stringstream ss; + ss << 'L' << plugin << '#' << index; + return ss.str(); + } + + void LandTexture::parseUniqueRecordId(const std::string& id, int& plugin, int& index) + { + size_t middle = id.find('#'); + + if (middle == std::string::npos || id[0] != 'L') + throw std::runtime_error("Invalid LandTexture ID"); + + plugin = std::stoi(id.substr(1,middle-1)); + index = std::stoi(id.substr(middle+1)); + } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index 91459eee2..a7376438c 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -13,6 +13,11 @@ namespace CSMWorld int mPluginIndex; void load (ESM::ESMReader &esm, bool &isDeleted); + + /// Returns a string identifier that will be unique to any LandTexture. + static std::string createUniqueRecordId(int plugin, int index); + /// Deconstructs a unique string identifier into plugin and index. + static void parseUniqueRecordId(const std::string& id, int& plugin, int& index); }; } diff --git a/apps/opencs/model/world/landtexturetableproxymodel.cpp b/apps/opencs/model/world/landtexturetableproxymodel.cpp new file mode 100644 index 000000000..cf33fab9e --- /dev/null +++ b/apps/opencs/model/world/landtexturetableproxymodel.cpp @@ -0,0 +1,21 @@ +#include "landtexturetableproxymodel.hpp" + +#include "idtable.hpp" + +namespace CSMWorld +{ + LandTextureTableProxyModel::LandTextureTableProxyModel(QObject* parent) + : IdTableProxyModel(parent) + { + } + + bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const + { + int columnIndex = mSourceModel->findColumnIndex(Columns::ColumnId_Modification); + QModelIndex index = mSourceModel->index(sourceRow, columnIndex); + if (mSourceModel->data(index).toInt() != RecordBase::State_ModifiedOnly) + return false; + + return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } +} diff --git a/apps/opencs/model/world/landtexturetableproxymodel.hpp b/apps/opencs/model/world/landtexturetableproxymodel.hpp new file mode 100644 index 000000000..bbedecb53 --- /dev/null +++ b/apps/opencs/model/world/landtexturetableproxymodel.hpp @@ -0,0 +1,22 @@ +#ifndef CSM_WORLD_LANDTEXTURETABLEPROXYMODEL_H +#define CSM_WORLD_LANDTEXTURETABLEPROXYMODEL_H + +#include "idtableproxymodel.hpp" + +namespace CSMWorld +{ + /// \brief Removes base records from filtered results. + class LandTextureTableProxyModel : public IdTableProxyModel + { + Q_OBJECT + public: + + LandTextureTableProxyModel(QObject* parent = nullptr); + + protected: + + bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const override; + }; +} + +#endif diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 31dae256e..44a6ce07d 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -813,6 +813,12 @@ void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, mData.insertRecord(*newRecord, type, destination); } +bool CSMWorld::RefIdCollection::touchRecord(const std::string& id) +{ + throw std::runtime_error("RefIdCollection::touchRecord is unimplemented"); + return false; +} + void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record, UniversalId::Type type) { diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index de5a709c6..48558d1c2 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -80,6 +80,8 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type); + virtual bool touchRecord(const std::string& id); + virtual void appendBlankRecord (const std::string& id, UniversalId::Type type); ///< \param type Will be ignored, unless the collection supports multiple record types diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 77db2be10..38386f6da 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -53,6 +53,8 @@ namespace { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "LandTextures", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Meta Data Table", 0 }, @@ -118,6 +120,8 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, @@ -364,8 +368,8 @@ std::vector CSMWorld::UniversalId::listTypes (int c for (int i=0; sIndexArg[i].mName; ++i) if (sIndexArg[i].mClass & classes) list.push_back (sIndexArg[i].mType); - - return list; + + return list; } CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index e9104fc22..accd1b78d 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -126,6 +126,10 @@ namespace CSMWorld Type_SoundGen, Type_MagicEffects, Type_MagicEffect, + Type_Lands, + Type_Land, + Type_LandTextures, + Type_LandTexture, Type_Pathgrids, Type_Pathgrid, Type_StartScripts, diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index dfbeea031..10de46e06 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -172,6 +172,16 @@ void CSVDoc::View::setupWorldMenu() setupShortcut("document-world-references", references); world->addAction (references); + QAction *lands = new QAction (tr ("Lands"), this); + connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); + setupShortcut("document-world-lands", lands); + world->addAction (lands); + + QAction *landTextures = new QAction (tr ("Land Textures"), this); + connect (landTextures, SIGNAL (triggered()), this, SLOT (addLandTexturesSubView())); + setupShortcut("document-world-landtextures", landTextures); + world->addAction (landTextures); + QAction *grid = new QAction (tr ("Pathgrid"), this); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); setupShortcut("document-world-pathgrid", grid); @@ -876,6 +886,16 @@ void CSVDoc::View::addRunLogSubView() addSubView (CSMWorld::UniversalId::Type_RunLog); } +void CSVDoc::View::addLandsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Lands); +} + +void CSVDoc::View::addLandTexturesSubView() +{ + addSubView (CSMWorld::UniversalId::Type_LandTextures); +} + void CSVDoc::View::addPathgridSubView() { addSubView (CSMWorld::UniversalId::Type_Pathgrids); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 834407be0..46aa87891 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -224,6 +224,10 @@ namespace CSVDoc void addRunLogSubView(); + void addLandsSubView(); + + void addLandTexturesSubView(); + void addPathgridSubView(); void addStartScriptsSubView(); diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp index 7f901c470..1f5772f18 100644 --- a/apps/opencs/view/prefs/dialogue.cpp +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -24,7 +24,7 @@ void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) main->addWidget (list); - QFontMetrics metrics (QApplication::font()); + QFontMetrics metrics (QApplication::font(list)); int maxWidth = 1; @@ -84,7 +84,6 @@ CSVPrefs::Dialogue::~Dialogue() void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event) { QMainWindow::closeEvent (event); - CSMPrefs::State::get().save(); } diff --git a/apps/opencs/view/render/cameracontroller.cpp b/apps/opencs/view/render/cameracontroller.cpp index 7b802b0ef..37f439fd3 100644 --- a/apps/opencs/view/render/cameracontroller.cpp +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -668,16 +668,25 @@ namespace CSVRender mInitialized = true; } + + void OrbitCameraController::setConstRoll(bool enabled) + { + mConstRoll = enabled; + } void OrbitCameraController::rotateHorizontal(double value) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d absoluteUp = osg::Vec3(0,0,1); - osg::Quat rotation = osg::Quat(value, up); + osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; + if (mConstRoll) + up = rotation * up; + getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); } @@ -687,9 +696,14 @@ namespace CSVRender getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d forward = center - eye; - osg::Quat rotation = osg::Quat(value, up ^ forward); + osg::Vec3d axis = up ^ forward; + + osg::Quat rotation = osg::Quat(value,axis); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; + + if (mConstRoll) + up = rotation * up; getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); } diff --git a/apps/opencs/view/render/cameracontroller.hpp b/apps/opencs/view/render/cameracontroller.hpp index a2ebba51a..658e572c5 100644 --- a/apps/opencs/view/render/cameracontroller.hpp +++ b/apps/opencs/view/render/cameracontroller.hpp @@ -160,6 +160,8 @@ namespace CSVRender /// \brief Flag controller to be re-initialized. void reset(); + void setConstRoll(bool enable); + private: void initialize(); @@ -181,6 +183,8 @@ namespace CSVRender double mOrbitSpeed; double mOrbitSpeedMult; + bool mConstRoll; + private slots: void naviPrimary(bool active); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 765c5b316..552a54ac2 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -26,6 +26,33 @@ #include "terrainstorage.hpp" #include "object.hpp" +namespace CSVRender +{ + class CellNodeContainer : public osg::Referenced + { + public: + + CellNodeContainer(Cell* cell) : mCell(cell) {} + + Cell* getCell(){ return mCell; } + + private: + + Cell* mCell; + }; + + class CellNodeCallback : public osg::NodeCallback + { + public: + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + CellNodeContainer* container = static_cast(node->getUserData()); + container->getCell()->updateLand(); + } + }; +} + bool CSVRender::Cell::removeObject (const std::string& id) { std::map::iterator iter = @@ -75,10 +102,69 @@ bool CSVRender::Cell::addObjects (int start, int end) return modified; } +void CSVRender::Cell::updateLand() +{ + if (!mUpdateLand || mLandDeleted) + return; + + mUpdateLand = false; + + // Cell is deleted + if (mDeleted) + { + unloadLand(); + return; + } + + // Setup land if available + const CSMWorld::IdCollection& land = mData.getLand(); + int landIndex = land.searchId(mId); + if (landIndex != -1 && !land.getRecord(mId).isDeleted()) + { + const ESM::Land& esmLand = land.getRecord(mId).get(); + + if (esmLand.getLandData (ESM::Land::DATA_VHGT)) + { + if (mTerrain) + { + mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); + mTerrain->clearAssociatedCaches(); + } + else + { + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, + mData.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain)); + } + + mTerrain->loadCell(esmLand.mX, esmLand.mY); + + if (!mCellBorder) + mCellBorder.reset(new CellBorder(mCellNode, mCoordinates)); + + mCellBorder->buildShape(esmLand); + + return; + } + } + + // No land data + mLandDeleted = true; + unloadLand(); +} + +void CSVRender::Cell::unloadLand() +{ + if (mTerrain) + mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); + + if (mCellBorder) + mCellBorder.reset(); +} + CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) : mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0), - mSubModeElementMask (0) + mSubModeElementMask (0), mUpdateLand(true), mLandDeleted(false) { std::pair result = CSMWorld::CellCoordinates::fromId (id); @@ -86,6 +172,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mCoordinates = result.first; mCellNode = new osg::Group; + mCellNode->setUserData(new CellNodeContainer(this)); + mCellNode->setUpdateCallback(new CellNodeCallback); rootNode->addChild(mCellNode); setCellMarker(); @@ -99,22 +187,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st addObjects (0, rows-1); - const CSMWorld::IdCollection& land = mData.getLand(); - int landIndex = land.searchId(mId); - if (landIndex != -1) - { - const ESM::Land& esmLand = land.getRecord(mId).get(); - - if (esmLand.getLandData (ESM::Land::DATA_VHGT)) - { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, data.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain)); - mTerrain->loadCell(esmLand.mX, - esmLand.mY); - - mCellBorder.reset(new CellBorder(mCellNode, mCoordinates)); - mCellBorder->buildShape(esmLand); - } - } + updateLand(); mPathgrid.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates)); mCellWater.reset(new CellWater(mData, mCellNode, mId, mCoordinates)); @@ -285,6 +358,38 @@ void CSVRender::Cell::pathgridRemoved() mPathgrid->removeGeometry(); } +void CSVRender::Cell::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + mUpdateLand = true; +} + +void CSVRender::Cell::landAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + mLandDeleted = true; + unloadLand(); +} + +void CSVRender::Cell::landAdded (const QModelIndex& parent, int start, int end) +{ + mUpdateLand = true; + mLandDeleted = false; +} + +void CSVRender::Cell::landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + mUpdateLand = true; +} + +void CSVRender::Cell::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + mUpdateLand = true; +} + +void CSVRender::Cell::landTextureAdded (const QModelIndex& parent, int start, int end) +{ + mUpdateLand = true; +} + void CSVRender::Cell::reloadAssets() { for (std::map::const_iterator iter (mObjects.begin()); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index f53f61973..444608688 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -57,6 +57,7 @@ namespace CSVRender bool mDeleted; int mSubMode; unsigned int mSubModeElementMask; + bool mUpdateLand, mLandDeleted; /// Ignored if cell does not have an object with the given ID. /// @@ -72,6 +73,9 @@ namespace CSVRender /// \return Have any objects been added? bool addObjects (int start, int end); + void updateLand(); + void unloadLand(); + public: enum Selection @@ -118,6 +122,18 @@ namespace CSVRender void pathgridRemoved(); + void landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void landAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + void landAdded (const QModelIndex& parent, int start, int end); + + void landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + void landTextureAdded (const QModelIndex& parent, int start, int end); + void reloadAssets(); void setSelection (int elementMask, Selection mode); @@ -145,6 +161,8 @@ namespace CSVRender /// Erase all overrides and restore the visual representation of the cell to its /// true state. void reset (unsigned int elementMask); + + friend class CellNodeCallback; }; } diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 522057097..dae6467c5 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,6 +22,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/cellcoordinates.hpp" +#include "../../model/prefs/state.hpp" #include #include @@ -220,7 +222,7 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) for (int i=0; i<2; ++i) { - float length = i ? shaftLength : 0; + float length = i ? shaftLength : MarkerShaftWidth; vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); @@ -285,15 +287,15 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) for (int i=0; i<8; ++i) colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, - axis==2 ? 1.0f : 0.2f, 1.0f)); + axis==2 ? 1.0f : 0.2f, mMarkerTransparency)); for (int i=8; i<8+4+1; ++i) colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f, - axis==2 ? 1.0f : 0.0f, 1.0f)); + axis==2 ? 1.0f : 0.0f, mMarkerTransparency)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + setupCommonMarkerState(geometry); osg::ref_ptr geode (new osg::Geode); geode->addDrawable (geometry); @@ -305,11 +307,11 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) { const float Pi = 3.14159265f; - const float InnerRadius = mBaseNode->getBound().radius(); + const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius()); const float OuterRadius = InnerRadius + MarkerShaftWidth; const float SegmentDistance = 100.f; - const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance))); + const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * Pi / SegmentDistance))); const size_t VerticesPerSegment = 4; const size_t IndicesPerSegment = 24; @@ -334,6 +336,9 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, IndexCount); + // prevent some depth collision issues from overlaps + osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth/4, 0, axis); + for (size_t i = 0; i < SegmentCount; ++i) { size_t index = i * VerticesPerSegment; @@ -344,13 +349,17 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) float outerX = OuterRadius * std::cos(i * Angle); float outerY = OuterRadius * std::sin(i * Angle); - vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis); + vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset; } - colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f); + colors->at(0) = osg::Vec4f ( + axis==0 ? 1.0f : 0.2f, + axis==1 ? 1.0f : 0.2f, + axis==2 ? 1.0f : 0.2f, + mMarkerTransparency); for (size_t i = 0; i < SegmentCount; ++i) { @@ -374,7 +383,7 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) geometry->setColorArray(colors, osg::Array::BIND_OVERALL); geometry->addPrimitiveSet(primitives); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + setupCommonMarkerState(geometry); osg::ref_ptr geode = new osg::Geode(); geode->addDrawable (geometry); @@ -382,6 +391,15 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) return geode; } +void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr geometry) +{ + osg::ref_ptr state = geometry->getOrCreateStateSet(); + state->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + state->setMode(GL_BLEND, osg::StateAttribute::ON); + + state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); +} + osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) { switch (axis) @@ -399,7 +417,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) : mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), - mScaleOverride (1), mOverrideFlags (0), mSubMode (-1) + mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f) { mRootNode = new osg::PositionAttitudeTransform; @@ -453,6 +471,7 @@ void CSVRender::Object::setSelected(bool selected) else mRootNode->addChild(mBaseNode); + mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble(); updateMarker(); } @@ -629,6 +648,12 @@ void CSVRender::Object::setScale (float scale) adjustTransform(); } +void CSVRender::Object::setMarkerTransparency(float value) +{ + mMarkerTransparency = value; + updateMarker(); +} + void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) { const CSMWorld::RefCollection& collection = mData.getReferences(); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index e14697e62..3e54093d3 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -96,6 +97,7 @@ namespace CSVRender int mOverrideFlags; osg::ref_ptr mMarker[3]; int mSubMode; + float mMarkerTransparency; /// Not implemented Object (const Object&); @@ -121,6 +123,9 @@ namespace CSVRender osg::ref_ptr makeMoveOrScaleMarker (int axis); osg::ref_ptr makeRotateMarker (int axis); + /// Sets up a stateset with properties common to all marker types. + void setupCommonMarkerState(osg::ref_ptr geometry); + osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); public: @@ -179,6 +184,8 @@ namespace CSVRender /// Set override scale void setScale (float scale); + void setMarkerTransparency(float value); + /// Apply override changes via command and end edit mode void apply (CSMWorld::CommandMacro& commands); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index b1077139c..4a745195b 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -351,6 +351,72 @@ void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, } } +void CSVRender::PagedWorldspaceWidget::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + for (int r = topLeft.row(); r <= bottomRight.row(); ++r) + { + std::string id = mDocument.getData().getLand().getId(r); + + auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); + if (cellIt != mCells.end()) + { + cellIt->second->landDataChanged(topLeft, bottomRight); + flagAsModified(); + } + } +} + +void CSVRender::PagedWorldspaceWidget::landAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + for (int r = start; r <= end; ++r) + { + std::string id = mDocument.getData().getLand().getId(r); + + auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); + if (cellIt != mCells.end()) + { + cellIt->second->landAboutToBeRemoved(parent, start, end); + flagAsModified(); + } + } +} + +void CSVRender::PagedWorldspaceWidget::landAdded (const QModelIndex& parent, int start, int end) +{ + for (int r = start; r <= end; ++r) + { + std::string id = mDocument.getData().getLand().getId(r); + + auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); + if (cellIt != mCells.end()) + { + cellIt->second->landAdded(parent, start, end); + flagAsModified(); + } + } +} + +void CSVRender::PagedWorldspaceWidget::landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + for (auto cellIt : mCells) + cellIt.second->landTextureChanged(topLeft, bottomRight); + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + for (auto cellIt : mCells) + cellIt.second->landTextureAboutToBeRemoved(parent, start, end); + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::landTextureAdded (const QModelIndex& parent, int start, int end) +{ + for (auto cellIt : mCells) + cellIt.second->landTextureAdded(parent, start, end); + flagAsModified(); +} + std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { @@ -475,6 +541,24 @@ CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc connect (&document.getData(), SIGNAL (assetTablesChanged ()), this, SLOT (assetTablesChanged ())); + QAbstractItemModel *lands = document.getData().getTableModel (CSMWorld::UniversalId::Type_Lands); + + connect (lands, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (landDataChanged (const QModelIndex&, const QModelIndex&))); + connect (lands, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (landAboutToBeRemoved (const QModelIndex&, int, int))); + connect (lands, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (landAdded (const QModelIndex&, int, int))); + + QAbstractItemModel *ltexs = document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures); + + connect (ltexs, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (landTextureDataChanged (const QModelIndex&, const QModelIndex&))); + connect (ltexs, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (landTextureAboutToBeRemoved (const QModelIndex&, int, int))); + connect (ltexs, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (landTextureAdded (const QModelIndex&, int, int))); + // Shortcuts CSMPrefs::Shortcut* loadCameraCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-cell", this); connect(loadCameraCellShortcut, SIGNAL(activated()), this, SLOT(loadCameraCell())); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 8c41df51e..6672c2268 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -155,6 +155,14 @@ namespace CSVRender virtual void cellAdded (const QModelIndex& index, int start, int end); + virtual void landDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight); + virtual void landAboutToBeRemoved (const QModelIndex& parent, int start, int end); + virtual void landAdded (const QModelIndex& parent, int start, int end); + + virtual void landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight); + virtual void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end); + virtual void landTextureAdded (const QModelIndex& parent, int start, int end); + void assetTablesChanged (); void loadCameraCell(); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 11c7f5926..f24a9de50 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -56,6 +56,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) traits->vsync = false; mView = new osgViewer::View; + updateCameraParameters( traits->width / static_cast(traits->height) ); osg::ref_ptr window = new osgQt::GraphicsWindowQt(traits.get()); QLayout* layout = new QHBoxLayout(this); @@ -66,7 +67,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->setGraphicsContext(window); mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); - mView->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast(traits->width)/static_cast(traits->height), 1.0f, 10000.0f ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; lightMgr->setStartLight(1); @@ -188,6 +188,8 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); + mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); + // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); @@ -370,6 +372,40 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); } + else if (*setting=="3D Scene Input/navi-orbit-const-roll") + { + mOrbitCamControl->setConstRoll(setting->isTrue()); + } + else if (*setting=="Rendering/camera-fov" || + *setting=="Rendering/camera-ortho" || + *setting=="Rendering/camera-ortho-size") + { + updateCameraParameters(); + } +} + +void RenderWidget::updateCameraParameters(double overrideAspect) +{ + const float nearDist = 1.0; + const float farDist = 1000.0; + + if (CSMPrefs::get()["Rendering"]["camera-ortho"].isTrue()) + { + const float size = CSMPrefs::get()["Rendering"]["camera-ortho-size"].toInt(); + const float aspect = overrideAspect >= 0.0 ? overrideAspect : (width() / static_cast(height())); + const float halfH = size * 10.0; + const float halfW = halfH * aspect; + + mView->getCamera()->setProjectionMatrixAsOrtho( + -halfW, halfW, -halfH, halfH, nearDist, farDist); + } + else + { + mView->getCamera()->setProjectionMatrixAsPerspective( + CSMPrefs::get()["Rendering"]["camera-fov"].toInt(), + static_cast(width())/static_cast(height()), + nearDist, farDist); + } } void SceneWidget::selectNavigationMode (const std::string& mode) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index cc3191c81..13a109a9b 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -64,6 +64,8 @@ namespace CSVRender osg::ref_ptr mView; osg::ref_ptr mRootNode; + void updateCameraParameters(double overrideAspect = -1.0); + QTimer mTimer; protected slots: diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index c63d41be3..51c9dd009 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,5 +1,8 @@ #include "terrainstorage.hpp" +#include "../../model/world/land.hpp" +#include "../../model/world/landtexture.hpp" + namespace CSVRender { @@ -11,12 +14,9 @@ namespace CSVRender osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) { - std::ostringstream stream; - stream << "#" << cellX << " " << cellY; - // The cell isn't guaranteed to have Land. This is because the terrain implementation // has to wrap the vertices of the last row and column to the next cell, which may be a nonexisting cell - int index = mData.getLand().searchId(stream.str()); + int index = mData.getLand().searchId(CSMWorld::Land::createUniqueRecordId(cellX, cellY)); if (index == -1) return NULL; @@ -26,16 +26,11 @@ namespace CSVRender const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - int numRecords = mData.getLandTextures().getSize(); - - for (int i=0; imIndex == index && ltex->mPluginIndex == plugin) - return ltex; - } + int row = mData.getLandTextures().searchId(CSMWorld::LandTexture::createUniqueRecordId(plugin, index)); + if (row == -1) + return nullptr; - return NULL; + return &mData.getLandTextures().getRecord(row).get(); } 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 325fa5f6d..af53c86f0 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -51,6 +51,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg , mToolTipPos (-1, -1) , mShowToolTips(false) , mToolTipDelay(0) + , mInConstructor(true) { setAcceptDrops(true); @@ -114,6 +115,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); + + mInConstructor = false; } CSVRender::WorldspaceWidget::~WorldspaceWidget () @@ -128,6 +131,17 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti mDragWheelFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-shift-factor") mDragShiftFactor = setting->toDouble(); + else if (*setting=="Rendering/object-marker-alpha" && !mInConstructor) + { + float alpha = setting->toDouble(); + // getSelection is virtual, thus this can not be called from the constructor + auto selection = getSelection(Mask_Reference); + for (osg::ref_ptr tag : selection) + { + if (auto objTag = dynamic_cast(tag.get())) + objTag->mObject->setMarkerTransparency(alpha); + } + } else if (*setting=="Tooltips/scene-delay") mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 08b97e1be..9160ca47e 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -65,6 +65,7 @@ namespace CSVRender QPoint mToolTipPos; bool mShowToolTips; int mToolTipDelay; + bool mInConstructor; public: diff --git a/apps/opencs/view/world/colordelegate.cpp b/apps/opencs/view/world/colordelegate.cpp index 1a89fc675..15a07b42c 100644 --- a/apps/opencs/view/world/colordelegate.cpp +++ b/apps/opencs/view/world/colordelegate.cpp @@ -5,29 +5,32 @@ #include "../widget/coloreditor.hpp" -CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, +CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, QObject *parent) : CommandDelegate(dispatcher, document, parent) {} -void CSVWorld::ColorDelegate::paint(QPainter *painter, +void CSVWorld::ColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + int colorInt = index.data().toInt(); + QColor color(colorInt & 0xff, (colorInt >> 8) & 0xff, (colorInt >> 16) & 0xff); + QRect coloredRect(option.rect.x() + qRound(option.rect.width() / 4.0), option.rect.y() + qRound(option.rect.height() / 4.0), option.rect.width() / 2, option.rect.height() / 2); painter->save(); - painter->fillRect(coloredRect, index.data().value()); + painter->fillRect(coloredRect, color); painter->setPen(Qt::black); painter->drawRect(coloredRect); painter->restore(); } -CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document &document, +CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document &document, QObject *parent) const { return new ColorDelegate(dispatcher, document, parent); diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp index 320bbf6ae..0020fd3c8 100644 --- a/apps/opencs/view/world/creator.hpp +++ b/apps/opencs/view/world/creator.hpp @@ -31,6 +31,9 @@ namespace CSVWorld virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) = 0; + /// Touches a record, if the creator supports it. + virtual void touch(const std::vector& ids) = 0; + virtual void setEditLock (bool locked) = 0; virtual void toggleWidgets(bool active = true) = 0; diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index bf4c4967f..5e2118e9b 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -51,6 +51,11 @@ std::string CSVWorld::GenericCreator::getId() const return mId->text().toUtf8().constData(); } +std::string CSVWorld::GenericCreator::getClonedId() const +{ + return mClonedId; +} + std::string CSVWorld::GenericCreator::getIdValidatorResult() const { std::string errors; @@ -254,6 +259,22 @@ void CSVWorld::GenericCreator::cloneMode(const std::string& originId, mClonedType = type; } +void CSVWorld::GenericCreator::touch(const std::vector& ids) +{ + // Combine multiple touch commands into one "macro" command + mUndoStack.beginMacro("Touch Records"); + + CSMWorld::IdTable& table = dynamic_cast(*mData.getTableModel(mListId)); + for (const CSMWorld::UniversalId& uid : ids) + { + CSMWorld::TouchCommand* touchCmd = new CSMWorld::TouchCommand(table, uid.getId()); + mUndoStack.push(touchCmd); + } + + // Execute + mUndoStack.endMacro(); +} + void CSVWorld::GenericCreator::toggleWidgets(bool active) { } diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 60d487bc1..3baacfc06 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -64,6 +64,8 @@ namespace CSVWorld virtual std::string getId() const; + std::string getClonedId() const; + virtual std::string getIdValidatorResult() const; /// Allow subclasses to add additional data to \a command. @@ -103,6 +105,8 @@ namespace CSVWorld virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); + virtual void touch(const std::vector& ids); + virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. diff --git a/apps/opencs/view/world/landcreator.cpp b/apps/opencs/view/world/landcreator.cpp new file mode 100644 index 000000000..2ebfe1869 --- /dev/null +++ b/apps/opencs/view/world/landcreator.cpp @@ -0,0 +1,120 @@ +#include "landcreator.hpp" + +#include + +#include +#include + +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/land.hpp" + +namespace CSVWorld +{ + LandCreator::LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator(data, undoStack, id) + , mXLabel(nullptr) + , mYLabel(nullptr) + , mX(nullptr) + , mY(nullptr) + { + const int MaxInt = std::numeric_limits::max(); + const int MinInt = std::numeric_limits::min(); + + setManualEditing(false); + + mXLabel = new QLabel("X: "); + mX = new QSpinBox(); + mX->setMinimum(MinInt); + mX->setMaximum(MaxInt); + insertBeforeButtons(mXLabel, false); + insertBeforeButtons(mX, true); + + mYLabel = new QLabel("Y: "); + mY = new QSpinBox(); + mY->setMinimum(MinInt); + mY->setMaximum(MaxInt); + insertBeforeButtons(mYLabel, false); + insertBeforeButtons(mY, true); + + connect (mX, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int))); + connect (mY, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int))); + } + + void LandCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) + { + GenericCreator::cloneMode(originId, type); + + int x = 0, y = 0; + CSMWorld::Land::parseUniqueRecordId(originId, x, y); + + mX->setValue(x); + mY->setValue(y); + } + + void LandCreator::touch(const std::vector& ids) + { + // Combine multiple touch commands into one "macro" command + getUndoStack().beginMacro("Touch records"); + + CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); + CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); + for (const CSMWorld::UniversalId& uid : ids) + { + CSMWorld::TouchLandCommand* touchCmd = new CSMWorld::TouchLandCommand(lands, ltexs, uid.getId()); + getUndoStack().push(touchCmd); + } + + // Execute + getUndoStack().endMacro(); + } + + void LandCreator::focus() + { + mX->setFocus(); + } + + void LandCreator::reset() + { + GenericCreator::reset(); + mX->setValue(0); + mY->setValue(0); + } + + std::string LandCreator::getErrors() const + { + if (getData().getLand().searchId(getId()) >= 0) + return "A land with that name already exists."; + + return ""; + } + + std::string LandCreator::getId() const + { + return CSMWorld::Land::createUniqueRecordId(mX->value(), mY->value()); + } + + void LandCreator::pushCommand(std::unique_ptr command, const std::string& id) + { + if (mCloneMode) + { + CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); + CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); + + getUndoStack().beginMacro(("Clone " + id).c_str()); + getUndoStack().push(command.release()); + + CSMWorld::CopyLandTexturesCommand* ltexCopy = new CSMWorld::CopyLandTexturesCommand(lands, ltexs, getClonedId(), getId()); + getUndoStack().push(ltexCopy); + + getUndoStack().endMacro(); + } + else + getUndoStack().push (command.release()); + } + + void LandCreator::coordChanged(int value) + { + update(); + } +} diff --git a/apps/opencs/view/world/landcreator.hpp b/apps/opencs/view/world/landcreator.hpp new file mode 100644 index 000000000..e0c5577e4 --- /dev/null +++ b/apps/opencs/view/world/landcreator.hpp @@ -0,0 +1,47 @@ +#ifndef CSV_WORLD_LANDCREATOR_H +#define CSV_WORLD_LANDCREATOR_H + +#include "genericcreator.hpp" + +class QLabel; +class QSpinBox; + +namespace CSVWorld +{ + class LandCreator : public GenericCreator + { + Q_OBJECT + + QLabel* mXLabel; + QLabel* mYLabel; + QSpinBox* mX; + QSpinBox* mY; + + public: + + LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; + + void touch(const std::vector& ids) override; + + void focus() override; + + void reset() override; + + std::string getErrors() const override; + + protected: + + std::string getId() const override; + + void pushCommand(std::unique_ptr command, + const std::string& id) override; + + private slots: + + void coordChanged(int value); + }; +} + +#endif diff --git a/apps/opencs/view/world/landtexturecreator.cpp b/apps/opencs/view/world/landtexturecreator.cpp new file mode 100644 index 000000000..43d911e50 --- /dev/null +++ b/apps/opencs/view/world/landtexturecreator.cpp @@ -0,0 +1,101 @@ +#include "landtexturecreator.hpp" + +#include +#include + +#include +#include +#include + +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/landtexture.hpp" + +namespace CSVWorld +{ + LandTextureCreator::LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator(data, undoStack, id) + { + // One index is reserved for a default texture + const size_t MaxIndex = std::numeric_limits::max() - 1; + + setManualEditing(false); + + QLabel* nameLabel = new QLabel("Name"); + insertBeforeButtons(nameLabel, false); + + mNameEdit = new QLineEdit(this); + insertBeforeButtons(mNameEdit, true); + + QLabel* indexLabel = new QLabel("Index"); + insertBeforeButtons(indexLabel, false); + + mIndexBox = new QSpinBox(this); + mIndexBox->setMinimum(0); + mIndexBox->setMaximum(MaxIndex); + insertBeforeButtons(mIndexBox, true); + + connect(mNameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(nameChanged(const QString&))); + connect(mIndexBox, SIGNAL(valueChanged(int)), this, SLOT(indexChanged(int))); + } + + void LandTextureCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) + { + GenericCreator::cloneMode(originId, type); + + CSMWorld::IdTable& table = dynamic_cast(*getData().getTableModel(getCollectionId())); + + int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureNickname); + mNameEdit->setText((table.data(table.getModelIndex(originId, column)).toString())); + + column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureIndex); + mIndexBox->setValue((table.data(table.getModelIndex(originId, column)).toInt())); + } + + void LandTextureCreator::focus() + { + mIndexBox->setFocus(); + } + + void LandTextureCreator::reset() + { + GenericCreator::reset(); + mNameEdit->setText(""); + mIndexBox->setValue(0); + } + + std::string LandTextureCreator::getErrors() const + { + if (getData().getLandTextures().searchId(getId()) >= 0) + { + return "Index is already in use"; + } + + return ""; + } + + void LandTextureCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const + { + GenericCreator::configureCreateCommand(command); + + CSMWorld::IdTable& table = dynamic_cast(*getData().getTableModel(getCollectionId())); + int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureNickname); + command.addValue(column, mName.c_str()); + } + + std::string LandTextureCreator::getId() const + { + return CSMWorld::LandTexture::createUniqueRecordId(0, mIndexBox->value()); + } + + void LandTextureCreator::nameChanged(const QString& value) + { + mName = value.toUtf8().constData(); + update(); + } + + void LandTextureCreator::indexChanged(int value) + { + update(); + } +} diff --git a/apps/opencs/view/world/landtexturecreator.hpp b/apps/opencs/view/world/landtexturecreator.hpp new file mode 100644 index 000000000..b11c47758 --- /dev/null +++ b/apps/opencs/view/world/landtexturecreator.hpp @@ -0,0 +1,49 @@ +#ifndef CSV_WORLD_LANDTEXTURECREATOR_H +#define CSV_WORLD_LANDTEXTURECREATOR_H + +#include + +#include "genericcreator.hpp" + +class QLineEdit; +class QSpinBox; + +namespace CSVWorld +{ + class LandTextureCreator : public GenericCreator + { + Q_OBJECT + + public: + + LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; + + void focus() override; + + void reset() override; + + std::string getErrors() const override; + + protected: + + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; + + std::string getId() const override; + + private slots: + + void nameChanged(const QString& val); + void indexChanged(int val); + + private: + + QLineEdit* mNameEdit; + QSpinBox* mIndexBox; + + std::string mName; + }; +} + +#endif diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 7c27bdf7a..3e72f9a9e 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -18,6 +18,8 @@ #include "pathgridcreator.hpp" #include "previewsubview.hpp" #include "bodypartcreator.hpp" +#include "landcreator.hpp" +#include "landtexturecreator.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { @@ -81,6 +83,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator); + manager.add (CSMWorld::UniversalId::Type_Lands, + new CSVDoc::SubViewFactoryWithCreator >); + + manager.add (CSMWorld::UniversalId::Type_LandTextures, + new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Globals, new CSVDoc::SubViewFactoryWithCreator >); @@ -181,6 +189,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Pathgrid, new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add (CSMWorld::UniversalId::Type_Land, + new CSVDoc::SubViewFactoryWithCreator >(false)); + + manager.add (CSMWorld::UniversalId::Type_LandTexture, + new CSVDoc::SubViewFactoryWithCreator >(false)); + manager.add (CSMWorld::UniversalId::Type_DebugProfile, new CSVDoc::SubViewFactoryWithCreator > (false)); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 30dba4241..34ecd57d0 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -16,6 +16,7 @@ #include "../../model/world/idtableproxymodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/landtexturetableproxymodel.hpp" #include "../../model/world/record.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/commanddispatcher.hpp" @@ -60,6 +61,9 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) menu.addAction(mCloneAction); } + if (mTouchAction) + menu.addAction (mTouchAction); + if (mCreateAction) menu.addAction (mCreateAction); @@ -226,17 +230,22 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) -: DragRecordTable(document), mCreateAction (0), - mCloneAction(0), mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false) + : DragRecordTable(document), mCreateAction (nullptr), mCloneAction(nullptr), mTouchAction(nullptr), + mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false) { mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || id.getType() == CSMWorld::UniversalId::Type_JournalInfos; + bool isLtexTable = (id.getType() == CSMWorld::UniversalId::Type_LandTextures); if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); } + else if (isLtexTable) + { + mProxyModel = new CSMWorld::LandTextureTableProxyModel (this); + } else { mProxyModel = new CSMWorld::IdTableProxyModel (this); @@ -302,6 +311,15 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, cloneShortcut->associateAction(mCloneAction); } + if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) + { + mTouchAction = new QAction(tr("Touch Record"), this); + connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord())); + addAction(mTouchAction); + CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this); + touchShortcut->associateAction(mTouchAction); + } + mRevertAction = new QAction (tr ("Revert Record"), this); connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); addAction (mRevertAction); @@ -442,6 +460,22 @@ void CSVWorld::Table::cloneRecord() } } +void CSVWorld::Table::touchRecord() +{ + if (!mEditLock && mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) + { + std::vector touchIds; + + QModelIndexList selectedRows = selectionModel()->selectedRows(); + for (auto it = selectedRows.begin(); it != selectedRows.end(); ++it) + { + touchIds.push_back(getUniversalId(it->row())); + } + + emit touchRequest(touchIds); + } +} + void CSVWorld::Table::moveUpRecord() { if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 26f6e7899..02f9023e7 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -56,6 +56,7 @@ namespace CSVWorld QAction *mEditAction; QAction *mCreateAction; QAction *mCloneAction; + QAction *mTouchAction; QAction *mRevertAction; QAction *mDeleteAction; QAction *mMoveUpAction; @@ -115,6 +116,8 @@ namespace CSVWorld void cloneRequest(const CSMWorld::UniversalId&); + void touchRequest(const std::vector& ids); + void closeRequest(); void extendedDeleteConfigRequest(const std::vector &selectedIds); @@ -129,6 +132,8 @@ namespace CSVWorld void cloneRecord(); + void touchRecord(); + void moveUpRecord(); void moveDownRecord(); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 5a25bbc53..cfde5c694 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -72,9 +72,9 @@ void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandCo mExtendedConfigurator->setFocus(); } -CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, +CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget *parent) : QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false), mRow(0), mColumn(0) { @@ -249,6 +249,11 @@ void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, mCreator->focus(); } +void CSVWorld::TableBottomBox::touchRequest(const std::vector& ids) +{ + mCreator->touch(ids); +} + void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) { extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 781cccc9e..5402c466e 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -102,6 +102,7 @@ namespace CSVWorld void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); + void touchRequest(const std::vector&); void extendedDeleteConfigRequest(const std::vector &selectedIds); void extendedRevertConfigRequest(const std::vector &selectedIds); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 6664a3771..12e29995d 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -69,6 +69,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + connect (mTable, SIGNAL(touchRequest(const std::vector&)), + mBottom, SLOT(touchRequest(const std::vector&))); + connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), diff --git a/apps/openmw-mp/main.cpp b/apps/openmw-mp/main.cpp index 342c77eda..f9c490a08 100644 --- a/apps/openmw-mp/main.cpp +++ b/apps/openmw-mp/main.cpp @@ -1,26 +1,29 @@ -#include -#include -#include "Player.hpp" -#include "Networking.hpp" -#include "MasterClient.hpp" -#include -#include -#include -#include -#include #include -#include -#include + +#include #include #include -#include + +#include +#include +#include +#include + +#include +#include #include -#include "Utils.hpp" +#include +#include +#include +#include + +#include "Player.hpp" +#include "Networking.hpp" #include "MasterClient.hpp" +#include "Utils.hpp" -#include -#include +#include #ifdef ENABLE_BREAKPAD #include @@ -29,41 +32,6 @@ using namespace std; using namespace mwmp; -void printVersion(string version, Version::Version ver, int protocol) -{ - cout << "TES3:MP dedicated server " << version; - cout << " ("; -#if defined(_WIN32) - cout << "Windows"; -#elif defined(__linux) - cout << "Linux"; -#elif defined(__APPLE__) - cout << "OS X"; -#else - cout << "Unknown OS"; -#endif - cout << " "; -#ifdef __x86_64__ - cout << "64-bit"; -#elif defined(__i386__) || defined(_M_I86) - cout << "32-bit"; -#elif defined(__ARM_ARCH) - cout << "ARMv" << __ARM_ARCH << " "; - #ifdef __aarch64__ - cout << "64-bit"; - #else - cout << "32-bit"; - #endif -#else - cout << "Unknown architecture"; -#endif - cout << ")" << endl; - cout << "Protocol version: " << protocol << endl; - cout << "Commit hash: " << ver.mCommitHash.substr(0, 10) << endl; - - cout << "------------------------------------------------------------" << endl; -} - #ifdef ENABLE_BREAKPAD google_breakpad::ExceptionHandler *pHandler = 0; #if defined(_WIN32) @@ -230,9 +198,7 @@ int main(int argc, char *argv[]) vector plugins (Utils::split(mgr.getString("plugins", "Plugins"), ',')); - - printVersion(TES3MP_VERSION, version, TES3MP_PROTO_VERSION); - + Utils::printVersion("TES3MP dedicated server", TES3MP_VERSION, version.mCommitHash, TES3MP_PROTO_VERSION); setenv("AMXFILE", moddir.c_str(), 1); setenv("MOD_DIR", moddir.c_str(), 1); // hack for lua diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d71c9d5f3..b8f4aaac5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -42,7 +42,7 @@ add_openmw_dir (mwgui itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview - draganddrop timeadvancer jailscreen itemchargeview + draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation ) add_openmw_dir (mwdialogue @@ -57,7 +57,8 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory + soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output + loudness movieaudiofactory alext efx efx-presets ) add_openmw_dir (mwworld @@ -131,7 +132,7 @@ add_openmw_dir (mwmp/processors/world BaseObjectProcessor ProcessorConsoleComman # Main executable if (NOT ANDROID) - add_executable(tes3mp + openmw_add_executable(tes3mp ${OPENMW_FILES} ${GAME} ${GAME_HEADER} ${APPLE_BUNDLE_RESOURCES} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 1b4af0592..ee995ee5b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -108,12 +108,14 @@ void OMW::Engine::executeLocalScripts() } } -void OMW::Engine::frame(float frametime) +bool OMW::Engine::frame(float frametime) { try { mStartTick = mViewer->getStartTick(); + mEnvironment.setFrameDuration(frametime); + // update input mEnvironment.getInputManager()->update(frametime, false); @@ -127,7 +129,7 @@ void OMW::Engine::frame(float frametime) The game cannot be paused in multiplayer, so prevent that from happening even here */ //if (!mEnvironment.getInputManager()->isWindowVisible()) - // return; + // return false; /* End of tes3mp change (major) */ @@ -240,11 +242,6 @@ void OMW::Engine::frame(float frametime) // update GUI mEnvironment.getWindowManager()->onFrame(frametime); - if (mEnvironment.getStateManager()->getState()!= - MWBase::StateManager::State_NoGame) - { - mEnvironment.getWindowManager()->update(); - } unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); osg::Stats* stats = mViewer->getViewerStats(); @@ -271,8 +268,9 @@ void OMW::Engine::frame(float frametime) } catch (const std::exception& e) { - std::cerr << "Error in framelistener: " << e.what() << std::endl; + std::cerr << "Error in frame: " << e.what() << std::endl; } + return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) @@ -831,29 +829,9 @@ void OMW::Engine::go() frameTimer.setStartTick(); dt = std::min(dt, 0.2); - bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); - - /* - Start of tes3mp change (major) - - Whether the GUI is active should have no relevance in multiplayer, so the guiActive - boolean is always set to false instead - */ - guiActive = false; - /* - End of tes3mp change (major) - */ - - if (!guiActive) - simulationTime += dt; - mViewer->advance(simulationTime); - mEnvironment.setFrameDuration(dt); - - frame(dt); - - if (!mEnvironment.getInputManager()->isWindowVisible()) + if (!frame(dt)) { OpenThreads::Thread::microSleep(5000); continue; @@ -866,6 +844,22 @@ void OMW::Engine::go() mEnvironment.getWorld()->updateWindowManager(); mViewer->renderingTraversals(); + + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); + + /* + Start of tes3mp change (major) + + Whether the GUI is active should have no relevance in multiplayer, so the guiActive + boolean is always set to false instead + */ + guiActive = false; + /* + End of tes3mp change (major) + */ + + if (!guiActive) + simulationTime += dt; } mEnvironment.limitFrameRate(frameTimer.time_s()); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index bf144bfed..2fff91c7e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -118,7 +118,7 @@ namespace OMW void executeLocalScripts(); - void frame (float dt); + bool frame (float dt); /// Load settings from various files, returns the path to the user settings file std::string loadSettings (Settings::Manager & settings); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 1421930e2..d482dcf6d 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -49,9 +49,11 @@ extern int is_debugger_attached(void); /* Start of tes3mp addition - Include the header of the logger added for multiplayer + Include additional headers for multiplayer purposes */ #include +#include +#include /* End of tes3mp addition */ @@ -207,7 +209,26 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().toStdString()); - std::cout << v.describe() << std::endl; + + /* + Start of tes3mp addition + + Print the multiplayer version first + */ + Utils::printVersion("TES3MP client", TES3MP_VERSION, v.mCommitHash, TES3MP_PROTO_VERSION); + /* + End of tes3mp addition + */ + + /* + Start of tes3mp change (minor) + + Because there is no need to print the commit hash again, only print OpenMW's version + */ + std::cout << "OpenMW version " << v.mVersion << std::endl; + /* + End of tes3mp change (minor) + */ engine.setGrabMouse(!variables["no-grab"].as()); @@ -224,6 +245,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat std::string local(variables["data-local"].as().toStdString()); if (!local.empty()) { + if (local.front() == '\"') + local = local.substr(1, local.length() - 2); + dataDirs.push_back(Files::PathContainer::value_type(local)); } diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index f489e4cb2..213bebe8b 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -2,6 +2,8 @@ #define GAME_MWBASE_DIALOGUEMANAGER_H #include +#include +#include #include @@ -34,6 +36,12 @@ namespace MWBase public: + class ResponseCallback + { + public: + virtual void addResponse(const std::string& title, const std::string& text) = 0; + }; + DialogueManager() {} virtual void clear() = 0; @@ -42,7 +50,7 @@ namespace MWBase virtual bool isInChoice() const = 0; - virtual void startDialogue (const MWWorld::Ptr& actor) = 0; + virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0; virtual void addTopic (const std::string& topic) = 0; @@ -57,20 +65,24 @@ namespace MWBase End of tes3mp addition */ - virtual void askQuestion (const std::string& question,int choice) = 0; + virtual void addChoice (const std::string& text,int choice) = 0; + virtual const std::vector >& getChoices() = 0; + + virtual bool isGoodbye() = 0; virtual void goodbye() = 0; virtual void say(const MWWorld::Ptr &actor, const std::string &topic) = 0; - //calbacks for the GUI - virtual void keywordSelected (const std::string& keyword) = 0; + virtual void keywordSelected (const std::string& keyword, ResponseCallback* callback) = 0; virtual void goodbyeSelected() = 0; - virtual void questionAnswered (int answer) = 0; + virtual void questionAnswered (int answer, ResponseCallback* callback) = 0; + + virtual std::list getAvailableTopics() = 0; - virtual bool checkServiceRefused () = 0; + virtual bool checkServiceRefused (ResponseCallback* callback) = 0; - virtual void persuade (int type) = 0; + virtual void persuade (int type, ResponseCallback* callback) = 0; virtual int getTemporaryDispositionChange () const = 0; /// @note This change is temporary and gets discarded when dialogue ends. @@ -98,7 +110,7 @@ namespace MWBase Declare this method here so it can be used from outside of MWDialogue::DialogueManager */ - virtual void updateTopics() = 0; + virtual void updateActorKnownTopics() = 0; /* End of tes3mp addition */ diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index e6d71eaa2..7ca0be7e3 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -150,7 +150,7 @@ namespace MWBase /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, - int count) = 0; + int count, bool alarm = true) = 0; /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; /// Attempt sleeping in a bed. If this is illegal, call commitCrime. @@ -201,6 +201,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; + /// Check if there are actors in selected range + virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0; + ///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; @@ -235,6 +238,10 @@ namespace MWBase virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0; + /// Check if the target actor was detected by an observer + /// If the observer is a non-NPC, check all actors in AI processing distance as observers + virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; + virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). @@ -244,7 +251,8 @@ namespace MWBase /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim) = 0; + virtual bool isBoundItem(const MWWorld::Ptr& item) = 0; + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0; /// Turn actor into werewolf or normal form. virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0; @@ -256,7 +264,6 @@ namespace MWBase virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; - virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 9aa4bafdf..2b3cf1f0d 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -18,54 +18,59 @@ namespace MWSound class Stream; struct Sound_Decoder; typedef std::shared_ptr DecoderPtr; + + /* These must all fit together */ + enum class PlayMode { + Normal = 0, /* non-looping, affected by environment */ + Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ + NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ + 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. */ + NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the + * player is making it. */ + LoopNoEnv = Loop | NoEnv, + LoopRemoveAtDistance = Loop | RemoveAtDistance + }; + enum class Type { + Sfx = 1<<4, /* Normal SFX sound */ + Voice = 1<<5, /* Voice sound */ + Foot = 1<<6, /* Footstep sound */ + Music = 1<<7, /* Music track */ + Movie = 1<<8, /* Movie audio track */ + Mask = Sfx | Voice | Foot | Music | Movie + }; + // Used for creating a type mask for SoundManager::pauseSounds and resumeSounds + inline int operator~(Type a) { return ~static_cast(a); } + inline int operator&(Type a, Type b) { return static_cast(a) & static_cast(b); } + inline int operator&(int a, Type b) { return a & static_cast(b); } + inline int operator|(Type a, Type b) { return static_cast(a) | static_cast(b); } } namespace MWBase { - typedef std::shared_ptr SoundPtr; - typedef std::shared_ptr SoundStreamPtr; + using Sound = MWSound::Sound; + using SoundStream = MWSound::Stream; /// \brief Interface for sound manager (implemented in MWSound) class SoundManager { - public: - /* These must all fit together */ - enum PlayMode { - 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_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 - }; - enum PlayType { - Play_TypeSfx = 1<<4, /* Normal SFX sound */ - Play_TypeVoice = 1<<5, /* Voice sound */ - Play_TypeFoot = 1<<6, /* Footstep sound */ - Play_TypeMusic = 1<<7, /* Music track */ - Play_TypeMovie = 1<<8, /* Movie audio track */ - Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeFoot|Play_TypeMusic|Play_TypeMovie - }; - - private: - SoundManager (const SoundManager&); ///< not implemented SoundManager& operator= (const SoundManager&); ///< not implemented - public: + protected: + using PlayMode = MWSound::PlayMode; + using Type = MWSound::Type; + public: SoundManager() {} - virtual ~SoundManager() {} virtual void processChangedSettings(const std::set< std::pair >& settings) = 0; @@ -77,9 +82,6 @@ namespace MWBase ///< Play a soundifle /// \param filename name of a sound file in "Music/" in the data directory. - virtual void startRandomTitle() = 0; - ///< Starts a random track from the current playlist - virtual bool isMusicPlaying() = 0; ///< Returns true if music is playing @@ -106,34 +108,36 @@ namespace MWBase /// and get an average loudness value (scale [0,1]) at the current time position. /// If the actor is not saying anything, returns 0. - virtual SoundStreamPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; - ///< Play a 2D audio track, using a custom decoder + virtual SoundStream *playTrack(const MWSound::DecoderPtr& decoder, Type type) = 0; + ///< Play a 2D audio track, using a custom decoder. The caller is expected to call + /// stopTrack with the returned handle when done. - virtual void stopTrack(SoundStreamPtr stream) = 0; + virtual void stopTrack(SoundStream *stream) = 0; ///< Stop the given audio track from playing - virtual double getTrackTimeDelay(SoundStreamPtr stream) = 0; + virtual double getTrackTimeDelay(SoundStream *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; + virtual Sound *playSound(const std::string& soundId, float volume, float pitch, + Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, + float offset=0) = 0; ///< Play a sound, independently of 3D-position ///< @param offset Number of seconds into the sound to start playback. - 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; + virtual Sound *playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, + float volume, float pitch, Type type=Type::Sfx, + PlayMode mode=PlayMode::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 Number of seconds into the sound to start playback. - 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; + virtual Sound *playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, + float volume, float pitch, Type type=Type::Sfx, + PlayMode mode=PlayMode::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 stopSound(SoundPtr sound) = 0; + virtual void stopSound(Sound *sound) = 0; ///< Stop the given sound from playing virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId) = 0; @@ -158,10 +162,10 @@ namespace MWBase ///< 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 - virtual void pauseSounds(int types=Play_TypeMask) = 0; + virtual void pauseSounds(int types=static_cast(Type::Mask)) = 0; ///< Pauses all currently playing sounds, including music. - virtual void resumeSounds(int types=Play_TypeMask) = 0; + virtual void resumeSounds(int types=static_cast(Type::Mask)) = 0; ///< Resumes all previously paused sounds. virtual void update(float duration) = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f77134b68..52d637c64 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../mwgui/mode.hpp" namespace Loading @@ -100,23 +102,17 @@ namespace MWBase virtual ~WindowManager() {} - /** - * Should be called each frame to update windows/gui elements. - * This could mean updating sizes of gui elements or opening - * new dialogs. - */ - virtual void update() = 0; - /// @note This method will block until the video finishes playing /// (and will continually update the window while doing so) virtual void playVideo(const std::string& name, bool allowSkipping) = 0; virtual void setNewGame(bool newgame) = 0; + virtual void pushGuiMode (MWGui::GuiMode mode, const MWWorld::Ptr& arg) = 0; virtual void pushGuiMode (MWGui::GuiMode mode) = 0; - virtual void popGuiMode() = 0; + virtual void popGuiMode(bool noSound=false) = 0; - virtual void removeGuiMode (MWGui::GuiMode mode) = 0; + virtual void removeGuiMode (MWGui::GuiMode mode, bool noSound=false) = 0; ///< can be anywhere in the stack virtual void goToJail(int days) = 0; @@ -144,7 +140,6 @@ namespace MWBase virtual bool isAllowed (MWGui::GuiWindow wnd) const = 0; /// \todo investigate, if we really need to expose every single lousy UI element to the outside world - virtual MWGui::DialogueWindow* getDialogueWindow() = 0; virtual MWGui::InventoryWindow* getInventoryWindow() = 0; virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; @@ -195,6 +190,7 @@ namespace MWBase virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0; virtual void setCursorVisible(bool visible) = 0; + virtual void setCursorActive(bool active) = 0; virtual void getMousePosition(int &x, int &y) = 0; virtual void getMousePosition(float &x, float &y) = 0; virtual void setDragDrop(bool dragDrop) = 0; @@ -259,7 +255,7 @@ namespace MWBase virtual void showCrosshair(bool show) = 0; virtual bool getSubtitlesEnabled() = 0; - virtual bool toggleGui() = 0; + virtual bool toggleHud() = 0; virtual void disallowMouse() = 0; virtual void allowMouse() = 0; @@ -333,21 +329,6 @@ namespace MWBase virtual bool getPlayerSleeping() = 0; virtual void wakeUpPlayer() = 0; - virtual void showCompanionWindow(MWWorld::Ptr actor) = 0; - virtual void startSpellMaking(MWWorld::Ptr actor) = 0; - virtual void startEnchanting(MWWorld::Ptr actor) = 0; - virtual void startRecharge(MWWorld::Ptr soulgem) = 0; - virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0; - virtual void startTraining(MWWorld::Ptr actor) = 0; - virtual void startRepair(MWWorld::Ptr actor) = 0; - virtual void startRepairItem(MWWorld::Ptr item) = 0; - virtual void startTravel(const MWWorld::Ptr& actor) = 0; - virtual void startSpellBuying(const MWWorld::Ptr& actor) = 0; - virtual void startTrade(const MWWorld::Ptr& actor) = 0; - virtual void openContainer(const MWWorld::Ptr& container, bool loot) = 0; - virtual void showBook(const MWWorld::Ptr& item, bool showTakeButton) = 0; - virtual void showScroll(const MWWorld::Ptr& item, bool showTakeButton) = 0; - virtual void showSoulgemDialog (MWWorld::Ptr item) = 0; virtual void changePointer (const std::string& name) = 0; @@ -389,11 +370,11 @@ namespace MWBase virtual void pinWindow (MWGui::GuiWindow window) = 0; /// Fade the screen in, over \a time seconds - virtual void fadeScreenIn(const float time, bool clearQueue=true) = 0; + virtual void fadeScreenIn(const float time, bool clearQueue=true, float delay=0.f) = 0; /// Fade the screen out to black, over \a time seconds - virtual void fadeScreenOut(const float time, bool clearQueue=true) = 0; + virtual void fadeScreenOut(const float time, bool clearQueue=true, float delay=0.f) = 0; /// Fade the screen to a specified percentage of black, over \a time seconds - virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true) = 0; + virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true, float delay=0.f) = 0; /// Darken the screen to a specified percentage virtual void setBlindness(const int percent) = 0; @@ -411,7 +392,7 @@ namespace MWBase // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path) = 0; - virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0; + virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0; virtual std::string correctTexturePath(const std::string& path) = 0; virtual bool textureExists(const std::string& path) = 0; @@ -419,6 +400,8 @@ namespace MWBase virtual void writeFog(MWWorld::CellStore* cell) = 0; virtual const MWGui::TextColours& getTextColours() = 0; + + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 9a92f427e..222199211 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -522,8 +522,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; + virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; 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; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 234e72664..ecae30083 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -181,9 +181,7 @@ namespace MWClass if(isTrapped) { ptr.getCellRef().setTrap(""); - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, - "Disarm Trap", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, - MWBase::SoundManager::Play_Normal); + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f); isTrapped = false; /* diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5edf9ff0f..6d198b261 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -567,11 +567,11 @@ namespace MWClass // by default user can loot friendly actors during death animation if (canLoot && !stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // death animation is not finished, do nothing return std::shared_ptr (new MWWorld::FailedAction("")); @@ -580,6 +580,9 @@ namespace MWClass if(stats.getAiSequence().isInCombat()) return std::shared_ptr(new MWWorld::FailedAction("")); + if(stats.getKnockedDown()) + return std::shared_ptr(new MWWorld::FailedAction("")); + return std::shared_ptr(new MWWorld::ActionTalk(ptr)); } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 5b03c626f..2b4e47b78 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -175,9 +175,7 @@ namespace MWClass if(isTrapped) { ptr.getCellRef().setTrap(""); - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, - "Disarm Trap", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, - MWBase::SoundManager::Play_Normal); + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f); isTrapped = false; /* diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 172c16c83..f9056b75d 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -49,8 +49,8 @@ namespace MWClass if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, - MWBase::SoundManager::Play_TypeSfx, - MWBase::SoundManager::Play_Loop); + MWSound::Type::Sfx, + MWSound::PlayMode::Loop); } bool Light::useAnim() const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 38da4887c..297442f99 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1008,11 +1008,11 @@ namespace MWClass // by default user can loot friendly actors during death animation if (canLoot && !stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // death animation is not finished, do nothing return std::shared_ptr (new MWWorld::FailedAction("")); @@ -1265,6 +1265,15 @@ namespace MWClass else { ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr); + + // Take in account armor condition + const bool hasHealth = it->getClass().hasItemHealth(*it); + if (hasHealth) + { + int armorHealth = it->getClass().getItemHealth(*it); + int armorMaxHealth = it->getClass().getItemMaxHealth(*it); + ratings[i] *= (float(armorHealth) / armorMaxHealth); + } } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 480415e38..f531779a3 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -46,8 +47,6 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwgui/dialogue.hpp" - #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" #include "../mwscript/extensions.hpp" @@ -72,16 +71,8 @@ namespace MWDialogue { mChoice = -1; mIsInChoice = false; + mGoodbye = false; mCompilerContext.setExtensions (&extensions); - - const MWWorld::Store &dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); - - MWWorld::Store::iterator it = dialogs.begin(); - for (; it != dialogs.end(); ++it) - { - mDialogueMap[Misc::StringUtils::lowerCase(it->mId)] = *it; - } } void DialogueManager::clear() @@ -113,6 +104,7 @@ namespace MWDialogue void DialogueManager::parseText (const std::string& text) { + updateActorKnownTopics(); std::vector hypertext = HyperTextParser::parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) @@ -146,17 +138,15 @@ namespace MWDialogue if (mActorKnownTopics.count( topicId )) mKnownTopics.insert( topicId ); } - - updateTopics(); } - void DialogueManager::startDialogue (const MWWorld::Ptr& actor) + bool DialogueManager::startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) { updateGlobals(); // Dialogue with dead actor (e.g. through script) should not be allowed. if (actor.getClass().getCreatureStats(actor).isDead()) - return; + return false; mLastTopic = ""; mPermanentDispositionChange = 0; @@ -164,6 +154,8 @@ namespace MWDialogue mChoice = -1; mIsInChoice = false; + mGoodbye = false; + mChoices.clear(); mActor = actor; @@ -172,13 +164,6 @@ namespace MWDialogue mActorKnownTopics.clear(); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - - // If the dialogue window was already open, keep the existing history - bool resetHistory = (!MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Dialogue)); - - win->startDialogue(actor, actor.getClass().getName (actor), resetHistory); - //greeting const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); @@ -192,9 +177,6 @@ namespace MWDialogue // Search a response (we do not accept a fallback to "Info refusal" here) if (const ESM::DialInfo *info = filter.search (*it, false)) { - //initialise the GUI - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue); - creatureStats.talkedToPlayer(); if (!info->mSound.empty()) @@ -202,30 +184,18 @@ namespace MWDialogue // TODO play sound } - // first topics update so that parseText knows the keywords to highlight - updateTopics(); - - parseText (info->mResponse); - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), "", false); + callback->addResponse("", Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript, mActor); - mLastTopic = Misc::StringUtils::lowerCase(it->mId); + mLastTopic = it->mId; - // update topics again to accommodate changes resulting from executeScript - updateTopics(); + parseText (info->mResponse); - return; + return true; } } } - - // No greetings found. The dialogue window should not be shown. - // If this is a companion, we must show the companion window directly (used by BM_bear_be_unique). - bool isCompanion = !mActor.getClass().getScript(mActor).empty() - && mActor.getRefData().getLocals().getIntVar(mActor.getClass().getScript(mActor), "companion"); - if (isCompanion) - MWBase::Environment::get().getWindowManager()->showCompanionWindow(mActor); + return false; } bool DialogueManager::compile (const std::string& cmd, std::vector& code, const MWWorld::Ptr& actor) @@ -303,7 +273,7 @@ namespace MWDialogue } } - void DialogueManager::executeTopic (const std::string& topic) + void DialogueManager::executeTopic (const std::string& topic, ResponseCallback* callback) { Filter filter (mActor, mChoice, mTalkedTo); @@ -312,13 +282,9 @@ namespace MWDialogue const ESM::Dialogue& dialogue = *dialogues.find (topic); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - const ESM::DialInfo* info = filter.search(dialogue, true); if (info) { - parseText (info->mResponse); - std::string title; if (dialogue.mType==ESM::Dialogue::Persuasion) { @@ -338,7 +304,7 @@ namespace MWDialogue title = topic; MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); + callback->addResponse(title, Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); if (dialogue.mType == ESM::Dialogue::Topic) { @@ -349,7 +315,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); + MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(topic), info->mId, mActor); break; } } @@ -357,13 +323,15 @@ namespace MWDialogue executeScript (info->mResultScript, mActor); + parseText (info->mResponse); + mLastTopic = topic; } - else - { - // no response found, print a fallback text - win->addResponse ("…", topic); - } + } + + const ESM::Dialogue *DialogueManager::searchDialogue(const std::string& id) + { + return MWBase::Environment::get().getWorld()->getStore().get().search(id); } void DialogueManager::updateGlobals() @@ -371,19 +339,16 @@ namespace MWDialogue MWBase::Environment::get().getWorld()->updateDialogueGlobals(); } - void DialogueManager::updateTopics() + void DialogueManager::updateActorKnownTopics() { updateGlobals(); - std::list keywordList; - int choice = mChoice; - mChoice = -1; mActorKnownTopics.clear(); const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); - Filter filter (mActor, mChoice, mTalkedTo); + Filter filter (mActor, -1, mTalkedTo); for (MWWorld::Store::iterator iter = dialogs.begin(); iter != dialogs.end(); ++iter) { @@ -391,92 +356,41 @@ namespace MWDialogue { if (filter.responseAvailable (*iter)) { - std::string lower = Misc::StringUtils::lowerCase(iter->mId); - mActorKnownTopics.insert (lower); - - //does the player know the topic? - if (mKnownTopics.count(lower)) - { - keywordList.push_back (iter->mId); - } + mActorKnownTopics.insert (iter->mId); } } } - // check the available services of this actor - int services = 0; - if (mActor.getTypeName() == typeid(ESM::NPC).name()) - { - MWWorld::LiveCellRef* ref = mActor.get(); - if (ref->mBase->mHasAI) - services = ref->mBase->mAiData.mServices; - } - else if (mActor.getTypeName() == typeid(ESM::Creature).name()) - { - MWWorld::LiveCellRef* ref = mActor.get(); - if (ref->mBase->mHasAI) - services = ref->mBase->mAiData.mServices; - } - - int windowServices = 0; - - if (services & ESM::NPC::Weapon - || services & ESM::NPC::Armor - || services & ESM::NPC::Clothing - || services & ESM::NPC::Books - || services & ESM::NPC::Ingredients - || services & ESM::NPC::Picks - || services & ESM::NPC::Probes - || services & ESM::NPC::Lights - || services & ESM::NPC::Apparatus - || services & ESM::NPC::RepairItem - || services & ESM::NPC::Misc) - windowServices |= MWGui::DialogueWindow::Service_Trade; - - if((mActor.getTypeName() == typeid(ESM::NPC).name() && !mActor.get()->mBase->getTransport().empty()) - || (mActor.getTypeName() == typeid(ESM::Creature).name() && !mActor.get()->mBase->getTransport().empty())) - windowServices |= MWGui::DialogueWindow::Service_Travel; - - if (services & ESM::NPC::Spells) - windowServices |= MWGui::DialogueWindow::Service_BuySpells; - - if (services & ESM::NPC::Spellmaking) - windowServices |= MWGui::DialogueWindow::Service_CreateSpells; - - if (services & ESM::NPC::Training) - windowServices |= MWGui::DialogueWindow::Service_Training; - - if (services & ESM::NPC::Enchanting) - windowServices |= MWGui::DialogueWindow::Service_Enchant; + } - if (services & ESM::NPC::Repair) - windowServices |= MWGui::DialogueWindow::Service_Repair; + std::list DialogueManager::getAvailableTopics() + { + updateActorKnownTopics(); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + std::list keywordList; - win->setServices (windowServices); + for (const std::string& topic : mActorKnownTopics) + { + //does the player know the topic? + if (mKnownTopics.count(topic)) + keywordList.push_back(topic); + } // sort again, because the previous sort was case-sensitive keywordList.sort(Misc::StringUtils::ciLess); - win->setKeywords(keywordList); - - mChoice = choice; + return keywordList; } - void DialogueManager::keywordSelected (const std::string& keyword) + void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) { if(!mIsInChoice) { - if(mDialogueMap.find(keyword) != mDialogueMap.end()) + const ESM::Dialogue* dialogue = searchDialogue(keyword); + if (dialogue && dialogue->mType == ESM::Dialogue::Topic) { - if (mDialogueMap[keyword].mType == ESM::Dialogue::Topic) - { - executeTopic (keyword); - } + executeTopic (keyword, callback); } } - - updateTopics(); } bool DialogueManager::isInChoice() const @@ -486,8 +400,6 @@ namespace MWDialogue void DialogueManager::goodbyeSelected() { - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); - // Apply disposition change to NPC's base disposition if (mActor.getClass().isNpc()) { @@ -503,38 +415,41 @@ namespace MWDialogue mTemporaryDispositionChange = 0; } - void DialogueManager::questionAnswered (int answer) + void DialogueManager::questionAnswered (int answer, ResponseCallback* callback) { mChoice = answer; - if (mDialogueMap.find(mLastTopic) != mDialogueMap.end()) + const ESM::Dialogue* dialogue = searchDialogue(mLastTopic); + if (dialogue) { Filter filter (mActor, mChoice, mTalkedTo); - if (mDialogueMap[mLastTopic].mType == ESM::Dialogue::Topic - || mDialogueMap[mLastTopic].mType == ESM::Dialogue::Greeting) + if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting) { - if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic], true)) + if (const ESM::DialInfo *info = filter.search (*dialogue, true)) { std::string text = info->mResponse; parseText (text); mChoice = -1; mIsInChoice = false; - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->clearChoices(); + mChoices.clear(); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext)); + callback->addResponse("", Interpreter::fixDefinesDialog(text, interpreterContext)); - // 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 = mDialogueMap[mLastTopic].mInfo.begin(); - iter!=mDialogueMap[mLastTopic].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 (mLastTopic, info->mId, mActor); - break; + if (iter->mId == info->mId) + { + MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor); + break; + } } } @@ -544,32 +459,38 @@ namespace MWDialogue { mChoice = -1; mIsInChoice = false; - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->clearChoices(); + mChoices.clear(); } } } - updateTopics(); + updateActorKnownTopics(); } - void DialogueManager::askQuestion (const std::string& question, int choice) + void DialogueManager::addChoice (const std::string& text, int choice) { mIsInChoice = true; - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->addChoice(question, choice); + mChoices.push_back(std::make_pair(text, choice)); } - void DialogueManager::goodbye() + const std::vector >& DialogueManager::getChoices() { - mIsInChoice = true; + return mChoices; + } - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + bool DialogueManager::isGoodbye() + { + return mGoodbye; + } - win->goodbye(); + void DialogueManager::goodbye() + { + mIsInChoice = false; + mGoodbye = true; } - void DialogueManager::persuade(int type) + void DialogueManager::persuade(int type, ResponseCallback* callback) { bool success; float temp, perm; @@ -618,7 +539,7 @@ namespace MWDialogue text = "Bribe"; } - executeTopic (text + (success ? " Success" : " Fail")); + executeTopic (text + (success ? " Success" : " Fail"), callback); } int DialogueManager::getTemporaryDispositionChange() const @@ -631,7 +552,7 @@ namespace MWDialogue mTemporaryDispositionChange += delta; } - bool DialogueManager::checkServiceRefused() + bool DialogueManager::checkServiceRefused(ResponseCallback* callback) { Filter filter (mActor, mChoice, mTalkedTo); @@ -639,7 +560,6 @@ namespace MWDialogue MWBase::Environment::get().getWorld()->getStore().get(); const ESM::Dialogue& dialogue = *dialogues.find ("Service Refusal"); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); std::vector infos = filter.list (dialogue, false, false, true); if (!infos.empty()) @@ -653,8 +573,7 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), - gmsts.find ("sServiceRefusal")->getString()); + callback->addResponse(gmsts.find ("sServiceRefusal")->getString(), Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript, mActor); return true; @@ -677,6 +596,12 @@ namespace MWDialogue return; } + if (actor.getClass().getCreatureStats(actor).getKnockedDown()) + { + // Unconscious actors can not speak + return; + } + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Dialogue *dial = store.get().find(topic); @@ -812,7 +737,7 @@ namespace MWDialogue if (actor == mActor && !mLastTopic.empty()) { MWBase::Environment::get().getJournal()->removeLastAddedTopicResponse( - mLastTopic, actor.getClass().getName(actor)); + Misc::StringUtils::lowerCase(mLastTopic), actor.getClass().getName(actor)); } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 12fbff796..a859b020c 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -22,14 +23,13 @@ namespace MWDialogue { class DialogueManager : public MWBase::DialogueManager { - std::map mDialogueMap; - std::set mKnownTopics;// Those are the topics the player knows. + std::set mKnownTopics;// Those are the topics the player knows. // Modified faction reactions. > typedef std::map > ModFactionReactionMap; ModFactionReactionMap mChangedFactionReaction; - std::set mActorKnownTopics; + std::set mActorKnownTopics; Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; @@ -42,19 +42,24 @@ namespace MWDialogue int mChoice; std::string mLastTopic; // last topic ID, lowercase bool mIsInChoice; + bool mGoodbye; + + std::vector > mChoices; float mTemporaryDispositionChange; float mPermanentDispositionChange; void parseText (const std::string& text); - void updateTopics(); + void updateActorKnownTopics(); void updateGlobals(); bool compile (const std::string& cmd, std::vector& code, const MWWorld::Ptr& actor); void executeScript (const std::string& script, const MWWorld::Ptr& actor); - void executeTopic (const std::string& topic); + void executeTopic (const std::string& topic, ResponseCallback* callback); + + const ESM::Dialogue* searchDialogue(const std::string& id); public: @@ -64,7 +69,9 @@ namespace MWDialogue virtual bool isInChoice() const; - virtual void startDialogue (const MWWorld::Ptr& actor); + virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback); + + std::list getAvailableTopics(); virtual void addTopic (const std::string& topic); @@ -79,20 +86,23 @@ namespace MWDialogue End of tes3mp addition */ - virtual void askQuestion (const std::string& question,int choice); + virtual void addChoice (const std::string& text,int choice); + const std::vector >& getChoices(); + + virtual bool isGoodbye(); virtual void goodbye(); - virtual bool checkServiceRefused (); + virtual bool checkServiceRefused (ResponseCallback* callback); virtual void say(const MWWorld::Ptr &actor, const std::string &topic); //calbacks for the GUI - virtual void keywordSelected (const std::string& keyword); + virtual void keywordSelected (const std::string& keyword, ResponseCallback* callback); virtual void goodbyeSelected(); - virtual void questionAnswered (int answer); + virtual void questionAnswered (int answer, ResponseCallback* callback); - virtual void persuade (int type); + virtual void persuade (int type, ResponseCallback* callback); virtual int getTemporaryDispositionChange () const; /// @note This change is temporary and gets discarded when dialogue ends. diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 67bb53e71..e8757e4a3 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -193,7 +193,8 @@ bool MWDialogue::Filter::testFunctionLocal(const MWDialogue::SelectWrapper& sele return false; // shouldn't happen, we checked that variable has a type above, so must exist const MWScript::Locals& locals = mActor.getRefData().getLocals(); - + if (locals.isEmpty()) + return select.selectCompare(0); switch (type) { case 's': return select.selectCompare (static_cast (locals.mShorts[index])); diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 80e4932be..26c04a7bc 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -95,11 +95,16 @@ namespace MWDialogue void Journal::addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) { - // bail out of we already have heard this... + // bail out if we already have heard this... std::string infoId = JournalEntry::idFromIndex (id, index); for (TEntryIter i = mJournal.begin (); i != mJournal.end (); ++i) if (i->mTopic == id && i->mInfoId == infoId) + { + if (getJournalIndex(id) < index) + setJournalIndex(id, index); + MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); return; + } StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 642dfe613..500115f83 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -1,6 +1,8 @@ #include "alchemywindow.hpp" #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -19,6 +21,7 @@ #include "sortfilteritemmodel.hpp" #include "itemview.hpp" #include "itemwidget.hpp" +#include "widgets.hpp" namespace MWGui { @@ -54,12 +57,19 @@ namespace MWGui mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); + mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); + center(); } + void AlchemyWindow::onAccept(MyGUI::EditBox* sender) + { + onCreateButtonClicked(sender); + } + void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy); } void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) @@ -112,8 +122,9 @@ namespace MWGui update(); } - void AlchemyWindow::open() + void AlchemyWindow::onOpen() { + mAlchemy->clear(); mAlchemy->setAlchemist (MWMechanics::getPlayer()); InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer()); @@ -138,12 +149,8 @@ namespace MWGui } update(); - } - void AlchemyWindow::exit() { - mAlchemy->clear(); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Inventory); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); } void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index e1f48d4a3..d1e54241a 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -5,7 +5,6 @@ #include "../mwmechanics/alchemy.hpp" -#include "widgets.hpp" #include "windowbase.hpp" namespace MWMechanics @@ -24,8 +23,9 @@ namespace MWGui public: AlchemyWindow(); - virtual void open(); - virtual void exit(); + virtual void onOpen(); + + void onResChange(int, int) { center(); } private: std::string mSuggestedPotionName; @@ -43,6 +43,7 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* _sender); void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); + void onAccept(MyGUI::EditBox*); void onSelectedItem(int index); diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 69fb2c462..c1867541b 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -3,11 +3,14 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" + #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "widgets.hpp" @@ -63,13 +66,20 @@ namespace MWGui okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } - void BirthDialog::open() + void BirthDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); updateBirths(); updateSpells(); - } + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); + + // Show the current birthsign by default + const std::string &signId = + MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + if (!signId.empty()) + setBirthId(signId); + } void BirthDialog::setBirthId(const std::string &birthId) { @@ -244,6 +254,11 @@ namespace MWGui } } } - } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellArea->setVisibleVScroll(false); + mSpellArea->setCanvasSize(MyGUI::IntSize(mSpellArea->getWidth(), std::max(mSpellArea->getHeight(), coord.top))); + mSpellArea->setVisibleVScroll(true); + mSpellArea->setViewOffset(MyGUI::IntPoint(0, 0)); + } } diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index 0a84bb4e9..86af14286 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -20,7 +20,9 @@ namespace MWGui void setBirthId(const std::string &raceId); void setNextButtonShow(bool shown); - virtual void open(); + virtual void onOpen(); + + bool exit() { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; @@ -47,7 +49,7 @@ namespace MWGui void updateSpells(); MyGUI::ListBox* mBirthList; - MyGUI::Widget* mSpellArea; + MyGUI::ScrollView* mSpellArea; MyGUI::ImageBox* mBirthImage; std::vector mSpellItems; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 137594076..5a9237cea 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -16,6 +16,7 @@ class BookPageImpl; static bool ucsSpace (int codePoint); static bool ucsLineBreak (int codePoint); +static bool ucsCarriageReturn (int codePoint); static bool ucsBreakingSpace (int codePoint); struct BookTypesetter::Style { virtual ~Style () {} }; @@ -280,7 +281,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter style.mActiveColour = fontColour; style.mNormalColour = fontColour; style.mInteractiveId = 0; - + return &style; } @@ -342,7 +343,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter writeImpl (static_cast (style), begin_, end_); } - + void lineBreak (float margin) { assert (margin == 0); //TODO: figure out proper behavior here... @@ -352,7 +353,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter mRun = NULL; mLine = NULL; } - + void sectionBreak (int margin) { add_partial_text(); @@ -1188,6 +1189,9 @@ public: { Utf8Stream::UnicodeChar code_point = stream.consume (); + if (ucsCarriageReturn (code_point)) + continue; + if (!ucsSpace (code_point)) glyphStream.emitGlyph (code_point); else @@ -1331,6 +1335,11 @@ static bool ucsLineBreak (int codePoint) return codePoint == '\n'; } +static bool ucsCarriageReturn (int codePoint) +{ + return codePoint == '\r'; +} + static bool ucsSpace (int codePoint) { switch (codePoint) diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index b3296e7a7..c18548dad 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -1,6 +1,7 @@ #include "bookwindow.hpp" #include +#include #include @@ -11,6 +12,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/class.hpp" #include "formatting.hpp" @@ -51,6 +53,11 @@ namespace MWGui mRightPage->setNeedMouseFocus(true); mRightPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); + mNextPageButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); + mPrevPageButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); + mTakeButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); + mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); + if (mNextPageButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge @@ -74,15 +81,16 @@ namespace MWGui mPages.clear(); } - void BookWindow::openBook (MWWorld::Ptr book, bool showTakeButton) + void BookWindow::setPtr (const MWWorld::Ptr& book) { mBook = book; + MWWorld::Ptr player = MWMechanics::getPlayer(); + bool showTakeButton = book.getContainerStore() != &player.getClass().getContainerStore(player); + clearPages(); mCurrentPage = 0; - MWBase::Environment::get().getWindowManager()->playSound("book open"); - MWWorld::LiveCellRef *ref = mBook.get(); Formatting::BookFormatter formatter; @@ -92,14 +100,8 @@ namespace MWGui updatePages(); setTakeButtonShow(showTakeButton); - } - void BookWindow::exit() - { - // no 3d sounds because the object could be in a container. - MWBase::Environment::get().getWindowManager()->playSound("book close"); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); } void BookWindow::setTakeButtonShow(bool show) @@ -108,6 +110,14 @@ namespace MWGui mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } + void BookWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) + { + if (key == MyGUI::KeyCode::ArrowUp) + prevPage(); + else if (key == MyGUI::KeyCode::ArrowDown) + nextPage(); + } + void BookWindow::setInventoryAllowed(bool allowed) { mTakeButtonAllowed = allowed; @@ -116,7 +126,7 @@ namespace MWGui void BookWindow::onCloseButtonClicked (MyGUI::Widget* sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } void BookWindow::onTakeButtonClicked (MyGUI::Widget* sender) @@ -144,20 +154,16 @@ namespace MWGui mLeftPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 1) ); mRightPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 2) ); - //If it is the last page, hide the button "Next Page" - if ( (mCurrentPage+1)*2 == mPages.size() - || (mCurrentPage+1)*2 == mPages.size() + 1) - { - mNextPageButton->setVisible(false); - } else { - mNextPageButton->setVisible(true); - } - //If it is the fist page, hide the button "Prev Page" - if (mCurrentPage == 0) { - mPrevPageButton->setVisible(false); - } else { - mPrevPageButton->setVisible(true); - } + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + bool nextPageVisible = (mCurrentPage+1)*2 < mPages.size(); + mNextPageButton->setVisible(nextPageVisible); + bool prevPageVisible = mCurrentPage != 0; + mPrevPageButton->setVisible(prevPageVisible); + + if (focus == mNextPageButton && !nextPageVisible && prevPageVisible) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mPrevPageButton); + else if (focus == mPrevPageButton && !prevPageVisible && nextPageVisible) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNextPageButton); if (mPages.empty()) return; diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index 881b9997c..2459c3464 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -14,11 +14,11 @@ namespace MWGui public: BookWindow(); - virtual void exit(); - - void openBook(MWWorld::Ptr book, bool showTakeButton); + void setPtr(const MWWorld::Ptr& book); void setInventoryAllowed(bool allowed); + void onResChange(int, int) { center(); } + protected: void onNextPageButtonClicked (MyGUI::Widget* sender); void onPrevPageButtonClicked (MyGUI::Widget* sender); @@ -27,6 +27,8 @@ namespace MWGui void onMouseWheel(MyGUI::Widget* _sender, int _rel); void setTakeButtonShow(bool show); + void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); + void nextPage(); void prevPage(); diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index e1f5a165e..4d2a15c82 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -7,6 +7,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" + +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/esmstore.hpp" #include "tooltips.hpp" @@ -125,13 +128,22 @@ namespace MWGui okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } - void PickClassDialog::open() + void PickClassDialog::onOpen() { - WindowModal::open (); + WindowModal::onOpen (); updateClasses(); updateStats(); - } + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); + // Show the current class by default + MWWorld::Ptr player = MWMechanics::getPlayer(); + + const std::string &classId = + player.get()->mBase->mClass; + + if (!classId.empty()) + setClassId(classId); + } void PickClassDialog::setClassId(const std::string &classId) { @@ -341,9 +353,9 @@ namespace MWGui } } - void InfoBoxDialog::open() + void InfoBoxDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); // Fix layout layoutVertically(mTextBox, 4); layoutVertically(mButtonBar, 6); @@ -730,9 +742,10 @@ namespace MWGui exit(); } - void SelectSpecializationDialog::exit() + bool SelectSpecializationDialog::exit() { eventCancel(); + return true; } /* SelectAttributeDialog */ @@ -778,9 +791,10 @@ namespace MWGui exit(); } - void SelectAttributeDialog::exit() + bool SelectAttributeDialog::exit() { eventCancel(); + return true; } @@ -869,9 +883,10 @@ namespace MWGui exit(); } - void SelectSkillDialog::exit() + bool SelectSkillDialog::exit() { eventCancel(); + return true; } /* DescriptionDialog */ diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index 8206d6b03..3a2573fd4 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -21,7 +21,9 @@ namespace MWGui std::string getText() const; void setButtons(ButtonList &buttons); - virtual void open(); + virtual void onOpen(); + + bool exit() { return false; } // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; @@ -67,6 +69,8 @@ namespace MWGui std::string getClassId() const; void setClassId(const std::string &classId); + bool exit() { return false; } + // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; @@ -100,7 +104,9 @@ namespace MWGui void setClassId(const std::string &classId); void setNextButtonShow(bool shown); - virtual void open(); + virtual void onOpen(); + + bool exit() { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; @@ -142,7 +148,7 @@ namespace MWGui SelectSpecializationDialog(); ~SelectSpecializationDialog(); - virtual void exit(); + virtual bool exit(); ESM::Class::Specialization getSpecializationId() const { return mSpecializationId; } @@ -175,7 +181,7 @@ namespace MWGui SelectAttributeDialog(); ~SelectAttributeDialog(); - virtual void exit(); + virtual bool exit(); ESM::Attribute::AttributeID getAttributeId() const { return mAttributeId; } @@ -206,7 +212,7 @@ namespace MWGui SelectSkillDialog(); ~SelectSkillDialog(); - virtual void exit(); + virtual bool exit(); ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } @@ -262,6 +268,8 @@ namespace MWGui CreateClassDialog(); virtual ~CreateClassDialog(); + bool exit() { return false; } + std::string getName() const; std::string getDescription() const; ESM::Class::Specialization getSpecializationId() const; diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index fc4a98489..ad71be7df 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -13,6 +13,7 @@ #include "companionitemmodel.hpp" #include "draganddrop.hpp" #include "countdialog.hpp" +#include "widgets.hpp" namespace { @@ -103,7 +104,7 @@ void CompanionWindow::onBackgroundSelected() } } -void CompanionWindow::openCompanion(const MWWorld::Ptr& npc) +void CompanionWindow::setPtr(const MWWorld::Ptr& npc) { mPtr = npc; updateEncumbranceBar(); @@ -116,8 +117,9 @@ void CompanionWindow::openCompanion(const MWWorld::Ptr& npc) setTitle(npc.getClass().getName(npc)); } -void CompanionWindow::onFrame() +void CompanionWindow::onFrame(float dt) { + checkReferenceAvailable(); updateEncumbranceBar(); } @@ -139,10 +141,11 @@ void CompanionWindow::updateEncumbranceBar() void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { - exit(); + if (exit()) + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } -void CompanionWindow::exit() +bool CompanionWindow::exit() { if (mModel && mModel->hasProfit(mPtr) && getProfit(mPtr) < 0) { @@ -151,9 +154,9 @@ void CompanionWindow::exit() buttons.push_back("#{sCompanionWarningButtonTwo}"); mMessageBoxManager->createInteractiveMessageBox("#{sCompanionWarningMessage}", buttons); mMessageBoxManager->eventButtonPressed += MyGUI::newDelegate(this, &CompanionWindow::onMessageBoxButtonClicked); + return false; } - else - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); + return true; } void CompanionWindow::onMessageBoxButtonClicked(int button) @@ -162,7 +165,7 @@ void CompanionWindow::onMessageBoxButtonClicked(int button) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); // Important for Calvus' contract script to work properly - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } } diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index d37f0c4e4..8ca350617 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -1,12 +1,16 @@ #ifndef OPENMW_MWGUI_COMPANIONWINDOW_H #define OPENMW_MWGUI_COMPANIONWINDOW_H -#include "widgets.hpp" #include "windowbase.hpp" #include "referenceinterface.hpp" namespace MWGui { + namespace Widgets + { + class MWDynamicStat; + } + class MessageBoxManager; class ItemView; class DragAndDrop; @@ -18,12 +22,13 @@ namespace MWGui public: CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager); - virtual void exit(); + virtual bool exit(); virtual void resetReference(); - void openCompanion(const MWWorld::Ptr& npc); - void onFrame (); + void setPtr(const MWWorld::Ptr& npc); + void onFrame (float dt); + void clear() { resetReference(); } private: ItemView* mItemView; diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 33f8dbe3e..65b079d85 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -3,6 +3,9 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + namespace MWGui { ConfirmationDialog::ConfirmationDialog() : @@ -16,14 +19,6 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked); } - void ConfirmationDialog::askForConfirmation(const std::string& message, const std::string& confirmMessage, const std::string& cancelMessage) - { - mCancelButton->setCaptionWithReplacing(cancelMessage); - mOkButton->setCaptionWithReplacing(confirmMessage); - - askForConfirmation(message); - } - void ConfirmationDialog::askForConfirmation(const std::string& message) { setVisible(true); @@ -38,18 +33,21 @@ namespace MWGui mMessage->setSize(mMessage->getWidth(), mMessage->getTextSize().height + 24); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); + center(); } - void ConfirmationDialog::exit() + bool ConfirmationDialog::exit() { - setVisible(false); - eventCancelClicked(); + return true; } void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) { + setVisible(false); + exit(); } diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index 745c7a1a5..ab52549ec 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -10,8 +10,7 @@ namespace MWGui public: ConfirmationDialog(); void askForConfirmation(const std::string& message); - void askForConfirmation(const std::string& message, const std::string& confirmMessage, const std::string& cancelMessage); - virtual void exit(); + virtual bool exit(); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index aeff7dc39..e8ac33f6d 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -145,25 +145,13 @@ namespace MWGui mCompilerContext.setExtensions (&mExtensions); } - void Console::open() + void Console::onOpen() { // Give keyboard focus to the combo box whenever the console is // turned on MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } - void Console::close() - { - // Apparently, hidden widgets can retain key focus - // Remove for MyGUI 3.2.2 - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); - } - - void Console::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Console); - } - void Console::setFont(const std::string &fntName) { mHistory->setFontName(fntName); @@ -227,6 +215,11 @@ namespace MWGui } } + void Console::clear() + { + resetReference(); + } + void Console::keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char) diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 98e46a559..bbff34c8d 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -39,10 +39,7 @@ namespace MWGui Console(int w, int h, bool consoleOnlyScripts); - virtual void open(); - virtual void close(); - - virtual void exit(); + virtual void onOpen(); void setFont(const std::string &fntName); @@ -63,6 +60,8 @@ namespace MWGui void executeFile (const std::string& path); + void clear(); + virtual void resetReference (); protected: diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 81fad8d48..31848c62d 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -22,11 +22,10 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/pickpocket.hpp" #include "../mwmechanics/creaturestats.hpp" #include "countdialog.hpp" @@ -35,6 +34,7 @@ #include "itemview.hpp" #include "itemwidget.hpp" #include "inventoryitemmodel.hpp" +#include "containeritemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "pickpocketitemmodel.hpp" #include "draganddrop.hpp" @@ -45,7 +45,6 @@ namespace MWGui ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) : WindowBase("openmw_container_window.layout") , mDragAndDrop(dragAndDrop) - , mPickpocketDetected(false) , mSortModel(NULL) , mModel(NULL) , mSelectedItem(-1) @@ -60,7 +59,6 @@ namespace MWGui mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); - mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ContainerWindow::onKeyPressed); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); setCoord(200,0,600,300); @@ -68,10 +66,9 @@ namespace MWGui void ContainerWindow::onItemSelected(int index) { - if (mDragAndDrop->mIsOnDragAndDrop) + if (mDragAndDrop->mIsOnDragAndDrop && mModel) { - if (mModel && mModel->allowedToInsertItems()) - dropItem(); + dropItem(); return; } @@ -150,90 +147,90 @@ namespace MWGui void ContainerWindow::dropItem() { - if (mPtr.getTypeName() == typeid(ESM::Container).name()) - { - // check container organic flag - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mFlags & ESM::Container::Organic) - { - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sContentsMessage2}"); - return; - } - - // check that we don't exceed container capacity - MWWorld::Ptr item = mDragAndDrop->mItem.mBase; - float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; - if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); - return; - } - } + bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount); /* Start of tes3mp addition Send an ID_CONTAINER packet every time an item is dropped in a container */ - mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); - worldEvent->reset(); - worldEvent->cell = *mPtr.getCell()->getCell(); - worldEvent->action = mwmp::BaseEvent::ADD; + if (success) + { + mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); + worldEvent->reset(); + worldEvent->cell = *mPtr.getCell()->getCell(); + worldEvent->action = mwmp::BaseEvent::ADD; - mwmp::WorldObject worldObject; - worldObject.refId = mPtr.getCellRef().getRefId(); - worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; - worldObject.mpNum = mPtr.getCellRef().getMpNum(); + mwmp::WorldObject worldObject; + worldObject.refId = mPtr.getCellRef().getRefId(); + worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; + worldObject.mpNum = mPtr.getCellRef().getMpNum(); - MWWorld::Ptr itemPtr = mDragAndDrop->mItem.mBase; + MWWorld::Ptr itemPtr = mDragAndDrop->mItem.mBase; - mwmp::ContainerItem containerItem; - containerItem.refId = itemPtr.getCellRef().getRefId(); - - // Make sure we get the drag and drop count, not the count of the original item - containerItem.count = mDragAndDrop->mDraggedCount; + mwmp::ContainerItem containerItem; + containerItem.refId = itemPtr.getCellRef().getRefId(); + + // Make sure we get the drag and drop count, not the count of the original item + containerItem.count = mDragAndDrop->mDraggedCount; - containerItem.charge = itemPtr.getCellRef().getCharge(); + containerItem.charge = itemPtr.getCellRef().getCharge(); - worldObject.containerItems.push_back(containerItem); - worldEvent->addObject(worldObject); + worldObject.containerItems.push_back(containerItem); + worldEvent->addObject(worldObject); - mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); - mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); + mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); + mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s\n- item: %s, %i", - worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str(), - containerItem.refId.c_str(), containerItem.count); + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s\n- item: %s, %i", + worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str(), + containerItem.refId.c_str(), containerItem.count); + } /* End of tes3mp addition */ - mDragAndDrop->drop(mModel, mItemView); + if (success) + mDragAndDrop->drop(mModel, mItemView); } void ContainerWindow::onBackgroundSelected() { - if (mDragAndDrop->mIsOnDragAndDrop && mModel && mModel->allowedToInsertItems()) + if (mDragAndDrop->mIsOnDragAndDrop && mModel) dropItem(); } - void ContainerWindow::openContainer(const MWWorld::Ptr& container, bool loot) + void ContainerWindow::setPtr(const MWWorld::Ptr& container) { - mwmp::Main::get().getCellController()->openContainer(container, loot); + /* + Start of tes3mp addition + + Mark this container as open for multiplayer logic purposes + */ + mwmp::Main::get().getCellController()->openContainer(container); + /* + End of tes3mp addition + */ - mPickpocketDetected = false; mPtr = container; - if (mPtr.getTypeName() == typeid(ESM::NPC).name() && !loot) + bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); + + if (mPtr.getClass().hasInventoryStore(mPtr)) { - // we are stealing stuff - MWWorld::Ptr player = MWMechanics::getPlayer(); - mModel = new PickpocketItemModel(player, new InventoryItemModel(container), - !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); + if (mPtr.getClass().isNpc() && !loot) + { + // we are stealing stuff + mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container), + !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); + } + else + mModel = new InventoryItemModel(container); } else - mModel = new InventoryItemModel(container); + { + mModel = new ContainerItemModel(container); + } mDisposeCorpseButton->setVisible(loot); @@ -247,14 +244,6 @@ namespace MWGui setTitle(container.getClass().getName(container)); } - void ContainerWindow::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) - { - if (_key == MyGUI::KeyCode::Space) - onCloseButtonClicked(mCloseButton); - if (_key == MyGUI::KeyCode::Return || _key == MyGUI::KeyCode::NumpadEnter) - onTakeAllButtonClicked(mTakeButton); - } - void ContainerWindow::resetReference() { ReferenceInterface::resetReference(); @@ -263,97 +252,99 @@ namespace MWGui mSortModel = NULL; } - void ContainerWindow::close() + void ContainerWindow::onClose() { + /* + Start of tes3mp addition + + Mark this container as closed for multiplayer logic purposes + */ mwmp::Main::get().getCellController()->closeContainer(mPtr); - WindowBase::close(); - - if (dynamic_cast(mModel) - // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened) - && !MWBase::Environment::get().getWindowManager()->containsMode(GM_Container) - // If it was already detected while taking an item, no need to check now - && !mPickpocketDetected - ) - { - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::Pickpocket pickpocket(player, mPtr); - if (pickpocket.finish()) - { - MWBase::Environment::get().getMechanicsManager()->commitCrime( - player, mPtr, MWBase::MechanicsManager::OT_Pickpocket, 0, true); - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - mPickpocketDetected = true; - return; - } - } - } + /* + End of tes3mp addition + */ - void ContainerWindow::exit() - { - if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); - } + WindowBase::onClose(); + + if (mModel) + mModel->onClose(); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); } void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) { - if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) + if(mDragAndDrop != NULL && mDragAndDrop->mIsOnDragAndDrop) + return; + + // transfer everything into the player's inventory + ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); + mModel->update(); + + // unequip all items to avoid unequipping/reequipping + if (mPtr.getClass().hasInventoryStore(mPtr)) { - // transfer everything into the player's inventory - ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); - mModel->update(); + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); for (size_t i=0; igetItemCount(); ++i) { - if (i==0) - { - // play the sound of the first object - MWWorld::Ptr item = mModel->getItem(i).mBase; - std::string sound = item.getClass().getUpSoundId(item); - MWBase::Environment::get().getWindowManager()->playSound(sound); - } - const ItemStack& item = mModel->getItem(i); + if (invStore.isEquipped(item.mBase) == false) + continue; - if (!onTakeItem(item, item.mCount)) - break; + invStore.unequipItem(item.mBase, mPtr); + } + } - mModel->moveItem(item, item.mCount, playerModel); + mModel->update(); + + for (size_t i=0; igetItemCount(); ++i) + { + if (i==0) + { + // play the sound of the first object + MWWorld::Ptr item = mModel->getItem(i).mBase; + std::string sound = item.getClass().getUpSoundId(item); + MWBase::Environment::get().getWindowManager()->playSound(sound); } - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); + const ItemStack& item = mModel->getItem(i); - /* - Start of tes3mp addition + if (!onTakeItem(item, item.mCount)) + break; - Send an ID_CONTAINER packet every time the Take All button is used on - a container - */ - mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); - worldEvent->reset(); - worldEvent->cell = *mPtr.getCell()->getCell(); - worldEvent->action = mwmp::BaseEvent::SET; + mModel->moveItem(item, item.mCount, playerModel); + } - mwmp::WorldObject worldObject; - worldObject.refId = mPtr.getCellRef().getRefId(); - worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; - worldObject.mpNum = mPtr.getCellRef().getMpNum(); - worldEvent->addObject(worldObject); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); - mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); - mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); + /* + Start of tes3mp addition - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s", - worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str()); - /* - End of tes3mp addition - */ - } + Send an ID_CONTAINER packet every time the Take All button is used on + a container + */ + mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); + worldEvent->reset(); + worldEvent->cell = *mPtr.getCell()->getCell(); + worldEvent->action = mwmp::BaseEvent::SET; + + mwmp::WorldObject worldObject; + worldObject.refId = mPtr.getCellRef().getRefId(); + worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; + worldObject.mpNum = mPtr.getCellRef().getMpNum(); + worldEvent->addObject(worldObject); + + mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); + mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); + + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s", + worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str()); + /* + End of tes3mp addition + */ } void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender) @@ -394,33 +385,7 @@ namespace MWGui bool ContainerWindow::onTakeItem(const ItemStack &item, int count) { - MWWorld::Ptr player = MWMechanics::getPlayer(); - // TODO: move to ItemModels - if (dynamic_cast(mModel) - && !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()) - { - MWMechanics::Pickpocket pickpocket(player, mPtr); - if (pickpocket.pick(item.mBase, count)) - { - int value = item.mBase.getClass().getValue(item.mBase) * count; - MWBase::Environment::get().getMechanicsManager()->commitCrime( - player, mPtr, MWBase::MechanicsManager::OT_Theft, value, true); - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - mPickpocketDetected = true; - return false; - } - else - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); - } - else - { - // Looting a dead corpse is considered OK - if (mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead()) - return true; - else - MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, mPtr, count); - } - return true; + return mModel->onTakeItem(item.mBase, count); } } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 520bce9d9..cf02d165d 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -33,18 +33,17 @@ namespace MWGui public: ContainerWindow(DragAndDrop* dragAndDrop); - void openContainer(const MWWorld::Ptr& container, bool loot=false); - virtual void close(); + void setPtr(const MWWorld::Ptr& container); + virtual void onClose(); + void clear() { resetReference(); } - virtual void resetReference(); + void onFrame(float dt) { checkReferenceAvailable(); } - virtual void exit(); + virtual void resetReference(); private: DragAndDrop* mDragAndDrop; - bool mPickpocketDetected; - MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; ItemModel* mModel; @@ -61,7 +60,6 @@ namespace MWGui void onCloseButtonClicked(MyGUI::Widget* _sender); void onTakeAllButtonClicked(MyGUI::Widget* _sender); void onDisposeCorpseButtonClicked(MyGUI::Widget* sender); - void onKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char); /// @return is taking the item allowed? bool onTakeItem(const ItemStack& item, int count); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index d62cd16b0..24e7f8d00 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -2,11 +2,18 @@ #include +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/actorutil.hpp" namespace { @@ -47,6 +54,19 @@ ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) mItemSources.push_back(source); } +bool ContainerItemModel::allowedToUseItems() const +{ + if (mItemSources.size() == 0) + return true; + + MWWorld::Ptr ptr = MWMechanics::getPlayer(); + MWWorld::Ptr victim; + + // Check if the player is allowed to use items from opened container + MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); + return mm->isAllowedToUse(ptr, mItemSources[0], victim); +} + ItemStack ContainerItemModel::getItem (ModelIndex index) { if (index < 0) @@ -126,6 +146,9 @@ void ContainerItemModel::update() for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { + if (!(*it).getClass().showsInInventory(*it)) + continue; + std::vector::iterator itemStack = mItems.begin(); for (; itemStack != mItems.end(); ++itemStack) { @@ -166,5 +189,51 @@ void ContainerItemModel::update() } } } +bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count) +{ + if (mItemSources.empty()) + return false; + + MWWorld::Ptr target = mItemSources[0]; + + if (target.getTypeName() != typeid(ESM::Container).name()) + return true; + + // check container organic flag + MWWorld::LiveCellRef* ref = target.get(); + if (ref->mBase->mFlags & ESM::Container::Organic) + { + MWBase::Environment::get().getWindowManager()-> + messageBox("#{sContentsMessage2}"); + return false; + } + + // check that we don't exceed container capacity + float weight = item.getClass().getWeight(item) * count; + if (target.getClass().getCapacity(target) < target.getClass().getEncumbrance(target) + weight) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); + return false; + } + + return true; +} + +bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count) +{ + if (mItemSources.empty()) + return false; + + MWWorld::Ptr target = mItemSources[0]; + + // Looting a dead corpse is considered OK + if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) + return true; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, target, count); + + return true; +} } diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 7ced6ae34..e8d1c5328 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -17,6 +17,11 @@ namespace MWGui ContainerItemModel (const MWWorld::Ptr& source); + virtual bool allowedToUseItems() const; + + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + virtual ItemStack getItem (ModelIndex index); virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index 03cf1cab6..cf058caac 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -56,19 +56,9 @@ namespace MWGui mItemEdit->setValue(maxCount); } - void CountDialog::cancel() //Keeping this here as I don't know if anything else relies on it. - { - exit(); - } - - void CountDialog::exit() - { - setVisible(false); - } - void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender) { - cancel(); + setVisible(false); } void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp index 7014b5fad..766612f68 100644 --- a/apps/openmw/mwgui/countdialog.hpp +++ b/apps/openmw/mwgui/countdialog.hpp @@ -15,8 +15,6 @@ namespace MWGui public: CountDialog(); void openCountDialog(const std::string& item, const std::string& message, const int maxCount); - void cancel(); - virtual void exit(); typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index f478dad7e..14bbe81ef 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -20,7 +22,6 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" -#include "widgets.hpp" #include "bookpage.hpp" #include "textcolours.hpp" @@ -29,8 +30,34 @@ namespace MWGui { - PersuasionDialog::PersuasionDialog() + class ResponseCallback : public MWBase::DialogueManager::ResponseCallback + { + public: + ResponseCallback(DialogueWindow* win, bool needMargin=true) + : mWindow(win) + , mNeedMargin(needMargin) + { + + } + + void addResponse(const std::string& title, const std::string& text) + { + mWindow->addResponse(title, text, mNeedMargin); + } + + void updateTopics() + { + mWindow->updateTopics(); + } + + private: + DialogueWindow* mWindow; + bool mNeedMargin; + }; + + PersuasionDialog::PersuasionDialog(ResponseCallback* callback) : WindowModal("openmw_persuasion_dialog.layout") + , mCallback(callback) { getWidget(mCancelButton, "CancelButton"); getWidget(mAdmireButton, "AdmireButton"); @@ -52,7 +79,7 @@ namespace MWGui void PersuasionDialog::onCancel(MyGUI::Widget *sender) { - exit(); + setVisible(false); } void PersuasionDialog::onPersuade(MyGUI::Widget *sender) @@ -68,14 +95,14 @@ namespace MWGui else /*if (sender == mBribe1000Button)*/ type = MWBase::MechanicsManager::PT_Bribe1000; - MWBase::Environment::get().getDialogueManager()->persuade(type); + MWBase::Environment::get().getDialogueManager()->persuade(type, mCallback.get()); + mCallback->updateTopics(); setVisible(false); } - void PersuasionDialog::open() + void PersuasionDialog::onOpen() { - WindowModal::open(); center(); MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -86,11 +113,12 @@ namespace MWGui mBribe1000Button->setEnabled (playerGold >= 1000); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + WindowModal::onOpen(); } - void PersuasionDialog::exit() + MyGUI::Widget* PersuasionDialog::getDefaultKeyFocus() { - setVisible(false); + return mAdmireButton; } // -------------------------------------------------------------------------------------------------- @@ -219,33 +247,31 @@ namespace MWGui void Choice::activated() { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); - MWBase::Environment::get().getDialogueManager()->questionAnswered(mChoiceId); + eventChoiceActivated(mChoiceId); } void Topic::activated() { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); - MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(mTopicId)); + eventTopicActivated(mTopicId); } void Goodbye::activated() { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + eventActivated(); } // -------------------------------------------------------------------------------------------------- DialogueWindow::DialogueWindow() : WindowBase("openmw_dialogue_window.layout") - , mServices(0) - , mEnabled(false) + , mIsCompanion(false) , mGoodbye(false) - , mPersuasionDialog() + , mPersuasionDialog(new ResponseCallback(this)) + , mCallback(new ResponseCallback(this)) + , mGreetingCallback(new ResponseCallback(this, false)) { // Centre dialog center(); @@ -257,11 +283,10 @@ namespace MWGui //Topics list getWidget(mTopicsList, "TopicsList"); - mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); + mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectListItem); - MyGUI::Button* byeButton; - getWidget(byeButton, "ByeButton"); - byeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked); + getWidget(mGoodbyeButton, "ByeButton"); + mGoodbyeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked); getWidget(mDispositionBar, "Disposition"); getWidget(mDispositionText,"DispositionText"); @@ -276,18 +301,34 @@ namespace MWGui mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } - void DialogueWindow::exit() + DialogueWindow::~DialogueWindow() { - if ((!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) - && !mGoodbye) + deleteLater(); + for (Link* link : mLinks) + delete link; + for (auto link : mTopicLinks) + delete link.second; + for (auto history : mHistoryContents) + delete history; + } + + void DialogueWindow::onTradeComplete() + { + addResponse("", MyGUI::LanguageManager::getInstance().replaceTags("#{sBarterDialog5}")); + } + + bool DialogueWindow::exit() + { + if ((MWBase::Environment::get().getDialogueManager()->isInChoice())) { - // in choice, not allowed to escape, but give access to main menu to allow loading other saves - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + return false; } else { + resetReference(); MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); mTopicsList->scrollToTop(); + return true; } } @@ -312,12 +353,13 @@ namespace MWGui void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) { - exit(); + if (exit()) + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); } - void DialogueWindow::onSelectTopic(const std::string& topic, int id) + void DialogueWindow::onSelectListItem(const std::string& topic, int id) { - if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) + if (mGoodbye || MWBase::Environment::get().getDialogueManager()->isInChoice()) return; int separatorPos = 0; @@ -328,7 +370,11 @@ namespace MWGui } if (id >= separatorPos) - MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(topic)); + { + onTopicActivated(topic); + if (mGoodbyeButton->getEnabled()) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); + } else { const MWWorld::Store &gmst = @@ -337,53 +383,67 @@ namespace MWGui if (topic == gmst.find("sPersuasion")->getString()) mPersuasionDialog.setVisible(true); else if (topic == gmst.find("sCompanionShare")->getString()) - MWBase::Environment::get().getWindowManager()->showCompanionWindow(mPtr); - else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused()) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr); + else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get())) { if (topic == gmst.find("sBarter")->getString()) - MWBase::Environment::get().getWindowManager()->startTrade(mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr); else if (topic == gmst.find("sSpells")->getString()) - MWBase::Environment::get().getWindowManager()->startSpellBuying(mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr); else if (topic == gmst.find("sTravel")->getString()) - MWBase::Environment::get().getWindowManager()->startTravel(mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr); else if (topic == gmst.find("sSpellMakingMenuTitle")->getString()) - MWBase::Environment::get().getWindowManager()->startSpellMaking (mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr); else if (topic == gmst.find("sEnchanting")->getString()) - MWBase::Environment::get().getWindowManager()->startEnchanting (mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr); else if (topic == gmst.find("sServiceTrainingTitle")->getString()) - MWBase::Environment::get().getWindowManager()->startTraining (mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr); else if (topic == gmst.find("sRepair")->getString()) - MWBase::Environment::get().getWindowManager()->startRepair (mPtr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); } + else + updateTopics(); } } - void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory) + void DialogueWindow::setPtr(const MWWorld::Ptr& actor) { - mGoodbye = false; - mEnabled = true; bool sameActor = (mPtr == actor); - mPtr = actor; - mTopicsList->setEnabled(true); - setTitle(npcName); - - clearChoices(); - - mTopicsList->clear(); - - if (resetHistory || !sameActor) + if (!sameActor) { for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) delete (*it); mHistoryContents.clear(); + mKeywords.clear(); + mTopicsList->clear(); + for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) + mDeleteLater.push_back(*it); // Links are not deleted right away to prevent issues with event handlers + mLinks.clear(); } - for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) - delete (*it); - mLinks.clear(); + mPtr = actor; + mGoodbye = false; + mTopicsList->setEnabled(true); - updateOptions(); + if (!MWBase::Environment::get().getDialogueManager()->startDialogue(actor, mGreetingCallback.get())) + { + // No greetings found. The dialogue window should not be shown. + // If this is a companion, we must show the companion window directly (used by BM_bear_be_unique). + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); + mPtr = MWWorld::Ptr(); + if (isCompanion(actor)) + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Companion, actor); + return; + } + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); + + setTitle(mPtr.getClass().getName(mPtr)); + + updateTopics(); + updateTopicsPane(); // force update for new services + + updateDisposition(); restock(); } @@ -401,16 +461,35 @@ namespace MWGui } } + void DialogueWindow::deleteLater() + { + for (Link* link : mDeleteLater) + delete link; + mDeleteLater.clear(); + } + void DialogueWindow::setKeywords(std::list keyWords) + { + if (mKeywords == keyWords && isCompanion() == mIsCompanion) + return; + mIsCompanion = isCompanion(); + mKeywords = keyWords; + + updateTopicsPane(); + } + + void DialogueWindow::updateTopicsPane() { mTopicsList->clear(); for (std::map::iterator it = mTopicLinks.begin(); it != mTopicLinks.end(); ++it) - delete it->second; + mDeleteLater.push_back(it->second); mTopicLinks.clear(); mKeywordSearch.clear(); - bool isCompanion = !mPtr.getClass().getScript(mPtr).empty() - && mPtr.getRefData().getLocals().getIntVar(mPtr.getClass().getScript(mPtr), "companion"); + int services = mPtr.getClass().getServices(mPtr); + + bool travel = (mPtr.getTypeName() == typeid(ESM::NPC).name() && !mPtr.get()->mBase->getTransport().empty()) + || (mPtr.getTypeName() == typeid(ESM::Creature).name() && !mPtr.get()->mBase->getTransport().empty()); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); @@ -418,39 +497,40 @@ namespace MWGui if (mPtr.getTypeName() == typeid(ESM::NPC).name()) mTopicsList->addItem(gmst.find("sPersuasion")->getString()); - if (mServices & Service_Trade) + if (services & ESM::NPC::AllItems) mTopicsList->addItem(gmst.find("sBarter")->getString()); - if (mServices & Service_BuySpells) + if (services & ESM::NPC::Spells) mTopicsList->addItem(gmst.find("sSpells")->getString()); - if (mServices & Service_Travel) + if (travel) mTopicsList->addItem(gmst.find("sTravel")->getString()); - if (mServices & Service_CreateSpells) + if (services & ESM::NPC::Spellmaking) mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->getString()); - if (mServices & Service_Enchant) + if (services & ESM::NPC::Enchanting) mTopicsList->addItem(gmst.find("sEnchanting")->getString()); - if (mServices & Service_Training) + if (services & ESM::NPC::Training) mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->getString()); - if (mServices & Service_Repair) + if (services & ESM::NPC::Repair) mTopicsList->addItem(gmst.find("sRepair")->getString()); - if (isCompanion) + if (isCompanion()) mTopicsList->addItem(gmst.find("sCompanionShare")->getString()); if (mTopicsList->getItemCount() > 0) mTopicsList->addSeparator(); - for(std::list::iterator it = keyWords.begin(); it != keyWords.end(); ++it) + for(std::list::iterator it = mKeywords.begin(); it != mKeywords.end(); ++it) { mTopicsList->addItem(*it); Topic* t = new Topic(*it); + t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); mTopicLinks[Misc::StringUtils::lowerCase(*it)] = t; mKeywordSearch.seed(Misc::StringUtils::lowerCase(*it), intptr_t(t)); @@ -484,9 +564,11 @@ namespace MWGui typesetter->sectionBreak(9); // choices const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - for (std::vector >::iterator it = mChoices.begin(); it != mChoices.end(); ++it) + mChoices = MWBase::Environment::get().getDialogueManager()->getChoices(); + for (std::vector >::const_iterator it = mChoices.begin(); it != mChoices.end(); ++it) { Choice* link = new Choice(it->second); + link->eventChoiceActivated += MyGUI::newDelegate(this, &DialogueWindow::onChoiceActivated); mLinks.push_back(link); typesetter->lineBreak(); @@ -496,9 +578,11 @@ namespace MWGui typesetter->write(questionStyle, to_utf8_span(it->first.c_str())); } + mGoodbye = MWBase::Environment::get().getDialogueManager()->isGoodbye(); if (mGoodbye) { Goodbye* link = new Goodbye(); + link->eventActivated += MyGUI::newDelegate(this, &DialogueWindow::onGoodbyeActivated); mLinks.push_back(link); std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, @@ -528,10 +612,11 @@ namespace MWGui onScrollbarMoved(mScrollBar, 0); } - MyGUI::Button* byeButton; - getWidget(byeButton, "ByeButton"); bool goodbyeEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() || mGoodbye; - byeButton->setEnabled(goodbyeEnabled); + bool goodbyeWasEnabled = mGoodbyeButton->getEnabled(); + mGoodbyeButton->setEnabled(goodbyeEnabled); + if (goodbyeEnabled && !goodbyeWasEnabled) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); bool topicsEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() && !mGoodbye; mTopicsList->setEnabled(topicsEnabled); @@ -542,59 +627,47 @@ namespace MWGui reinterpret_cast(link)->activated(); } - void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) + void DialogueWindow::onTopicActivated(const std::string &topicId) { - mHistory->setPosition(0, static_cast(pos) * -1); + MWBase::Environment::get().getDialogueManager()->keywordSelected(topicId, mCallback.get()); + updateTopics(); } - void DialogueWindow::addResponse(const std::string &text, const std::string &title, bool needMargin) + void DialogueWindow::onChoiceActivated(int id) { - // This is called from the dialogue manager, so text is - // case-smashed - thus we have to retrieve the correct case - // of the title through the topic list. - std::string realTitle = title; - if (realTitle != "") - { - for (size_t i=0; igetItemCount(); ++i) - { - std::string item = mTopicsList->getItemNameAt(i); - if (Misc::StringUtils::ciEqual(item, title)) - { - realTitle = item; - break; - } - } - } + MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); + updateTopics(); + } - mHistoryContents.push_back(new Response(text, realTitle, needMargin)); - updateHistory(); + void DialogueWindow::onGoodbyeActivated() + { + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); + resetReference(); } - void DialogueWindow::addMessageBox(const std::string& text) + void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) { - mHistoryContents.push_back(new Message(text)); - updateHistory(); + mHistory->setPosition(0, static_cast(pos) * -1); } - void DialogueWindow::addChoice(const std::string& choice, int id) + void DialogueWindow::addResponse(const std::string &title, const std::string &text, bool needMargin) { - mChoices.push_back(std::make_pair(choice, id)); + mHistoryContents.push_back(new Response(text, title, needMargin)); updateHistory(); + updateTopics(); } - void DialogueWindow::clearChoices() + void DialogueWindow::addMessageBox(const std::string& text) { - mChoices.clear(); + mHistoryContents.push_back(new Message(text)); updateHistory(); } - void DialogueWindow::updateOptions() + void DialogueWindow::updateDisposition() { - //Clear the list of topics - mTopicsList->clear(); - bool dispositionVisible = false; - if (mPtr.getClass().isNpc()) + if (!mPtr.isEmpty() && mPtr.getClass().isNpc()) { dispositionVisible = true; mDispositionBar->setProgressRange(100); @@ -620,26 +693,39 @@ namespace MWGui } } - void DialogueWindow::goodbye() + void DialogueWindow::onReferenceUnavailable() { - mGoodbye = true; - mEnabled = false; - updateHistory(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); } - void DialogueWindow::onReferenceUnavailable() + void DialogueWindow::onFrame(float dt) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + checkReferenceAvailable(); + if (mPtr.isEmpty()) + return; + + updateDisposition(); + deleteLater(); + + if (mChoices != MWBase::Environment::get().getDialogueManager()->getChoices() + || mGoodbye != MWBase::Environment::get().getDialogueManager()->isGoodbye()) + updateHistory(); } - void DialogueWindow::onFrame() + void DialogueWindow::updateTopics() { - if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name()) - { - int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr); - mDispositionBar->setProgressRange(100); - mDispositionBar->setProgressPosition(disp); - mDispositionText->setCaption(MyGUI::utility::toString(disp)+std::string("/100")); - } + setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()); } + + bool DialogueWindow::isCompanion() + { + return isCompanion(mPtr); + } + + bool DialogueWindow::isCompanion(const MWWorld::Ptr& actor) + { + return !actor.getClass().getScript(actor).empty() + && actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"); + } + } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 5b5ae5b68..2538602c6 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -8,6 +8,8 @@ #include "../mwdialogue/keywordsearch.hpp" +#include + namespace Gui { class MWList; @@ -20,18 +22,20 @@ namespace MWGui namespace MWGui { - class DialogueHistoryViewModel; - class BookPage; + class ResponseCallback; class PersuasionDialog : public WindowModal { public: - PersuasionDialog(); + PersuasionDialog(ResponseCallback* callback); + + virtual void onOpen(); - virtual void open(); - virtual void exit(); + virtual MyGUI::Widget* getDefaultKeyFocus(); private: + std::unique_ptr mCallback; + MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; MyGUI::Button* mIntimidateButton; @@ -54,6 +58,8 @@ namespace MWGui struct Topic : Link { + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_TopicId; + EventHandle_TopicId eventTopicActivated; Topic(const std::string& id) : mTopicId(id) {} std::string mTopicId; virtual void activated (); @@ -61,6 +67,8 @@ namespace MWGui struct Choice : Link { + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ChoiceId; + EventHandle_ChoiceId eventChoiceActivated; Choice(int id) : mChoiceId(id) {} int mChoiceId; virtual void activated (); @@ -68,6 +76,8 @@ namespace MWGui struct Goodbye : Link { + typedef MyGUI::delegates::CMultiDelegate0 Event_Activated; + Event_Activated eventActivated; virtual void activated (); }; @@ -99,46 +109,42 @@ namespace MWGui { public: DialogueWindow(); + ~DialogueWindow(); + + void onTradeComplete(); - virtual void exit(); + virtual bool exit(); // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; void notifyLinkClicked (TypesetBook::InteractiveId link); - void startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory); + void setPtr(const MWWorld::Ptr& actor); + void setKeywords(std::list keyWord); - void addResponse (const std::string& text, const std::string& title="", bool needMargin = true); + void addResponse (const std::string& title, const std::string& text, bool needMargin = true); void addMessageBox(const std::string& text); - void addChoice(const std::string& choice, int id); - void clearChoices(); - - void goodbye(); - void onFrame(); - - // make sure to call these before setKeywords() - void setServices(int services) { mServices = services; } + void onFrame(float dt); + void clear() { resetReference(); } - enum Services - { - Service_Trade = 0x01, - Service_BuySpells = 0x02, - Service_CreateSpells = 0x04, - Service_Enchant = 0x08, - Service_Training = 0x10, - Service_Travel = 0x20, - Service_Repair = 0x40 - }; + void updateTopics(); protected: - void onSelectTopic(const std::string& topic, int id); + void updateTopicsPane(); + bool isCompanion(const MWWorld::Ptr& actor); + bool isCompanion(); + + void onSelectListItem(const std::string& topic, int id); void onByeClicked(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onWindowResize(MyGUI::Window* _sender); + void onTopicActivated(const std::string& topicId); + void onChoiceActivated(int id); + void onGoodbyeActivated(); void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos); @@ -147,21 +153,22 @@ namespace MWGui virtual void onReferenceUnavailable(); private: - void updateOptions(); + void updateDisposition(); void restock(); + void deleteLater(); - int mServices; - - bool mEnabled; - - bool mGoodbye; + bool mIsCompanion; + std::list mKeywords; std::vector mHistoryContents; std::vector > mChoices; + bool mGoodbye; std::vector mLinks; std::map mTopicLinks; + std::vector mDeleteLater; + KeywordSearchT mKeywordSearch; BookPage* mHistory; @@ -169,10 +176,14 @@ namespace MWGui MyGUI::ScrollBar* mScrollBar; MyGUI::ProgressBar* mDispositionBar; MyGUI::TextBox* mDispositionText; + MyGUI::Button* mGoodbyeButton; PersuasionDialog mPersuasionDialog; MyGUI::IntSize mCurrentWindowSize; + + std::unique_ptr mCallback; + std::unique_ptr mGreetingCallback; }; } #endif diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index fa17d974d..d81b2ed00 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -121,10 +121,18 @@ void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) mSourceView->update(); } +void DragAndDrop::onFrame() +{ + if (mIsOnDragAndDrop && mItem.mBase.getRefData().getCount() == 0) + finish(); +} + void DragAndDrop::finish() { mIsOnDragAndDrop = false; mSourceSortModel->clearDragItems(); + // since mSourceView doesn't get updated in drag() + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); mDraggedWidget = 0; diff --git a/apps/openmw/mwgui/draganddrop.hpp b/apps/openmw/mwgui/draganddrop.hpp index a356fe4e2..dff8cd73c 100644 --- a/apps/openmw/mwgui/draganddrop.hpp +++ b/apps/openmw/mwgui/draganddrop.hpp @@ -29,6 +29,7 @@ namespace MWGui void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); void drop (ItemModel* targetModel, ItemView* targetView); + void onFrame(); void finish(); }; diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 4e4f6ab3b..6e0be4089 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -4,11 +4,11 @@ #include #include +#include #include #include -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -53,6 +53,7 @@ namespace MWGui mSoulBox->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onSelectSoul); mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onBuyButtonClicked); mTypeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onTypeButtonClicked); + mName->eventEditSelectAccept += MyGUI::newDelegate(this, &EnchantingDialog::onAccept); } EnchantingDialog::~EnchantingDialog() @@ -60,9 +61,10 @@ namespace MWGui delete mItemSelectionDialog; } - void EnchantingDialog::open() + void EnchantingDialog::onOpen() { center(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mName); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) @@ -100,11 +102,6 @@ namespace MWGui } } - void EnchantingDialog::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); - } - void EnchantingDialog::updateLabels() { std::stringstream enchantCost; @@ -143,57 +140,41 @@ namespace MWGui } } - void EnchantingDialog::startEnchanting (MWWorld::Ptr actor) + void EnchantingDialog::setPtr (const MWWorld::Ptr& ptr) { mName->setCaption(""); - mEnchanting.setSelfEnchanting(false); - mEnchanting.setEnchanter(actor); - - mBuyButton->setCaptionWithReplacing("#{sBuy}"); - - mChanceLayout->setVisible(false); - - mPtr = actor; + if (ptr.getClass().isActor()) + { + mEnchanting.setSelfEnchanting(false); + mEnchanting.setEnchanter(ptr); + mBuyButton->setCaptionWithReplacing("#{sBuy}"); + mChanceLayout->setVisible(false); + mPtr = ptr; + setSoulGem(MWWorld::Ptr()); + mPrice->setVisible(true); + mPriceText->setVisible(true); + } + else + { + mEnchanting.setSelfEnchanting(true); + mEnchanting.setEnchanter(MWMechanics::getPlayer()); + mBuyButton->setCaptionWithReplacing("#{sCreate}"); + bool enabled = Settings::Manager::getBool("show enchant chance","Game"); + mChanceLayout->setVisible(enabled); + mPtr = MWMechanics::getPlayer(); + setSoulGem(ptr); + mPrice->setVisible(false); + mPriceText->setVisible(false); + } - setSoulGem(MWWorld::Ptr()); setItem(MWWorld::Ptr()); - startEditing (); - mPrice->setVisible(true); - mPriceText->setVisible(true); - updateLabels(); - } - - void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) - { - mName->setCaption(""); - - MWWorld::Ptr player = MWMechanics::getPlayer(); - - mEnchanting.setSelfEnchanting(true); - mEnchanting.setEnchanter(player); - - mBuyButton->setCaptionWithReplacing("#{sCreate}"); - - bool enabled = Settings::Manager::getBool("show enchant chance","Game"); - - mChanceLayout->setVisible(enabled); - - mPtr = player; - startEditing(); - - setSoulGem(soulgem); - setItem(MWWorld::Ptr()); - - mPrice->setVisible(false); - mPriceText->setVisible(false); updateLabels(); } void EnchantingDialog::onReferenceUnavailable () { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); resetReference(); } @@ -209,7 +190,7 @@ namespace MWGui void EnchantingDialog::onCancelButtonClicked(MyGUI::Widget* sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Enchanting); } void EnchantingDialog::onSelectItem(MyGUI::Widget *sender) @@ -304,6 +285,11 @@ namespace MWGui updateEffectsView(); } + void EnchantingDialog::onAccept(MyGUI::EditBox *sender) + { + onBuyButtonClicked(sender); + } + void EnchantingDialog::onBuyButtonClicked(MyGUI::Widget* sender) { if (mEffects.size() <= 0) @@ -364,7 +350,7 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, item, mPtr, 1); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return; } } diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index cb7c6c0d0..4906de919 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -19,15 +19,17 @@ namespace MWGui EnchantingDialog(); virtual ~EnchantingDialog(); - virtual void open(); + virtual void onOpen(); - virtual void exit(); + void onFrame(float dt) { checkReferenceAvailable(); } + void clear() { resetReference(); } void setSoulGem (const MWWorld::Ptr& gem); void setItem (const MWWorld::Ptr& item); - void startEnchanting(MWWorld::Ptr actor); - void startSelfEnchanting(MWWorld::Ptr soulgem); + /// Actor Ptr: buy enchantment from this actor + /// Soulgem Ptr: player self-enchant + void setPtr(const MWWorld::Ptr& ptr); virtual void resetReference(); @@ -46,6 +48,7 @@ namespace MWGui void onBuyButtonClicked(MyGUI::Widget* sender); void updateLabels(); void onTypeButtonClicked(MyGUI::Widget* sender); + void onAccept(MyGUI::EditBox* sender); ItemSelectionDialog* mItemSelectionDialog; @@ -58,7 +61,7 @@ namespace MWGui MyGUI::Button* mTypeButton; MyGUI::Button* mBuyButton; - MyGUI::TextBox* mName; + MyGUI::EditBox* mName; MyGUI::TextBox* mEnchantmentPoints; MyGUI::TextBox* mCastCost; MyGUI::TextBox* mCharge; diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 72e1c09f3..a9319048e 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -275,8 +275,6 @@ namespace MWGui { case BookTextParser::Event_ImgTag: { - pag.setIgnoreLeadingEmptyLines(false); - const BookTextParser::Attributes & attr = parser.getAttributes(); if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) @@ -286,8 +284,19 @@ namespace MWGui int width = MyGUI::utility::parseInt(attr.at("width")); int height = MyGUI::utility::parseInt(attr.at("height")); + bool exists; + std::string correctedSrc = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, height, &exists); + + if (!exists) + { + std::cerr << "Warning: Could not find \"" << src << "\" referenced by an tag." << std::endl; + break; + } + + pag.setIgnoreLeadingEmptyLines(false); + ImageElement elem(paper, pag, mBlockStyle, - src, width, height); + correctedSrc, width, height); elem.paginate(); break; } @@ -397,10 +406,11 @@ namespace MWGui MyGUI::EditBox* box = parent->createWidget("NormalText", MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); - box->setProperty("Static", "true"); - box->setProperty("MultiLine", "true"); - box->setProperty("WordWrap", "true"); - box->setProperty("NeedMouse", "false"); + box->setEditStatic(true); + box->setEditMultiLine(true); + box->setEditWordWrap(true); + box->setNeedMouseFocus(false); + box->setNeedKeyFocus(false); box->setMaxTextLength(text.size()); box->setTextAlign(mBlockStyle.mAlign); box->setTextColour(mTextStyle.mColour); @@ -471,8 +481,7 @@ namespace MWGui MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); - std::string image = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, mImageHeight); - mImageBox->setImageTexture(image); + mImageBox->setImageTexture(src); mImageBox->setProperty("NeedMouse", "false"); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 9d6c548d7..0b34141c2 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -108,7 +108,7 @@ namespace MWGui HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) - : Layout("openmw_hud.layout") + : WindowBase("openmw_hud.layout") , LocalMapBase(customMarkers, localMapRender, Settings::Manager::getBool("local map hud fog of war", "Map")) , mHealth(NULL) , mMagicka(NULL) @@ -120,7 +120,6 @@ namespace MWGui , mSpellStatus(NULL) , mEffectBox(NULL) , mMinimap(NULL) - , mCompass(NULL) , mCrosshair(NULL) , mCellNameBox(NULL) , mDrowningFrame(NULL) @@ -422,6 +421,20 @@ namespace MWGui if (mIsDrowning) mDrowningFlashTheta += dt * osg::PI*2; + + mSpellIcons->updateWidgets(mEffectBox, true); + + if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) + { + updateEnemyHealthBar(); + } + + if (mIsDrowning) + { + float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f; + + mDrowningFlash->setAlpha(intensity); + } } void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) @@ -653,23 +666,6 @@ namespace MWGui } - void HUD::update() - { - mSpellIcons->updateWidgets(mEffectBox, true); - - if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) - { - updateEnemyHealthBar(); - } - - if (mIsDrowning) - { - float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f; - - mDrowningFlash->setAlpha(intensity); - } - } - void HUD::setEnemy(const MWWorld::Ptr &enemy) { mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); @@ -686,6 +682,13 @@ namespace MWGui mEnemyHealthTimer = -1; } + void HUD::clear() + { + unsetSelectedSpell(); + unsetSelectedWeapon(); + resetEnemy(); + } + void HUD::customMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index e2ef52be0..73428c034 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -16,7 +16,7 @@ namespace MWGui class SpellIcons; class ItemWidget; - class HUD : public Layout, public LocalMapBase + class HUD : public WindowBase, public LocalMapBase { public: HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender); @@ -55,11 +55,11 @@ namespace MWGui MyGUI::Widget* getEffectBox() { return mEffectBox; } - void update(); - void setEnemy(const MWWorld::Ptr& enemy); void resetEnemy(); + void clear(); + private: MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning; MyGUI::Widget* mHealthFrame; @@ -69,7 +69,6 @@ namespace MWGui MyGUI::Widget *mEffectBox, *mMinimapBox; MyGUI::Button* mMinimapButton; MyGUI::ScrollView* mMinimap; - MyGUI::ImageBox* mCompass; MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 2fe540f22..c9f55d352 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -1,9 +1,17 @@ #include "inventoryitemmodel.hpp" +#include + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" + #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + namespace MWGui { @@ -45,16 +53,33 @@ MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, setNewOwner); } - void InventoryItemModel::removeItem (const ItemStack& item, size_t count) { - MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); - int removed = store.remove(item.mBase, count, mActor); + int removed = 0; + // Re-equipping makes sense only if a target has inventory + if (mActor.getClass().hasInventoryStore(mActor)) + { + MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); + removed = store.remove(item.mBase, count, mActor, true); + } + else + { + MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); + removed = store.remove(item.mBase, count, mActor); + } + + std::stringstream error; if (removed == 0) - throw std::runtime_error("Item to remove not found in container store"); + { + error << "Item '" << item.mBase.getCellRef().getRefId() << "' was not found in container store to remove"; + throw std::runtime_error(error.str()); + } else if (removed < static_cast(count)) - throw std::runtime_error("Not enough items in the stack to remove"); + { + error << "Not enough items '" << item.mBase.getCellRef().getRefId() << "' in the stack to remove (" << static_cast(count) << " requested, " << removed << " found)"; + throw std::runtime_error(error.str()); + } } MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) @@ -94,4 +119,16 @@ void InventoryItemModel::update() } } +bool InventoryItemModel::onTakeItem(const MWWorld::Ptr &item, int count) +{ + // Looting a dead corpse is considered OK + if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead()) + return true; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count); + + return true; +} + } diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index f58ee2939..38730bd4d 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -15,6 +15,8 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 640a3be55..ce8a569f9 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -40,7 +40,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" #include "../mwscript/interpretercontext.hpp" -#include "../mwrender/characterpreview.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -243,8 +242,8 @@ namespace MWGui MWWorld::Ptr object = item.mBase; int count = item.mCount; - bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); + if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; @@ -377,7 +376,7 @@ namespace MWGui dirtyPreview(); } - void InventoryWindow::open() + void InventoryWindow::onOpen() { if (!mPtr.isEmpty()) { @@ -542,6 +541,7 @@ namespace MWGui if (mDragAndDrop->mIsOnDragAndDrop) { MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; + mDragAndDrop->finish(); if (mDragAndDrop->mSourceModel != mTradeModel) @@ -549,7 +549,19 @@ namespace MWGui // Move item to the player's inventory ptr = mDragAndDrop->mSourceModel->moveItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel); } + useItem(ptr); + + // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 item + if ((ptr.getTypeName() == typeid(ESM::Potion).name() || + ptr.getTypeName() == typeid(ESM::Ingredient).name()) + && mDragAndDrop->mDraggedCount > 1) + { + // Item can be provided from other window for example container. + // But after DragAndDrop::startDrag item automaticly always gets to player inventory. + mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem); + dragItem(nullptr, mDragAndDrop->mDraggedCount - 1); + } } else { @@ -603,11 +615,8 @@ namespace MWGui mEncumbranceBar->setValue(static_cast(encumbrance), static_cast(capacity)); } - void InventoryWindow::onFrame() + void InventoryWindow::onFrame(float dt) { - if (!mMainWidget->getVisible()) - return; - updateEncumbranceBar(); } @@ -662,6 +671,9 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); + + if (!object.getRefData().activate()) + return; MWBase::Environment::get().getMechanicsManager()->itemTaken(player, object, MWWorld::Ptr(), count); @@ -750,14 +762,16 @@ namespace MWGui lastId = item.getCellRef().getRefId(); - if (item.getClass().getTypeName() == typeid(ESM::Weapon).name() && isRightHandWeapon(item)) + if (item.getClass().getTypeName() == typeid(ESM::Weapon).name() && + isRightHandWeapon(item) && + item.getClass().canBeEquipped(item, player).first) { found = true; break; } } - if (!found) + if (!found || selected == cycled) return; useItem(model.getItem(cycled).mBase); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 761464540..5576b52ed 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -35,12 +35,12 @@ namespace MWGui public: InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem); - virtual void open(); + virtual void onOpen(); /// start trading, disables item drag&drop void setTrading(bool trading); - void onFrame(); + void onFrame(float dt); void pickUpObject (MWWorld::Ptr object); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 390bb0586..e3f38a54c 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -9,6 +9,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" namespace MWGui { @@ -23,38 +24,7 @@ namespace MWGui if (base.getClass().getEnchantment(base) != "") mFlags |= Flag_Enchanted; - static std::set boundItemIDCache; - - // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's for some reason - if (boundItemIDCache.empty()) - { - // Build a list of known bound item ID's - const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); - - for (MWWorld::Store::iterator currentIteration = gameSettings.begin(); currentIteration != gameSettings.end(); ++currentIteration) - { - const ESM::GameSetting ¤tSetting = *currentIteration; - std::string currentGMSTID = currentSetting.mId; - Misc::StringUtils::lowerCaseInPlace(currentGMSTID); - - // Don't bother checking this GMST if it's not a sMagicBound* one. - const std::string& toFind = "smagicbound"; - if (currentGMSTID.compare(0, toFind.length(), toFind) != 0) - continue; - - // All sMagicBound* GMST's should be of type string - std::string currentGMSTValue = currentSetting.getString(); - Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); - - boundItemIDCache.insert(currentGMSTValue); - } - } - - // Perform bound item check and assign the Flag_Bound bit if it passes - std::string tempItemID = base.getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(tempItemID); - - if (boundItemIDCache.count(tempItemID) != 0) + if (MWBase::Environment::get().getMechanicsManager()->isBoundItem(base)) mFlags |= Flag_Bound; } @@ -119,7 +89,17 @@ namespace MWGui return ret; } - bool ItemModel::allowedToInsertItems() const + bool ItemModel::allowedToUseItems() const + { + return true; + } + + bool ItemModel::onDropItem(const MWWorld::Ptr &item, int count) + { + return true; + } + + bool ItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { return true; } @@ -135,6 +115,11 @@ namespace MWGui delete mSourceModel; } + bool ProxyItemModel::allowedToUseItems() const + { + return mSourceModel->allowedToUseItems(); + } + MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { return mSourceModel->copyItem (item, count, setNewOwner); @@ -188,4 +173,18 @@ namespace MWGui mSourceModel = sourceModel; } + void ProxyItemModel::onClose() + { + mSourceModel->onClose(); + } + + bool ProxyItemModel::onDropItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onDropItem(item, count); + } + + bool ProxyItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onTakeItem(item, count); + } } diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 20955b206..e8e348a8a 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -70,8 +70,13 @@ namespace MWGui virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; - /// Is the player allowed to insert items into this model? (default true) - virtual bool allowedToInsertItems() const; + /// Is the player allowed to use items from this item model? (default true) + virtual bool allowedToUseItems() const; + virtual void onClose() + { + } + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); private: ItemModel(const ItemModel&); @@ -85,6 +90,13 @@ namespace MWGui public: ProxyItemModel(); virtual ~ProxyItemModel(); + + bool allowedToUseItems() const; + + virtual void onClose(); + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index 095f392b7..effd11d55 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -29,9 +29,10 @@ namespace MWGui center(); } - void ItemSelectionDialog::exit() + bool ItemSelectionDialog::exit() { eventDialogCanceled(); + return true; } void ItemSelectionDialog::openContainer(const MWWorld::Ptr& container) diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index 50e15a3fe..07b6452a5 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -21,7 +21,7 @@ namespace MWGui public: ItemSelectionDialog(const std::string& label); - virtual void exit(); + virtual bool exit(); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Item; diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index ecd6d18f0..7267912ae 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -37,8 +37,6 @@ namespace MWGui { getWidget(mProgressBar, "ProgressBar"); - setVisible(false); - mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &JailScreen::onJailProgressChanged); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &JailScreen::onJailFinished); @@ -88,7 +86,10 @@ namespace MWGui Prevent teleportation to jail if specified */ if (!mwmp::Main::get().getLocalPlayer()->ignoreJailTeleportation) + { MWBase::Environment::get().getWorld()->teleportToClosestMarker(player, "prisonmarker"); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.f); // override fade-in caused by cell transition + } /* End of tes3mp change (minor) */ diff --git a/apps/openmw/mwgui/jailscreen.hpp b/apps/openmw/mwgui/jailscreen.hpp index 7a544126d..36d19b5a9 100644 --- a/apps/openmw/mwgui/jailscreen.hpp +++ b/apps/openmw/mwgui/jailscreen.hpp @@ -14,6 +14,8 @@ namespace MWGui void onFrame(float dt); + bool exit() { return false; } + private: int mDays; diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index a1d74fab6..e8aa23158 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -5,8 +5,10 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "textcolours.hpp" +#include +#include +#include "textcolours.hpp" namespace { @@ -154,8 +156,8 @@ MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) typedef TypesetBook::Ptr book; -JournalBooks::JournalBooks (JournalViewModel::Ptr model) : - mModel (model) +JournalBooks::JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding) : + mModel (model), mEncoding(encoding) { } @@ -217,34 +219,82 @@ book JournalBooks::createQuestBook (const std::string& questName) } book JournalBooks::createTopicIndexBook () +{ + bool isRussian = (mEncoding == ToUTF8::WINDOWS_1251); + + BookTypesetter::Ptr typesetter = isRussian ? createCyrillicJournalIndex() : createLatinJournalIndex(); + + return typesetter->complete (); +} + +BookTypesetter::Ptr JournalBooks::createLatinJournalIndex () { BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); typesetter->setSectionAlignment (BookTypesetter::AlignCenter); - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); + char ch = 'A'; + BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); for (int i = 0; i < 26; ++i) { - char ch = 'A' + i; - char buffer [32]; - sprintf (buffer, "( %c )", ch); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, textColours.journalTopicOver, - textColours.journalTopicPressed, ch); + textColours.journalTopicPressed, (uint32_t) ch); if (i == 13) typesetter->sectionBreak (); typesetter->write (style, to_utf8_span (buffer)); typesetter->lineBreak (); + + ch++; } - return typesetter->complete (); + return typesetter; +} + +BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () +{ + BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); + + typesetter->setSectionAlignment (BookTypesetter::AlignCenter); + + BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); + + unsigned char ch[2] = {0xd0, 0x90}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 + + for (int i = 0; i < 32; ++i) + { + char buffer [32]; + sprintf(buffer, "( %c%c )", ch[0], ch[1]); + + Utf8Stream stream ((char*) ch); + uint32_t first = stream.peek(); + + const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); + BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, + textColours.journalTopicOver, + textColours.journalTopicPressed, first); + + ch[1]++; + + // Words can not be started with these characters + if (i == 26 || i == 28) + continue; + + if (i == 15) + typesetter->sectionBreak (); + + typesetter->write (style, to_utf8_span (buffer)); + typesetter->lineBreak (); + } + + return typesetter; } BookTypesetter::Ptr JournalBooks::createTypesetter () diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp index 8f87825f0..aa36eecdf 100644 --- a/apps/openmw/mwgui/journalbooks.hpp +++ b/apps/openmw/mwgui/journalbooks.hpp @@ -4,6 +4,8 @@ #include "bookpage.hpp" #include "journalviewmodel.hpp" +#include + namespace MWGui { MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text); @@ -13,7 +15,7 @@ namespace MWGui typedef TypesetBook::Ptr Book; JournalViewModel::Ptr mModel; - JournalBooks (JournalViewModel::Ptr model); + JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding); Book createEmptyJournalBook (); Book createJournalBook (); @@ -22,8 +24,12 @@ namespace MWGui Book createQuestBook (const std::string& questName); Book createTopicIndexBook (); + ToUTF8::FromType mEncoding; + private: BookTypesetter::Ptr createTypesetter (); + BookTypesetter::Ptr createLatinJournalIndex (); + BookTypesetter::Ptr createCyrillicJournalIndex (); }; } diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index b5d08eec6..6ff68c9c5 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" @@ -305,18 +307,37 @@ struct JournalViewModelImpl : JournalViewModel visitor (toUtf8Span (topic.getName())); } - void visitTopicNamesStartingWith (char character, std::function < void (const std::string&) > visitor) const + void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) { - if (i->first [0] != Misc::StringUtils::toLower(character)) + Utf8Stream stream (i->first.c_str()); + uint32_t first = toUpper(stream.peek()); + + if (first != character) continue; visitor (i->second.getName()); } + } + + static uint32_t toUpper(uint32_t ch) + { + // Russian alphabet + if (ch >= 0x0430 && ch < 0x0450) + ch -= 0x20; + + // Cyrillic IO character + if (ch == 0x0451) + ch -= 0x50; + + // Latin alphabet + if (ch >= 0x61 && ch < 0x80) + ch -= 0x20; + return ch; } struct TopicEntryImpl : BaseEntry diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 3edde3d31..01dcb49de 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -75,8 +75,8 @@ namespace MWGui /// provides the name of the topic specified by its id virtual void visitTopicName (TopicId topicId, std::function visitor) const = 0; - /// walks over the topics whose names start with the specified character providing the topics name - virtual void visitTopicNamesStartingWith (char character, std::function < void (const std::string&) > visitor) const = 0; + /// walks over the topics whose names start with the character + virtual void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const = 0; /// walks over the topic entries for the topic specified by its identifier virtual void visitTopicEntries (TopicId topicId, std::function visitor) const = 0; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index e96cc8b70..4b7a789e8 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -44,7 +45,7 @@ namespace static char const LeftTopicIndex [] = "LeftTopicIndex"; static char const RightTopicIndex [] = "RightTopicIndex"; - struct JournalWindowImpl : MWGui::WindowBase, MWGui::JournalBooks, MWGui::JournalWindow + struct JournalWindowImpl : MWGui::JournalBooks, MWGui::JournalWindow { struct DisplayState { @@ -84,19 +85,24 @@ namespace void adviseButtonClick (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender)) { - getWidget (name) -> + getWidget (name) -> eventMouseButtonClick += newDelegate(this, Handler); } + void adviseKeyPress (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character)) + { + getWidget (name) -> + eventKeyButtonPressed += newDelegate(this, Handler); + } + MWGui::BookPage* getPage (char const * name) { return getWidget (name); } - JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList) - : WindowBase("openmw_journal.layout"), JournalBooks (Model) + JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) + : JournalBooks (Model, encoding), JournalWindow() { - mMainWidget->setVisible(false); center(); adviseButtonClick (OptionsBTN, &JournalWindowImpl::notifyOptions ); @@ -112,6 +118,12 @@ namespace adviseButtonClick (ShowAllBTN, &JournalWindowImpl::notifyShowAll ); adviseButtonClick (ShowActiveBTN, &JournalWindowImpl::notifyShowActive); + adviseKeyPress (OptionsBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress (PrevPageBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress (NextPageBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress (CloseBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress (JournalBTN, &JournalWindowImpl::notifyKeyPress); + Gui::MWList* list = getWidget(QuestsList); list->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyQuestClicked); @@ -149,6 +161,15 @@ namespace Gui::ImageButton* showActiveButton = getWidget(ShowActiveBTN); Gui::ImageButton* showAllButton = getWidget(ShowAllBTN); Gui::ImageButton* questsButton = getWidget(QuestsBTN); + + Gui::ImageButton* nextButton = getWidget(NextPageBTN); + if (nextButton->getSize().width == 64) + { + // english button has a 7 pixel wide strip of garbage on its right edge + nextButton->setSize(64-7, nextButton->getSize().height); + nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height)); + } + if (!questList) { // If tribunal is not installed (-> no options button), we still want the Topics button available, @@ -164,6 +185,8 @@ namespace showActiveButton->setVisible(false); showAllButton->setVisible(false); questsButton->setVisible(false); + + adjustButton(TopicsBTN); } else { @@ -176,23 +199,25 @@ namespace adjustButton(ShowActiveBTN); adjustButton(OptionsBTN); adjustButton(QuestsBTN); - } - - Gui::ImageButton* nextButton = getWidget(NextPageBTN); - if (nextButton->getSize().width == 64) - { - // english button has a 7 pixel wide strip of garbage on its right edge - nextButton->setSize(64-7, nextButton->getSize().height); - nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height)); - } + adjustButton(TopicsBTN); + int topicsWidth = getWidget(TopicsBTN)->getSize().width; + int cancelLeft = getWidget(CancelBTN)->getPosition().left; + int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; - adjustButton(TopicsBTN); - int topicsWidth = getWidget(TopicsBTN)->getSize().width; - int cancelLeft = getWidget(CancelBTN)->getPosition().left; - int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; + getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->getPosition().top); - getWidget(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); - getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->getPosition().top); + // Usually Topics, Quests, and Cancel buttons have the 64px width, so we can place the Topics left-up from the Cancel button, and the Quests right-up from the Cancel button. + // But in some installations, e.g. German one, the Topics button has the 128px width, so we should place it exactly left from the Quests button. + if (topicsWidth == 64) + { + getWidget(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); + } + else + { + int questLeft = getWidget(QuestsBTN)->getPosition().left; + getWidget(TopicsBTN)->setPosition(questLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); + } + } mQuestMode = false; mAllQuests = false; @@ -211,7 +236,7 @@ namespace button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); } - void open() + void onOpen() { if (!MWBase::Environment::get().getWindowManager ()->getJournalAllowed ()) { @@ -238,9 +263,11 @@ namespace --page; } updateShowingPages(); + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(getWidget(CloseBTN)); } - void close() + void onClose() { mModel->unload (); @@ -349,8 +376,19 @@ namespace relPages = 0; } - setVisible (PrevPageBTN, page > 0); - setVisible (NextPageBTN, relPages > 2); + MyGUI::Widget* nextPageBtn = getWidget(NextPageBTN); + MyGUI::Widget* prevPageBtn = getWidget(PrevPageBTN); + + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + bool nextPageVisible = relPages > 2; + nextPageBtn->setVisible(nextPageVisible); + bool prevPageVisible = page > 0; + prevPageBtn->setVisible(prevPageVisible); + + if (focus == nextPageBtn && !nextPageVisible && prevPageVisible) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(prevPageBtn); + else if (focus == prevPageBtn && !prevPageVisible && nextPageVisible) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nextPageBtn); setVisible (PageOneNum, relPages > 0); setVisible (PageTwoNum, relPages > 1); @@ -362,6 +400,14 @@ namespace setText (PageTwoNum, page + 2); } + void notifyKeyPress(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) + { + if (key == MyGUI::KeyCode::ArrowUp) + notifyPrevPage(sender); + else if (key == MyGUI::KeyCode::ArrowDown) + notifyNextPage(sender); + } + void notifyTopicClicked (intptr_t linkId) { Book topicBook = createTopicBook (linkId); @@ -431,7 +477,7 @@ namespace MWBase::Environment::get().getWindowManager()->playSound("book page"); } - void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character) + void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId index) { setVisible (LeftTopicIndex, false); setVisible (RightTopicIndex, false); @@ -444,7 +490,7 @@ namespace AddNamesToList add(list); - mModel->visitTopicNamesStartingWith((char) character, add); + mModel->visitTopicNamesStartingWith(index, add); list->adjustSize(); @@ -599,7 +645,13 @@ namespace } // glue the implementation to the interface -MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList) +MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) { - return new JournalWindowImpl (Model, questList); + return new JournalWindowImpl (Model, questList, encoding); +} + +MWGui::JournalWindow::JournalWindow() + :WindowBase("openmw_journal.layout") +{ + } diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index cc645ca44..62080f72e 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -1,6 +1,10 @@ #ifndef MWGUI_JOURNAL_H #define MWGUI_JOURNAL_H +#include "windowbase.hpp" + +#include + #include namespace MWBase { class WindowManager; } @@ -9,10 +13,12 @@ namespace MWGui { struct JournalViewModel; - struct JournalWindow + struct JournalWindow : public WindowBase { + JournalWindow(); + /// construct a new instance of the one JournalWindow implementation - static JournalWindow * create (std::shared_ptr Model, bool questList); + static JournalWindow * create (std::shared_ptr Model, bool questList, ToUTF8::FromType encoding); /// destroy this instance of the JournalWindow implementation virtual ~JournalWindow () {}; diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp new file mode 100644 index 000000000..d27185204 --- /dev/null +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -0,0 +1,283 @@ +#include "keyboardnavigation.hpp" + +#include +#include +#include +#include +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" + +namespace MWGui +{ + +bool shouldAcceptKeyFocus(MyGUI::Widget* w) +{ + return w && !w->castType(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled(); +} + +/// Recursively get all child widgets that accept keyboard input +void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector& results) +{ + if (!parent->getVisible() || !parent->getEnabled()) + return; + + MyGUI::EnumeratorWidgetPtr enumerator = parent->getEnumerator(); + while (enumerator.next()) + { + MyGUI::Widget* w = enumerator.current(); + if (!w->getVisible() || !w->getEnabled()) + continue; + if (w->getNeedKeyFocus() && shouldAcceptKeyFocus(w)) + results.push_back(w); + else + getKeyFocusWidgets(w, results); + } +} + +KeyboardNavigation::KeyboardNavigation() + : mCurrentFocus(nullptr) + , mModalWindow(nullptr) +{ + MyGUI::WidgetManager::getInstance().registerUnlinker(this); +} + +KeyboardNavigation::~KeyboardNavigation() +{ + MyGUI::WidgetManager::getInstance().unregisterUnlinker(this); +} + +void KeyboardNavigation::saveFocus(int mode) +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (shouldAcceptKeyFocus(focus)) + { + mKeyFocus[mode] = focus; + } + else + { + mKeyFocus[mode] = mCurrentFocus; + } +} + +void KeyboardNavigation::restoreFocus(int mode) +{ + std::map::const_iterator found = mKeyFocus.find(mode); + if (found != mKeyFocus.end()) + { + MyGUI::Widget* w = found->second; + if (w && w->getVisible() && w->getEnabled()) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); + } +} + +void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget) +{ + for (std::pair& w : mKeyFocus) + if (w.second == widget) + w.second = nullptr; + if (widget == mCurrentFocus) + mCurrentFocus = nullptr; +} + +void styleFocusedButton(MyGUI::Widget* w) +{ + if (w) + { + if (MyGUI::Button* b = w->castType(false)) + { + b->_setWidgetState("highlighted"); + } + } +} + +bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) +{ + while (widget && widget->getParent()) + widget = widget->getParent(); + return widget == root; +} + +void KeyboardNavigation::onFrame() +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + + if (focus == mCurrentFocus) + { + styleFocusedButton(mCurrentFocus); + return; + } + + // workaround incorrect key focus resets (fix in MyGUI TBD) + if (!shouldAcceptKeyFocus(focus) && shouldAcceptKeyFocus(mCurrentFocus) && (!mModalWindow || isRootParent(mCurrentFocus, mModalWindow))) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCurrentFocus); + focus = mCurrentFocus; + } + + // style highlighted button (won't be needed for MyGUI 3.2.3) + if (focus != mCurrentFocus) + { + if (mCurrentFocus) + { + if (MyGUI::Button* b = mCurrentFocus->castType(false)) + b->_setWidgetState("normal"); + } + + mCurrentFocus = focus; + } + + styleFocusedButton(mCurrentFocus); +} + +void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (!focus || !shouldAcceptKeyFocus(focus)) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); + } + else + { + if (!isRootParent(focus, window)) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); + } +} + +void KeyboardNavigation::setModalWindow(MyGUI::Widget *window) +{ + mModalWindow = window; +} + +enum Direction +{ + D_Left, + D_Up, + D_Right, + D_Down, + D_Next, + D_Prev +}; + +bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text) +{ + switch (key.getValue()) + { + case MyGUI::KeyCode::ArrowLeft: + return switchFocus(D_Left, false); + case MyGUI::KeyCode::ArrowRight: + return switchFocus(D_Right, false); + case MyGUI::KeyCode::ArrowUp: + return switchFocus(D_Up, false); + case MyGUI::KeyCode::ArrowDown: + return switchFocus(D_Down, false); + case MyGUI::KeyCode::Tab: + return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); + case MyGUI::KeyCode::Return: + case MyGUI::KeyCode::NumpadEnter: + case MyGUI::KeyCode::Space: + return accept(); + default: + return false; + } +} + +bool KeyboardNavigation::switchFocus(int direction, bool wrap) +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + + bool isCycle = (direction == D_Prev || direction == D_Next); + + if ((focus && focus->getTypeName().find("Button") == std::string::npos) && !isCycle) + return false; + + if (focus && isCycle && focus->getUserString("AcceptTab") == "true") + return false; + + if ((!focus || !focus->getNeedKeyFocus()) && isCycle) + { + // if nothing is selected, select the first widget + return selectFirstWidget(); + } + if (!focus) + return false; + + MyGUI::Widget* window = focus; + while (window && window->getParent()) + window = window->getParent(); + MyGUI::VectorWidgetPtr keyFocusList; + getKeyFocusWidgets(window, keyFocusList); + + if (keyFocusList.empty()) + return false; + + MyGUI::VectorWidgetPtr::iterator found = std::find(keyFocusList.begin(), keyFocusList.end(), focus); + if (found == keyFocusList.end()) + { + if (isCycle) + return selectFirstWidget(); + else + return false; + } + + bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); + + int index = found - keyFocusList.begin(); + index = forward ? (index+1) : (index-1); + if (wrap) + index = (index + keyFocusList.size())%keyFocusList.size(); + else + index = std::min(std::max(0, index), static_cast(keyFocusList.size())-1); + + MyGUI::Widget* next = keyFocusList[index]; + int vertdiff = next->getTop() - focus->getTop(); + int horizdiff = next->getLeft() - focus->getLeft(); + bool isVertical = std::abs(vertdiff) > std::abs(horizdiff); + if (direction == D_Right && (horizdiff <= 0 || isVertical)) + return false; + else if (direction == D_Left && (horizdiff >= 0 || isVertical)) + return false; + else if (direction == D_Down && (vertdiff <= 0 || !isVertical)) + return false; + else if (direction == D_Up && (vertdiff >= 0 || !isVertical)) + return false; + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[index]); + return true; +} + +bool KeyboardNavigation::selectFirstWidget() +{ + MyGUI::VectorWidgetPtr keyFocusList; + MyGUI::EnumeratorWidgetPtr enumerator = MyGUI::Gui::getInstance().getEnumerator(); + if (mModalWindow) + enumerator = mModalWindow->getEnumerator(); + while (enumerator.next()) + getKeyFocusWidgets(enumerator.current(), keyFocusList); + + if (!keyFocusList.empty()) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[0]); + return true; + } + return false; +} + +bool KeyboardNavigation::accept() +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (!focus) + return false; + //MyGUI::Button* button = focus->castType(false); + //if (button && button->getEnabled()) + if (focus->getTypeName().find("Button") != std::string::npos && focus->getEnabled()) + { + focus->eventMouseButtonClick(focus); + return true; + } + return false; +} + + +} diff --git a/apps/openmw/mwgui/keyboardnavigation.hpp b/apps/openmw/mwgui/keyboardnavigation.hpp new file mode 100644 index 000000000..728f16a3d --- /dev/null +++ b/apps/openmw/mwgui/keyboardnavigation.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_MWGUI_KEYBOARDNAVIGATION_H +#define OPENMW_MWGUI_KEYBOARDNAVIGATION_H + +#include +#include + +namespace MWGui +{ + + class KeyboardNavigation : public MyGUI::IUnlinkWidget + { + public: + KeyboardNavigation(); + ~KeyboardNavigation(); + + /// @return Was the key handled by this class? + bool injectKeyPress(MyGUI::KeyCode key, unsigned int text); + + void saveFocus(int mode); + void restoreFocus(int mode); + + void _unlinkWidget(MyGUI::Widget* widget); + + void onFrame(); + + /// Set a key focus widget for this window, if one isn't already set. + void setDefaultFocus(MyGUI::Widget* window, MyGUI::Widget* defaultFocus); + + void setModalWindow(MyGUI::Widget* window); + + private: + bool switchFocus(int direction, bool wrap); + + bool selectFirstWidget(); + + /// Send button press event to focused button + bool accept(); + + std::map mKeyFocus; + + MyGUI::Widget* mCurrentFocus; + MyGUI::Widget* mModalWindow; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 362ad3b1c..286405f79 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -41,6 +41,7 @@ namespace MWGui { MyGUI::TextBox* t; getWidget(t, "AttribVal" + MyGUI::utility::toString(i)); + mAttributeValues.push_back(t); MyGUI::Button* b; getWidget(b, "Attrib" + MyGUI::utility::toString(i)); @@ -48,10 +49,7 @@ namespace MWGui b->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked); mAttributes.push_back(b); - mAttributeValues.push_back(t); - getWidget(t, "AttribMultiplier" + MyGUI::utility::toString(i)); - mAttributeMultipliers.push_back(t); } @@ -128,7 +126,7 @@ namespace MWGui setAttributeValues(); } - void LevelupDialog::open() + void LevelupDialog::onOpen() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); @@ -143,10 +141,10 @@ namespace MWGui mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); std::string levelupdescription; - if(level > 20) + levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level)); + + if (levelupdescription == "") levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); - else - levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level)); mLevelDescription->setCaption (levelupdescription); diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp index d49f7536d..36810665e 100644 --- a/apps/openmw/mwgui/levelupdialog.hpp +++ b/apps/openmw/mwgui/levelupdialog.hpp @@ -11,7 +11,7 @@ namespace MWGui public: LevelupDialog(); - virtual void open(); + virtual void onOpen(); private: MyGUI::Button* mOkButton; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index c5836b653..4753c39f2 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -50,8 +50,8 @@ namespace MWGui mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); - - setVisible(false); + mSceneImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Scene"); findSplashScreens(); } @@ -66,15 +66,27 @@ namespace MWGui std::string pattern = "Splash/"; mVFS->normalizeFilename(pattern); - std::map::const_iterator found = index.lower_bound(pattern); + /* priority given to the left */ + std::list supported_extensions = {".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}; + + auto found = index.lower_bound(pattern); while (found != index.end()) { const std::string& name = found->first; if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) { size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".tga") == 0) - mSplashScreens.push_back(found->first); + if (pos != std::string::npos) + { + for(auto const extension: supported_extensions) + { + if (name.compare(pos, name.size() - pos, extension) == 0) + { + mSplashScreens.push_back(found->first); + break; /* based on priority */ + } + } + } } else break; @@ -100,6 +112,7 @@ namespace MWGui { WindowBase::setVisible(visible); mBackgroundImage->setVisible(visible); + mSceneImage->setVisible(visible); } double LoadingScreen::getTargetFrameRate() const @@ -204,8 +217,11 @@ namespace MWGui // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed as 4:3 bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); + mBackgroundImage->setVisible(true); mBackgroundImage->setBackgroundImage(randomSplash, true, stretch); } + mSceneImage->setBackgroundImage(""); + mSceneImage->setVisible(false); } void LoadingScreen::setProgressRange (size_t range) @@ -282,9 +298,11 @@ namespace MWGui mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture)); mBackgroundImage->setBackgroundImage(""); + mBackgroundImage->setVisible(false); - mBackgroundImage->setRenderItemTexture(mGuiTexture.get()); - mBackgroundImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + mSceneImage->setRenderItemTexture(mGuiTexture.get()); + mSceneImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + mSceneImage->setVisible(true); } void LoadingScreen::draw() diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 2f8831fdc..8536972a3 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -72,6 +72,7 @@ namespace MWGui MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; BackgroundImage* mBackgroundImage; + BackgroundImage* mSceneImage; std::vector mSplashScreens; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 07f585df2..b79646d71 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -22,7 +22,7 @@ namespace MWGui { MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription) - : Layout("openmw_mainmenu.layout") + : WindowBase("openmw_mainmenu.layout") , mWidth (w), mHeight (h) , mVFS(vfs), mButtonBox(0) , mBackground(NULL) @@ -56,15 +56,31 @@ namespace MWGui if (visible) updateMenu(); - showBackground( - MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && - MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); + bool isMainMenu = + MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && + MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; + + showBackground(isMainMenu); + + if (visible) + { + if (isMainMenu) + { + if (mButtons["loadgame"]->getVisible()) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["loadgame"]); + else + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["newgame"]); + } + else + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["return"]); + } Layout::setVisible (visible); } void MainMenu::onNewGameConfirmed() { + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); MWBase::Environment::get().getStateManager()->newGame(); } @@ -182,7 +198,7 @@ namespace MWGui } } - void MainMenu::update(float dt) + void MainMenu::onFrame(float dt) { if (mVideo) { @@ -194,6 +210,11 @@ namespace MWGui } } + bool MainMenu::exit() + { + return MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running; + } + void MainMenu::updateMenu() { setCoord(0,0, mWidth, mHeight); @@ -244,7 +265,8 @@ namespace MWGui buttons.push_back("exitgame"); // Create new buttons if needed - for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) + std::vector allButtons { "return", "newgame", "savegame", "loadgame", "options", "credits", "exitgame"}; + for (std::vector::iterator it = allButtons.begin(); it != allButtons.end(); ++it) { if (mButtons.find(*it) == mButtons.end()) { diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index fe256dc8e..82553d22e 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_GAME_MWGUI_MAINMENU_H #define OPENMW_GAME_MWGUI_MAINMENU_H -#include "layout.hpp" +#include "windowbase.hpp" namespace Gui { @@ -20,7 +20,7 @@ namespace MWGui class SaveGameDialog; class VideoWidget; - class MainMenu : public Layout + class MainMenu : public WindowBase { int mWidth; int mHeight; @@ -36,7 +36,9 @@ namespace MWGui virtual void setVisible (bool visible); - void update(float dt); + void onFrame(float dt); + + bool exit(); private: const VFS::Manager* mVFS; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index c11535676..ff389c797 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -760,7 +760,7 @@ namespace MWGui void MapWindow::onNoteEditDelete() { ConfirmationDialog* confirmation = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - confirmation->askForConfirmation("#{sDeleteNote}", "#{sYes}", "#{sNo}"); + confirmation->askForConfirmation("#{sDeleteNote}"); confirmation->eventCancelClicked.clear(); confirmation->eventOkClicked.clear(); confirmation->eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDeleteConfirm); @@ -1002,7 +1002,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); } - void MapWindow::open() + void MapWindow::onOpen() { ensureGlobalMapLoaded(); @@ -1061,6 +1061,9 @@ namespace MWGui mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + + // Redraw children in proper order + mGlobalMap->getParent()->_updateChilds(); } } @@ -1163,18 +1166,13 @@ namespace MWGui return MyGUI::TextIterator::getOnlyText(mTextEdit->getCaption()); } - void EditNoteDialog::open() + void EditNoteDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } - void EditNoteDialog::exit() - { - setVisible(false); - } - void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget *sender) { setVisible(false); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index c5877470a..bb5546174 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -211,8 +211,7 @@ namespace MWGui public: EditNoteDialog(); - virtual void open(); - virtual void exit(); + virtual void onOpen(); void showDeleteButton(bool show); bool getDeleteButtonShown(); @@ -269,7 +268,7 @@ namespace MWGui void ensureGlobalMapLoaded(); - virtual void open(); + virtual void onOpen(); void onFrame(float dt); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 9e27c0154..d3504ebc3 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -32,7 +32,7 @@ MerchantRepair::MerchantRepair() mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick); } -void MerchantRepair::startRepair(const MWWorld::Ptr &actor) +void MerchantRepair::setPtr(const MWWorld::Ptr &actor) { mActor = actor; @@ -111,18 +111,13 @@ void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) mList->setViewOffset(MyGUI::IntPoint(0, static_cast(mList->getViewOffset().top + _rel*0.3f))); } -void MerchantRepair::open() +void MerchantRepair::onOpen() { center(); // Reset scrollbars mList->setViewOffset(MyGUI::IntPoint(0, 0)); } -void MerchantRepair::exit() -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); -} - void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -145,12 +140,12 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); actorStats.setGoldPool(actorStats.getGoldPool() + price); - startRepair(mActor); + setPtr(mActor); } void MerchantRepair::onOkButtonClick(MyGUI::Widget *sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); } } diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp index 8dcdfee12..4e4e7164f 100644 --- a/apps/openmw/mwgui/merchantrepair.hpp +++ b/apps/openmw/mwgui/merchantrepair.hpp @@ -12,11 +12,9 @@ class MerchantRepair : public WindowBase public: MerchantRepair(); - virtual void open(); + virtual void onOpen(); - virtual void exit(); - - void startRepair(const MWWorld::Ptr& actor); + void setPtr(const MWWorld::Ptr& actor); private: MyGUI::ScrollView* mList; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index ab43df0f1..7c89c4979 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -36,8 +36,13 @@ namespace MWGui void MessageBoxManager::clear() { - delete mInterMessageBoxe; - mInterMessageBoxe = NULL; + if (mInterMessageBoxe) + { + mInterMessageBoxe->setVisible(false); + + delete mInterMessageBoxe; + mInterMessageBoxe = NULL; + } std::vector::iterator it(mMessageBoxes.begin()); for (; it != mMessageBoxes.end(); ++it) @@ -77,6 +82,7 @@ namespace MWGui if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) { mLastButtonPressed = mInterMessageBoxe->readPressedButton(); + mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; mInterMessageBoxe = NULL; MWBase::Environment::get().getInputManager()->changeInputMode( @@ -120,6 +126,7 @@ namespace MWGui if (mInterMessageBoxe != NULL) { std::cerr << "Warning: replacing an interactive message box that was not answered yet" << std::endl; + mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; mInterMessageBoxe = NULL; } @@ -200,13 +207,14 @@ namespace MWGui , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) { - WindowModal::open(); + setVisible(true); int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget int buttonLeftPadding = 10; // padding between the buttons if horizontal int buttonTopPadding = 10; // ^-- if vertical - int buttonPadding = 5; // padding between button label and button itself + int buttonLabelLeftPadding = 12; // padding between button label and button itself, from left + int buttonLabelTopPadding = 4; // padding between button label and button itself, from top int buttonMainPadding = 10; // padding between buttons and bottom of the main widget mMarkedToDelete = false; @@ -228,8 +236,7 @@ namespace MWGui int buttonHeight = 0; MyGUI::IntCoord dummyCoord(0, 0, 0, 0); - std::vector::const_iterator it; - for(it = buttons.begin(); it != buttons.end(); ++it) + for(std::vector::const_iterator it = buttons.begin(); it != buttons.end(); ++it) { MyGUI::Button* button = mButtonsWidget->createWidget( MyGUI::WidgetStyle::Child, @@ -245,10 +252,10 @@ namespace MWGui if (buttonsWidth != 0) buttonsWidth += buttonLeftPadding; - int buttonWidth = button->getTextSize().width + 2*buttonPadding; + int buttonWidth = button->getTextSize().width + 2*buttonLabelLeftPadding; buttonsWidth += buttonWidth; - buttonHeight = button->getTextSize().height + 2*buttonPadding; + buttonHeight = button->getTextSize().height + 2*buttonLabelTopPadding; if (buttonsHeight != 0) buttonsHeight += buttonTopPadding; @@ -289,14 +296,13 @@ namespace MWGui MyGUI::IntSize buttonSize(0, buttonHeight); int left = (mainWidgetSize.width - buttonsWidth)/2; - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) + for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) { buttonCord.left = left; buttonCord.top = messageWidgetCoord.top + textSize.height + textButtonPadding; - buttonSize.width = (*button)->getTextSize().width + 2*buttonPadding; - buttonSize.height = (*button)->getTextSize().height + 2*buttonPadding; + buttonSize.width = (*button)->getTextSize().width + 2*buttonLabelLeftPadding; + buttonSize.height = (*button)->getTextSize().height + 2*buttonLabelTopPadding; (*button)->setCoord(buttonCord); (*button)->setSize(buttonSize); @@ -319,11 +325,10 @@ namespace MWGui int top = textPadding + textSize.height + textButtonPadding; - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) + for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) { - buttonSize.width = (*button)->getTextSize().width + buttonPadding*2; - buttonSize.height = (*button)->getTextSize().height + buttonPadding*2; + buttonSize.width = (*button)->getTextSize().width + buttonLabelLeftPadding*2; + buttonSize.height = (*button)->getTextSize().height + buttonLabelTopPadding*2; buttonCord.top = top; buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2; @@ -354,25 +359,20 @@ namespace MWGui } // Set key focus to "Ok" button - std::string ok = Misc::StringUtils::lowerCase(MyGUI::LanguageManager::getInstance().replaceTags("#{sOK}")); - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) + std::vector keywords { "sOk", "sYes" }; + for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) { - if(Misc::StringUtils::ciEqual((*button)->getCaption(), ok)) + for (const std::string& keyword : keywords) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button); - (*button)->eventKeyButtonPressed += MyGUI::newDelegate(this, &InteractiveMessageBox::onKeyPressed); - break; + if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), (*button)->getCaption())) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button); + return; + } } } } - void InteractiveMessageBox::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) - { - if (_key == MyGUI::KeyCode::Return || _key == MyGUI::KeyCode::NumpadEnter || _key == MyGUI::KeyCode::Space) - buttonActivated(_sender); - } - void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) { buttonActivated (pressed); diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index b4121fed3..53b277416 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -77,11 +77,12 @@ namespace MWGui void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); + virtual bool exit() { return false; } + bool mMarkedToDelete; private: void buttonActivated (MyGUI::Widget* _widget); - void onKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char); MessageBoxManager& mMessageBoxManager; MyGUI::EditBox* mMessageWidget; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index db851e067..c452a1f5f 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -23,7 +23,6 @@ namespace MWGui GM_Dialogue, // NPC interaction GM_Barter, GM_Rest, - GM_RestBed, GM_SpellBuying, GM_Travel, GM_SpellCreation, diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 238fb5913..e85715e87 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -3,15 +3,26 @@ #include #include +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/pickpocket.hpp" + +#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" + namespace MWGui { - PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel, bool hideItems) + PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& actor, ItemModel *sourceModel, bool hideItems) + : mActor(actor), mPickpocketDetected(false) { + MWWorld::Ptr player = MWMechanics::getPlayer(); mSourceModel = sourceModel; - int chance = thief.getClass().getSkill(thief, ESM::Skill::Sneak); + int chance = player.getClass().getSkill(player, ESM::Skill::Sneak); mSourceModel->update(); @@ -26,6 +37,11 @@ namespace MWGui } } + bool PickpocketItemModel::allowedToUseItems() const + { + return false; + } + ItemStack PickpocketItemModel::getItem (ModelIndex index) { if (index < 0) @@ -61,13 +77,63 @@ namespace MWGui void PickpocketItemModel::removeItem (const ItemStack &item, size_t count) { ProxyItemModel::removeItem(item, count); - /// \todo check if player is detected } - bool PickpocketItemModel::allowedToInsertItems() const + bool PickpocketItemModel::onDropItem(const MWWorld::Ptr &item, int count) { - // don't allow "reverse pickpocket" (yet) + // don't allow "reverse pickpocket" (it will be handled by scripts after 1.0) return false; } + void PickpocketItemModel::onClose() + { + // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened) + if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Container) + // If it was already detected while taking an item, no need to check now + || mPickpocketDetected) + return; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::Pickpocket pickpocket(player, mActor); + if (pickpocket.finish()) + { + MWBase::Environment::get().getMechanicsManager()->commitCrime( + player, mActor, MWBase::MechanicsManager::OT_Pickpocket, 0, true); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); + mPickpocketDetected = true; + } + } + + bool PickpocketItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + { + if (mActor.getClass().getCreatureStats(mActor).getKnockedDown()) + return mSourceModel->onTakeItem(item, count); + + bool success = stealItem(item, count); + if (success) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count, false); + } + + return success; + } + + bool PickpocketItemModel::stealItem(const MWWorld::Ptr &item, int count) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::Pickpocket pickpocket(player, mActor); + if (pickpocket.pick(item, count)) + { + MWBase::Environment::get().getMechanicsManager()->commitCrime( + player, mActor, MWBase::MechanicsManager::OT_Pickpocket, 0, true); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); + mPickpocketDetected = true; + return false; + } + else + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); + + return true; + } } diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp index 61f0569b5..83caf2ffd 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.hpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp @@ -11,11 +11,20 @@ namespace MWGui { public: PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel, bool hideItems=true); + + virtual bool allowedToUseItems() const; virtual ItemStack getItem (ModelIndex index); virtual size_t getItemCount(); virtual void update(); virtual void removeItem (const ItemStack& item, size_t count); - virtual bool allowedToInsertItems() const; + virtual void onClose(); + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + + protected: + MWWorld::Ptr mActor; + bool mPickpocketDetected; + bool stealItem(const MWWorld::Ptr &item, int count); private: std::vector mHiddenItems; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 6198372e0..aa2726799 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -76,11 +76,6 @@ namespace MWGui } } - void QuickKeysMenu::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); - } - void QuickKeysMenu::clear() { mActivatedIndex = -1; @@ -535,11 +530,6 @@ namespace MWGui center(); } - void QuickKeysMenuAssign::exit() - { - setVisible(false); - } - void QuickKeysMenu::write(ESM::ESMWriter &writer) { writer.startRecord(ESM::REC_KEYS); @@ -671,14 +661,15 @@ namespace MWGui exit(); } - void MagicSelectionDialog::exit() + bool MagicSelectionDialog::exit() { mParent->onAssignMagicCancel(); + return true; } - void MagicSelectionDialog::open () + void MagicSelectionDialog::onOpen () { - WindowModal::open(); + WindowModal::onOpen(); mMagicList->setModel(new SpellModel(MWMechanics::getPlayer())); mMagicList->resetScrollbars(); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index e89da52a6..55096d064 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -22,7 +22,7 @@ namespace MWGui QuickKeysMenu(); ~QuickKeysMenu(); - virtual void exit(); + void onResChange(int, int) { center(); } void onItemButtonClicked(MyGUI::Widget* sender); void onMagicButtonClicked(MyGUI::Widget* sender); @@ -97,7 +97,6 @@ namespace MWGui { public: QuickKeysMenuAssign(QuickKeysMenu* parent); - virtual void exit(); private: MyGUI::TextBox* mLabel; @@ -114,8 +113,8 @@ namespace MWGui public: MagicSelectionDialog(QuickKeysMenu* parent); - virtual void open(); - virtual void exit(); + virtual void onOpen(); + virtual bool exit(); private: MyGUI::Button* mCancelButton; diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 187aeb408..d9878f495 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -131,9 +131,9 @@ namespace MWGui okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } - void RaceDialog::open() + void RaceDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); updateRaces(); updateSkills(); @@ -174,6 +174,8 @@ namespace MWGui size_t initialPos = mHeadRotate->getScrollRange()/2+mHeadRotate->getScrollRange()/10; mHeadRotate->setScrollPosition(initialPos); onHeadRotate(mHeadRotate, initialPos); + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mRaceList); } void RaceDialog::setRaceId(const std::string &raceId) @@ -194,8 +196,10 @@ namespace MWGui updateSpellPowers(); } - void RaceDialog::close() + void RaceDialog::onClose() { + WindowModal::onClose(); + mPreviewImage->setRenderItemTexture(NULL); mPreviewTexture.reset(NULL); diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index b762745cd..c9e31d42d 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -51,8 +51,10 @@ namespace MWGui void setGender(Gender gender) { mGenderIndex = gender == GM_Male ? 0 : 1; } void setNextButtonShow(bool shown); - virtual void open(); - virtual void close(); + virtual void onOpen(); + virtual void onClose(); + + bool exit() { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 390a1f83c..26a364f72 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -21,7 +21,6 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" -#include "widgets.hpp" #include "itemwidget.hpp" #include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" @@ -46,11 +45,9 @@ Recharge::Recharge() mBox->setDisplayMode(ItemChargeView::DisplayMode_EnchantmentCharge); mGemIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onSelectItem); - - setVisible(false); } -void Recharge::open() +void Recharge::onOpen() { center(); @@ -62,12 +59,7 @@ void Recharge::open() mBox->resetScrollbars(); } -void Recharge::exit() -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); -} - -void Recharge::start (const MWWorld::Ptr &item) +void Recharge::setPtr (const MWWorld::Ptr &item) { mGemIcon->setItem(item); mGemIcon->setUserString("ToolTipType", "ItemPtr"); @@ -107,7 +99,7 @@ void Recharge::updateView() void Recharge::onCancel(MyGUI::Widget *sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); } void Recharge::onSelectItem(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp index cc3db14df..f4602ce30 100644 --- a/apps/openmw/mwgui/recharge.hpp +++ b/apps/openmw/mwgui/recharge.hpp @@ -22,11 +22,9 @@ class Recharge : public WindowBase public: Recharge(); - virtual void open(); + virtual void onOpen(); - virtual void exit(); - - void start (const MWWorld::Ptr& gem); + void setPtr (const MWWorld::Ptr& gem); protected: ItemChargeView* mBox; diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 9aaa98f19..6213d9e25 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -1,14 +1,8 @@ #include "referenceinterface.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - -#include "../mwmechanics/actorutil.hpp" - namespace MWGui { ReferenceInterface::ReferenceInterface() - : mCurrentPlayerCell(NULL) { } @@ -18,8 +12,6 @@ namespace MWGui void ReferenceInterface::checkReferenceAvailable() { - MWWorld::CellStore* playerCell = MWMechanics::getPlayer().getCell(); - // check if count of the reference has become 0 if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) { @@ -29,7 +21,5 @@ namespace MWGui onReferenceUnavailable(); } } - - mCurrentPlayerCell = playerCell; } } diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp index 0ba180de8..6428a5b54 100644 --- a/apps/openmw/mwgui/referenceinterface.hpp +++ b/apps/openmw/mwgui/referenceinterface.hpp @@ -17,15 +17,12 @@ namespace MWGui void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable - virtual void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; } + virtual void resetReference() { mPtr = MWWorld::Ptr(); } protected: virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable MWWorld::Ptr mPtr; - - private: - MWWorld::CellStore* mCurrentPlayerCell; }; } diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 4d6441450..11a8aece2 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -17,8 +17,6 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" -#include "widgets.hpp" - #include "itemwidget.hpp" #include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" @@ -46,7 +44,7 @@ Repair::Repair() mToolIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onSelectItem); } -void Repair::open() +void Repair::onOpen() { center(); @@ -58,12 +56,7 @@ void Repair::open() mRepairBox->resetScrollbars(); } -void Repair::exit() -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); -} - -void Repair::startRepairItem(const MWWorld::Ptr &item) +void Repair::setPtr(const MWWorld::Ptr &item) { MWBase::Environment::get().getWindowManager()->playSound("Item Repair Up"); @@ -145,7 +138,7 @@ void Repair::onItemCancel() void Repair::onCancel(MyGUI::Widget* /*sender*/) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); } void Repair::onRepairItem(MyGUI::Widget* /*sender*/, const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp index c50ba861a..f31625095 100644 --- a/apps/openmw/mwgui/repair.hpp +++ b/apps/openmw/mwgui/repair.hpp @@ -19,11 +19,9 @@ class Repair : public WindowBase public: Repair(); - virtual void open(); + virtual void onOpen(); - virtual void exit(); - - void startRepairItem (const MWWorld::Ptr& item); + void setPtr (const MWWorld::Ptr& item); protected: ItemChargeView* mRepairBox; diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index bf18e7355..61a7a2ac0 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -101,9 +101,9 @@ namespace MWGui okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked); } - void ReviewDialog::open() + void ReviewDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); mUpdateSkillArea = true; } diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index f21f2fa9d..8e9ec0ec7 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -31,6 +31,8 @@ namespace MWGui ReviewDialog(); + bool exit() { return false; } + void setPlayerName(const std::string &name); void setRace(const std::string &raceId); void setClass(const ESM::Class& class_); @@ -45,7 +47,7 @@ namespace MWGui void configureSkills(const SkillList& major, const SkillList& minor); void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value); - virtual void open(); + virtual void onOpen(); void onFrame(float duration); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 84d4ca959..43e511144 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -29,7 +29,6 @@ #include "../mwstate/character.hpp" #include "confirmationdialog.hpp" -#include "widgets.hpp" namespace MWGui { @@ -51,13 +50,17 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); - mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); + mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); + mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterAccept); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); mSaveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &SaveGameDialog::onKeyButtonPressed); mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); + + // To avoid accidental deletions + mDeleteButton->setNeedKeyFocus(false); } void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos) @@ -81,6 +84,7 @@ namespace MWGui dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); dialog->eventCancelClicked.clear(); + dialog->eventCancelClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotCancel); } void SaveGameDialog::onDeleteSlotConfirmed() @@ -106,6 +110,11 @@ namespace MWGui } } + void SaveGameDialog::onDeleteSlotCancel() + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + } + void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) { // This might have previously been a save slot from the list. If so, that is no longer the case @@ -118,13 +127,15 @@ namespace MWGui accept(); } - void SaveGameDialog::open() + void SaveGameDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); mSaveNameEdit->setCaption (""); if (mSaving) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit); + else + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); center(); @@ -191,11 +202,6 @@ namespace MWGui } - void SaveGameDialog::exit() - { - setVisible(false); - } - void SaveGameDialog::setLoadOrSave(bool load) { mSaving = !load; @@ -217,7 +223,7 @@ namespace MWGui void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender) { - exit(); + setVisible(false); } void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender) @@ -231,6 +237,11 @@ namespace MWGui accept(true); } + void SaveGameDialog::onConfirmationCancel() + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + } + void SaveGameDialog::accept(bool reallySure) { // Remove for MyGUI 3.2.2 @@ -246,6 +257,7 @@ namespace MWGui dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); + dialog->eventCancelClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationCancel); return; } if (mSaveNameEdit->getCaption().empty()) @@ -266,6 +278,7 @@ namespace MWGui dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); + dialog->eventCancelClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationCancel); return; } } @@ -313,6 +326,12 @@ namespace MWGui fillSaveList(); } + void SaveGameDialog::onCharacterAccept(MyGUI::ComboBox* sender, size_t pos) + { + // Give key focus to save list so we can confirm the selection with Enter + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + } + void SaveGameDialog::fillSaveList() { mSaveList->removeAllItems(); @@ -327,8 +346,6 @@ namespace MWGui { mSaveList->setIndexSelected(0); onSlotSelected(mSaveList, 0); - // Give key focus to save list so we can confirm the selection with Enter - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); } else onSlotSelected(mSaveList, MyGUI::ITEM_NONE); diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index be0e47cab..0a87b6600 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -17,9 +17,7 @@ namespace MWGui public: SaveGameDialog(); - virtual void open(); - - virtual void exit(); + virtual void onOpen(); void setLoadOrSave(bool load); @@ -31,6 +29,7 @@ namespace MWGui void onOkButtonClicked (MyGUI::Widget* sender); void onDeleteButtonClicked (MyGUI::Widget* sender); void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); + void onCharacterAccept(MyGUI::ComboBox* sender, size_t pos); // Slot selected (mouse click or arrow keys) void onSlotSelected (MyGUI::ListBox* sender, size_t pos); // Slot activated (double click or enter key) @@ -39,10 +38,12 @@ namespace MWGui void onSlotMouseClick(MyGUI::ListBox* sender, size_t pos); void onDeleteSlotConfirmed(); + void onDeleteSlotCancel(); void onEditSelectAccept (MyGUI::EditBox* sender); void onSaveNameChanged (MyGUI::EditBox* sender); void onConfirmationGiven(); + void onConfirmationCancel(); void accept(bool reallySure=false); diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index be0346e88..9a9a1ae01 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -2,16 +2,18 @@ #include #include +#include namespace MWGui { - FadeOp::FadeOp(ScreenFader * fader, float time, float targetAlpha) + FadeOp::FadeOp(ScreenFader * fader, float time, float targetAlpha, float delay) : mFader(fader), - mRemainingTime(time), + mRemainingTime(time+delay), mTargetTime(time), mTargetAlpha(targetAlpha), mStartAlpha(0.f), + mDelay(delay), mRunning(false) { } @@ -26,7 +28,7 @@ namespace MWGui if (mRunning) return; - mRemainingTime = mTargetTime; + mRemainingTime = mTargetTime + mDelay; mStartAlpha = mFader->getCurrentAlpha(); mRunning = true; } @@ -42,6 +44,12 @@ namespace MWGui return; } + if (mRemainingTime > mTargetTime) + { + mRemainingTime -= dt; + return; + } + float currentAlpha = mFader->getCurrentAlpha(); if (mStartAlpha > mTargetAlpha) { @@ -73,8 +81,9 @@ namespace MWGui , mFactor(1.f) , mRepeat(false) { + MyGUI::Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &ScreenFader::onFrameStart); + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - setVisible(false); MyGUI::ImageBox* imageBox = mMainWidget->castType(false); if (imageBox) @@ -88,7 +97,12 @@ namespace MWGui } } - void ScreenFader::update(float dt) + ScreenFader::~ScreenFader() + { + MyGUI::Gui::getInstance().eventFrameStart -= MyGUI::newDelegate(this, &ScreenFader::onFrameStart); + } + + void ScreenFader::onFrameStart(float dt) { if (!mQueue.empty()) { @@ -104,19 +118,25 @@ namespace MWGui mMainWidget->setAlpha(1.f-((1.f-mCurrentAlpha) * mFactor)); } - void ScreenFader::fadeIn(float time) + void ScreenFader::fadeIn(float time, float delay) + { + queue(time, 1.f, delay); + } + + void ScreenFader::fadeOut(const float time, float delay) { - queue(time, 1.f); + queue(time, 0.f, delay); } - void ScreenFader::fadeOut(const float time) + void ScreenFader::fadeTo(const int percent, const float time, float delay) { - queue(time, 0.f); + queue(time, percent/100.f, delay); } - void ScreenFader::fadeTo(const int percent, const float time) + void ScreenFader::clear() { - queue(time, percent/100.f); + clearQueue(); + notifyAlphaChanged(0.f); } void ScreenFader::setFactor(float factor) @@ -130,7 +150,7 @@ namespace MWGui mRepeat = repeat; } - void ScreenFader::queue(float time, float targetAlpha) + void ScreenFader::queue(float time, float targetAlpha, float delay) { if (time < 0.f) return; @@ -142,7 +162,7 @@ namespace MWGui return; } - mQueue.push_back(FadeOp::Ptr(new FadeOp(this, time, targetAlpha))); + mQueue.push_back(FadeOp::Ptr(new FadeOp(this, time, targetAlpha, delay))); } bool ScreenFader::isEmpty() diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp index b510e133a..79bea30e5 100644 --- a/apps/openmw/mwgui/screenfader.hpp +++ b/apps/openmw/mwgui/screenfader.hpp @@ -14,7 +14,7 @@ namespace MWGui public: typedef std::shared_ptr Ptr; - FadeOp(ScreenFader * fader, float time, float targetAlpha); + FadeOp(ScreenFader * fader, float time, float targetAlpha, float delay); bool isRunning(); @@ -28,6 +28,7 @@ namespace MWGui float mTargetTime; float mTargetAlpha; float mStartAlpha; + float mDelay; bool mRunning; }; @@ -35,17 +36,20 @@ namespace MWGui { public: ScreenFader(const std::string & texturePath, const std::string& layout = "openmw_screen_fader.layout", const MyGUI::FloatCoord& texCoordOverride = MyGUI::FloatCoord(0,0,1,1)); + ~ScreenFader(); - void update(float dt); + void onFrameStart(float dt); + + void fadeIn(const float time, float delay=0); + void fadeOut(const float time, float delay=0); + void fadeTo(const int percent, const float time, float delay=0); - void fadeIn(const float time); - void fadeOut(const float time); - void fadeTo(const int percent, const float time); + void clear(); void setFactor (float factor); void setRepeat(bool repeat); - void queue(float time, float targetAlpha); + void queue(float time, float targetAlpha, float delay); bool isEmpty(); void clearQueue(); diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index a5d4c3324..ea811f5b1 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -12,6 +12,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/class.hpp" #include "formatting.hpp" @@ -46,16 +47,19 @@ namespace MWGui adjustButton(mCloseButton); adjustButton(mTakeButton); + mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ScrollWindow::onKeyButtonPressed); + mTakeButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ScrollWindow::onKeyButtonPressed); + center(); } - void ScrollWindow::openScroll (MWWorld::Ptr scroll, bool showTakeButton) + void ScrollWindow::setPtr (const MWWorld::Ptr& scroll) { - // no 3d sounds because the object could be in a container. - MWBase::Environment::get().getWindowManager()->playSound("scroll"); - mScroll = scroll; + MWWorld::Ptr player = MWMechanics::getPlayer(); + bool showTakeButton = scroll.getContainerStore() != &player.getClass().getContainerStore(player); + MWWorld::LiveCellRef *ref = mScroll.get(); Formatting::BookFormatter formatter; @@ -65,21 +69,28 @@ namespace MWGui // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mTextView->setVisibleVScroll(false); if (size.height > mTextView->getSize().height) - mTextView->setCanvasSize(MyGUI::IntSize(410, size.height)); + mTextView->setCanvasSize(mTextView->getWidth(), size.height); else - mTextView->setCanvasSize(410, mTextView->getSize().height); + mTextView->setCanvasSize(mTextView->getWidth(), mTextView->getSize().height); mTextView->setVisibleVScroll(true); mTextView->setViewOffset(MyGUI::IntPoint(0,0)); setTakeButtonShow(showTakeButton); + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); } - void ScrollWindow::exit() + void ScrollWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) { - MWBase::Environment::get().getWindowManager()->playSound("scroll"); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); + int scroll = 0; + if (key == MyGUI::KeyCode::ArrowUp) + scroll = 40; + else if (key == MyGUI::KeyCode::ArrowDown) + scroll = -40; + + if (scroll != 0) + mTextView->setViewOffset(mTextView->getViewOffset() + MyGUI::IntPoint(0, scroll)); } void ScrollWindow::setTakeButtonShow(bool show) @@ -96,7 +107,7 @@ namespace MWGui void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); } void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender) @@ -106,6 +117,6 @@ namespace MWGui MWWorld::ActionTake take(mScroll); take.execute (MWMechanics::getPlayer()); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll, true); } } diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index 961f1b675..adc0bd93e 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -17,14 +17,16 @@ namespace MWGui public: ScrollWindow (); - void openScroll (MWWorld::Ptr scroll, bool showTakeButton); - virtual void exit(); + void setPtr (const MWWorld::Ptr& scroll); void setInventoryAllowed(bool allowed); + void onResChange(int, int) { center(); } + protected: void onCloseButtonClicked (MyGUI::Widget* _sender); void onTakeButtonClicked (MyGUI::Widget* _sender); void setTakeButtonShow(bool show); + void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); private: Gui::ImageButton* mCloseButton; diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index f46199c80..9bf6e4385 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -258,7 +258,7 @@ namespace MWGui void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); } void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index) @@ -353,24 +353,31 @@ namespace MWGui } bool supported = false; + int fallbackX = 0, fallbackY = 0; for (unsigned int i=0; igetItemCount(); ++i) { std::string resStr = mResolutionList->getItemNameAt(i); int resX, resY; parseResolution (resX, resY, resStr); + if (i == 0) + { + fallbackX = resX; + fallbackY = resY; + } + if (resX == Settings::Manager::getInt("resolution x", "Video") && resY == Settings::Manager::getInt("resolution y", "Video")) supported = true; } - if (!supported) + if (!supported && mResolutionList->getItemCount()) { - std::string msg = "This resolution is not supported in Fullscreen mode. Please select a resolution from the list."; - MWBase::Environment::get().getWindowManager()-> - messageBox(msg); - _sender->castType()->setCaption(off); - return; + if (fallbackX != 0 && fallbackY != 0) + { + Settings::Manager::setInt("resolution x", "Video", fallbackX); + Settings::Manager::setInt("resolution y", "Video", fallbackY); + } } mWindowBorderButton->setEnabled(!newState); @@ -553,17 +560,12 @@ namespace MWGui updateControlsBox (); } - void SettingsWindow::open() + void SettingsWindow::onOpen() { updateControlsBox (); resetScrollbars(); } - void SettingsWindow::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); - } - void SettingsWindow::onWindowResize(MyGUI::Window *_sender) { layoutControlsBox(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 2ac06dcaa..c6e48819c 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -15,12 +15,12 @@ namespace MWGui public: SettingsWindow(); - virtual void open(); - - virtual void exit(); + virtual void onOpen(); void updateControlsBox(); + void onResChange(int, int) { center(); } + protected: MyGUI::TabControl* mSettingsTab; MyGUI::Button* mOkButton; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 68d95fb88..1ee3cf631 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -59,15 +59,91 @@ namespace if (mSortByType && left.mType != right.mType) return left.mType < right.mType; - if (left.mBase.getTypeName() == right.mBase.getTypeName()) + float result = 0; + + // compare items by type + std::string leftName = left.mBase.getTypeName(); + std::string rightName = right.mBase.getTypeName(); + + if (leftName != rightName) + return compareType(leftName, rightName); + + // compare items by name + leftName = Misc::StringUtils::lowerCase(left.mBase.getClass().getName(left.mBase)); + rightName = Misc::StringUtils::lowerCase(right.mBase.getClass().getName(right.mBase)); + + result = leftName.compare(rightName); + if (result != 0) + return result < 0; + + // compare items by enchantment: + // 1. enchanted items showed before non-enchanted + // 2. item with lesser charge percent comes after items with more charge percent + // 3. item with constant effect comes before items with non-constant effects + int leftChargePercent = -1; + int rightChargePercent = -1; + leftName = left.mBase.getClass().getEnchantment(left.mBase); + rightName = right.mBase.getClass().getEnchantment(right.mBase); + + if (!leftName.empty()) + { + const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); + if (ench) + { + if (ench->mData.mType == ESM::Enchantment::ConstantEffect) + leftChargePercent = 101; + else + leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100 + : static_cast(left.mBase.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + } + } + + if (!rightName.empty()) { - std::string leftName = Misc::StringUtils::lowerCase(left.mBase.getClass().getName(left.mBase)); - std::string rightName = Misc::StringUtils::lowerCase(right.mBase.getClass().getName(right.mBase)); + const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); + if (ench) + { + if (ench->mData.mType == ESM::Enchantment::ConstantEffect) + rightChargePercent = 101; + else + rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100 + : static_cast(right.mBase.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + } + } - return leftName.compare(rightName) < 0; + result = leftChargePercent - rightChargePercent; + if (result != 0) + return result > 0; + + // compare items by condition + if (left.mBase.getClass().hasItemHealth(left.mBase) && right.mBase.getClass().hasItemHealth(right.mBase)) + { + result = left.mBase.getClass().getItemHealth(left.mBase) - right.mBase.getClass().getItemHealth(right.mBase); + if (result != 0) + return result > 0; } - else - return compareType(left.mBase.getTypeName(), right.mBase.getTypeName()); + + // compare items by remaining usage time + result = left.mBase.getClass().getRemainingUsageTime(left.mBase) - right.mBase.getClass().getRemainingUsageTime(right.mBase); + if (result != 0) + return result > 0; + + // compare items by value + result = left.mBase.getClass().getValue(left.mBase) - right.mBase.getClass().getValue(right.mBase); + if (result != 0) + return result > 0; + + // compare items by weight + result = left.mBase.getClass().getWeight(left.mBase) - right.mBase.getClass().getWeight(right.mBase); + if (result != 0) + return result > 0; + + // compare items by Id + leftName = left.mBase.getCellRef().getRefId(); + rightName = right.mBase.getCellRef().getRefId(); + + result = leftName.compare(rightName); + return result < 0; } }; } @@ -83,6 +159,11 @@ namespace MWGui mSourceModel = sourceModel; } + bool SortFilterItemModel::allowedToUseItems() const + { + return mSourceModel->allowedToUseItems(); + } + void SortFilterItemModel::addDragItem (const MWWorld::Ptr& dragItem, size_t count) { mDragItems.push_back(std::make_pair(dragItem, count)); @@ -230,4 +311,18 @@ namespace MWGui std::sort(mItems.begin(), mItems.end(), cmp); } + void SortFilterItemModel::onClose() + { + mSourceModel->onClose(); + } + + bool SortFilterItemModel::onDropItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onDropItem(item, count); + } + + bool SortFilterItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onTakeItem(item, count); + } } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 4731cbb8a..98da8d8c9 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -15,6 +15,7 @@ namespace MWGui bool filterAccepts (const ItemStack& item); + bool allowedToUseItems() const; virtual ItemStack getItem (ModelIndex index); virtual size_t getItemCount(); @@ -28,6 +29,10 @@ namespace MWGui /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } + void onClose(); + bool onDropItem(const MWWorld::Ptr &item, int count); + bool onTakeItem(const MWWorld::Ptr &item, int count); + static const int Category_Weapon = (1<<1); static const int Category_Apparel = (1<<2); static const int Category_Misc = (1<<3); diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp index 0232eb7b3..104c81eab 100644 --- a/apps/openmw/mwgui/soulgemdialog.cpp +++ b/apps/openmw/mwgui/soulgemdialog.cpp @@ -22,13 +22,11 @@ namespace MWGui { if (button == 0) { - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge); - MWBase::Environment::get().getWindowManager()->startRecharge(mSoulgem); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge, mSoulgem); } else { - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting); - MWBase::Environment::get().getWindowManager()->startSelfEnchanting(mSoulgem); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mSoulgem); } } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index cf991c85f..b238b9081 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -42,11 +42,6 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked); } - void SpellBuyingWindow::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying); - } - bool SpellBuyingWindow::sortSpells (const ESM::Spell* left, const ESM::Spell* right) { std::string leftName = Misc::StringUtils::lowerCase(left->mName); @@ -99,7 +94,12 @@ namespace MWGui mSpellsWidgetMap.clear(); } - void SpellBuyingWindow::startSpellBuying(const MWWorld::Ptr& actor, int startOffset) + void SpellBuyingWindow::setPtr(const MWWorld::Ptr &actor) + { + setPtr(actor, 0); + } + + void SpellBuyingWindow::setPtr(const MWWorld::Ptr& actor, int startOffset) { center(); mPtr = actor; @@ -183,14 +183,14 @@ namespace MWGui MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); - startSpellBuying(mPtr, mSpellsView->getViewOffset().top); + setPtr(mPtr, mSpellsView->getViewOffset().top); MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); } void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellBuying); } void SpellBuyingWindow::updateLabels() @@ -209,7 +209,7 @@ namespace MWGui { // remove both Spells and Dialogue (since you always trade with the NPC/creature that you have previously talked to) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } void SpellBuyingWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 37210819f..3414c1b94 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -25,9 +25,13 @@ namespace MWGui public: SpellBuyingWindow(); - void startSpellBuying(const MWWorld::Ptr& actor, int startOffset); + void setPtr(const MWWorld::Ptr& actor); + void setPtr(const MWWorld::Ptr& actor, int startOffset); - virtual void exit(); + void onFrame(float dt) { checkReferenceAvailable(); } + void clear() { resetReference(); } + + void onResChange(int, int) { center(); } protected: MyGUI::Button* mCancelButton; @@ -42,7 +46,7 @@ namespace MWGui void onMouseWheel(MyGUI::Widget* _sender, int _rel); void addSpell(const ESM::Spell& spell); void clearSpells(); - int mLastPos,mCurrentY; + int mCurrentY; static const int sLineHeight; diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 8e3d55cb6..14fc6f09e 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -107,19 +107,19 @@ namespace MWGui mConstantEffect = constant; } - void EditEffectDialog::open() + void EditEffectDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); center(); } - void EditEffectDialog::exit() + bool EditEffectDialog::exit() { - setVisible(false); if(mEditing) eventEffectModified(mOldEffect); else eventEffectRemoved(mEffect); + return true; } void EditEffectDialog::newEffect (const ESM::MagicEffect *effect) @@ -286,6 +286,7 @@ namespace MWGui void EditEffectDialog::onCancelButtonClicked (MyGUI::Widget* sender) { + setVisible(false); exit(); } @@ -359,11 +360,12 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked); mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked); + mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept); setWidgets(mAvailableEffectsList, mUsedEffectsView); } - void SpellCreationDialog::startSpellMaking (MWWorld::Ptr actor) + void SpellCreationDialog::setPtr (const MWWorld::Ptr& actor) { mPtr = actor; mNameEdit->setCaption(""); @@ -373,7 +375,7 @@ namespace MWGui void SpellCreationDialog::onCancelButtonClicked (MyGUI::Widget* sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation); } void SpellCreationDialog::onBuyButtonClicked (MyGUI::Widget* sender) @@ -440,14 +442,15 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation); } - void SpellCreationDialog::open() + void SpellCreationDialog::onAccept(MyGUI::EditBox *sender) { - center(); + onBuyButtonClicked(sender); } - void SpellCreationDialog::exit() + void SpellCreationDialog::onOpen() { - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation); + center(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); } void SpellCreationDialog::onReferenceUnavailable () diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 6cdf74d10..ec90fa3ce 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -23,8 +23,8 @@ namespace MWGui public: EditEffectDialog(); - virtual void open(); - virtual void exit(); + virtual void onOpen(); + virtual bool exit(); void setConstantEffect(bool constant); @@ -150,16 +150,19 @@ namespace MWGui public: SpellCreationDialog(); - virtual void open(); - virtual void exit(); + virtual void onOpen(); + void clear() { resetReference(); } - void startSpellMaking(MWWorld::Ptr actor); + void onFrame(float dt) { checkReferenceAvailable(); } + + void setPtr(const MWWorld::Ptr& actor); protected: virtual void onReferenceUnavailable (); void onCancelButtonClicked (MyGUI::Widget* sender); void onBuyButtonClicked (MyGUI::Widget* sender); + void onAccept(MyGUI::EditBox* sender); virtual void notifyEffectsChanged (); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 6953d682b..f83b72096 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -60,7 +60,7 @@ namespace MWGui { newSpell.mType = Spell::Type_Spell; std::string cost = std::to_string(spell->mData.mCost); - std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor))); + std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, NULL, true, true))); newSpell.mCostColumn = cost + "/" + chance; } else diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index ebda8873c..4268b33a0 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -106,6 +106,7 @@ namespace MWGui Gui::SharedStateButton* t = mScrollView->createWidget(skin, MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setNeedKeyFocus(true); t->setCaption(spell.mName); t->setTextAlign(MyGUI::Align::Left); adjustSpellWidget(spell, i, t); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 0ae16fffa..1f9e65624 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -73,22 +73,19 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); } - void SpellWindow::open() + void SpellWindow::onOpen() { updateSpells(); } void SpellWindow::onFrame(float dt) - { - if (mMainWidget->getVisible()) + { + NoDrop::onFrame(dt); + mUpdateTimer += dt; + if (0.5f < mUpdateTimer) { - NoDrop::onFrame(dt); - mUpdateTimer += dt; - if (0.5f < mUpdateTimer) - { - mUpdateTimer = 0; - mSpellView->incrementalUpdate(); - } + mUpdateTimer = 0; + mSpellView->incrementalUpdate(); } } diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index dcce10f9d..f8fead9ea 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -37,7 +37,7 @@ namespace MWGui virtual void onPinToggled(); virtual void onTitleDoubleClicked(); - virtual void open(); + virtual void onOpen(); SpellView* mSpellView; SpellIcons* mSpellIcons; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 17e51e338..d3d487579 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -105,7 +105,7 @@ namespace MWGui std::stringstream out; out << val << "/" << max; - setText(tname, out.str().c_str()); + setText(tname, out.str()); pt->setProgressRange(std::max(0, max)); pt->setProgressPosition(std::max(0, val)); @@ -297,9 +297,6 @@ namespace MWGui void StatsWindow::onFrame (float dt) { - if (!mMainWidget->getVisible()) - return; - NoDrop::onFrame(dt); MWWorld::Ptr player = MWMechanics::getPlayer(); diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 1fe3cb68d..487953e00 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -37,7 +37,7 @@ namespace MWGui void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } void updateSkillArea(); - virtual void open() { onWindowResize(mMainWidget->castType()); } + virtual void onOpen() { onWindowResize(mMainWidget->castType()); } private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 958b52dc0..169c9e742 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -42,9 +42,9 @@ namespace MWGui setText("LabelT", label); } - void TextInputDialog::open() + void TextInputDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 57dd34dd6..56c6632e1 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -20,7 +20,9 @@ namespace MWGui void setNextButtonShow(bool shown); void setTextLabel(const std::string &label); - virtual void open(); + virtual void onOpen(); + + bool exit() { return false; } /** Event : Dialog finished, OK button clicked.\n signature : void method()\n diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 9b89c3957..b2991a034 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -97,6 +97,8 @@ namespace MWGui if (guiMode) { + if (!MWBase::Environment::get().getWindowManager()->getCursorVisible()) + return; const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); if (MWBase::Environment::get().getWindowManager()->getWorldMouseOver() && ((MWBase::Environment::get().getWindowManager()->getMode() == GM_Console) @@ -118,7 +120,7 @@ namespace MWGui if (info.caption.empty()) info.caption=mFocusObject.getCellRef().getRefId(); info.icon=""; - tooltipSize = createToolTip(info, true); + tooltipSize = createToolTip(info, checkOwned()); } else tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); @@ -184,22 +186,23 @@ namespace MWGui ToolTipInfo info; info.text = data.caption; info.notes = data.notes; - tooltipSize = createToolTip(info, false); + tooltipSize = createToolTip(info); } else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") { std::pair pair = *focus->getUserData >(); mFocusObject = pair.second->getItem(pair.first).mBase; - tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false); + bool isAllowedToUse = pair.second->allowedToUseItems(); + tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false, !isAllowedToUse); } else if (type == "ToolTipInfo") { - tooltipSize = createToolTip(*focus->getUserData(), false); + tooltipSize = createToolTip(*focus->getUserData()); } else if (type == "AvatarItemSelection") { @@ -242,7 +245,7 @@ namespace MWGui info.text = "#{sSchool}: " + sSchoolNames[school]; } info.effects = effects; - tooltipSize = createToolTip(info, false); + tooltipSize = createToolTip(info); } else if (type == "Layout") { @@ -296,7 +299,7 @@ namespace MWGui { if (!mFocusObject.isEmpty()) { - MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount()); + MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true, checkOwned()); setCoord(viewSize.width/2 - tooltipSize.width/2, std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), @@ -323,14 +326,14 @@ namespace MWGui } } - void ToolTips::setFocusObject(const MWWorld::ConstPtr& focus) + void ToolTips::setFocusObject(const MWWorld::Ptr& focus) { mFocusObject = focus; update(mFrameDuration); } - MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image) + MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image, bool isOwned) { // this the maximum width of the tooltip before it starts word-wrapping setCoord(0, 0, 300, 300); @@ -349,7 +352,7 @@ namespace MWGui ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); if (!image) info.icon = ""; - tooltipSize = createToolTip(info, true); + tooltipSize = createToolTip(info, isOwned); } return tooltipSize; @@ -357,27 +360,21 @@ namespace MWGui bool ToolTips::checkOwned() { - if(!mFocusObject.isEmpty()) - { - MWWorld::Ptr ptr = MWMechanics::getPlayer(); - MWWorld::Ptr victim; - - MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); - bool allowed = mm->isAllowedToUse(ptr, mFocusObject, victim); - - return !allowed; - } - else - { + if(mFocusObject.isEmpty()) return false; - } + + MWWorld::Ptr ptr = MWMechanics::getPlayer(); + MWWorld::Ptr victim; + + MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); + return !mm->isAllowedToUse(ptr, mFocusObject, victim); } - MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isFocusObject) + MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isOwned) { mDynamicToolTipBox->setVisible(true); - if((mShowOwned == 1 || mShowOwned == 3) && isFocusObject && checkOwned()) + if((mShowOwned == 1 || mShowOwned == 3) && isOwned) mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp_Owned" : "HUD_Box_Owned"); else mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp" : "HUD_Box"); @@ -422,18 +419,20 @@ namespace MWGui std::string realImage = MWBase::Environment::get().getWindowManager()->correctIconPath(image); MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); - captionWidget->setProperty("Static", "true"); + captionWidget->setEditStatic(true); + captionWidget->setNeedKeyFocus(false); captionWidget->setCaptionWithReplacing(caption); MyGUI::IntSize captionSize = captionWidget->getTextSize(); int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); MyGUI::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText"); - textWidget->setProperty("Static", "true"); - textWidget->setProperty("MultiLine", "true"); - textWidget->setProperty("WordWrap", info.wordWrap ? "true" : "false"); + textWidget->setEditStatic(true); + textWidget->setEditMultiLine(true); + textWidget->setEditWordWrap(info.wordWrap); textWidget->setCaptionWithReplacing(text); textWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top); + textWidget->setNeedKeyFocus(false); MyGUI::IntSize textSize = textWidget->getTextSize(); captionSize += MyGUI::IntSize(imageSize, 0); // adjust for image diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index bb2affc63..1ef473b5a 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -59,7 +59,7 @@ namespace MWGui void setDelay(float delay); - void setFocusObject(const MWWorld::ConstPtr& focus); + void setFocusObject(const MWWorld::Ptr& 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 @@ -96,12 +96,12 @@ namespace MWGui private: MyGUI::Widget* mDynamicToolTipBox; - MWWorld::ConstPtr mFocusObject; + MWWorld::Ptr mFocusObject; - MyGUI::IntSize getToolTipViaPtr (int count, bool image=true); + MyGUI::IntSize getToolTipViaPtr (int count, bool image = true, bool isOwned = false); ///< @return requested tooltip size - MyGUI::IntSize createToolTip(const ToolTipInfo& info, bool isFocusObject); + MyGUI::IntSize createToolTip(const ToolTipInfo& info, bool isOwned = false); ///< @return requested tooltip size /// @param isFocusObject Is the object this tooltips originates from mFocusObject? diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index ae3feb61c..a26294a0e 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -15,6 +15,11 @@ namespace MWGui mSourceModel = sourceModel; } + bool TradeItemModel::allowedToUseItems() const + { + return true; + } + ItemStack TradeItemModel::getItem (ModelIndex index) { if (index < 0) diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp index 1bfee9b2a..cdb949c49 100644 --- a/apps/openmw/mwgui/tradeitemmodel.hpp +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -15,6 +15,8 @@ namespace MWGui public: TradeItemModel (ItemModel* sourceModel, const MWWorld::Ptr& merchant); + bool allowedToUseItems() const; + virtual ItemStack getItem (ModelIndex index); virtual size_t getItemCount(); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 34b1dc473..033d55e0f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -38,7 +38,6 @@ #include "containeritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" -#include "dialogue.hpp" #include "controllers.hpp" namespace @@ -107,6 +106,7 @@ namespace MWGui mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); mTotalBalance->eventValueChanged += MyGUI::newDelegate(this, &TradeWindow::onBalanceValueChanged); + mTotalBalance->eventEditSelectAccept += MyGUI::newDelegate(this, &TradeWindow::onAccept); mTotalBalance->setMinValue(INT_MIN+1); // disallow INT_MIN since abs(INT_MIN) is undefined setCoord(400, 0, 400, 300); @@ -126,7 +126,7 @@ namespace MWGui } } - void TradeWindow::startTrade(const MWWorld::Ptr& actor) + void TradeWindow::setPtr(const MWWorld::Ptr& actor) { mPtr = actor; @@ -151,6 +151,13 @@ namespace MWGui setTitle(actor.getClass().getName(actor)); onFilterChanged(mFilterAll); + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTotalBalance); + } + + void TradeWindow::onFrame(float dt) + { + checkReferenceAvailable(); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) @@ -182,11 +189,11 @@ namespace MWGui return mPtr.getClass().getServices(mPtr); } - void TradeWindow::exit() + bool TradeWindow::exit() { mTradeModel->abort(); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->abort(); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); + return true; } void TradeWindow::onItemSelected (int index) @@ -326,7 +333,7 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, it->mBase, mPtr, it->mCount); onCancelButtonClicked(mCancelButton); - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return; } } @@ -371,8 +378,7 @@ namespace MWGui End of tes3mp addition */ - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse( - MWBase::Environment::get().getWorld()->getStore().get().find("sBarterDialog5")->getString()); + eventTradeDone(); MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); @@ -380,9 +386,15 @@ namespace MWGui restock(); } + void TradeWindow::onAccept(MyGUI::EditBox *sender) + { + onOfferButtonClicked(sender); + } + void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } void TradeWindow::onMaxSaleButtonClicked(MyGUI::Widget* _sender) @@ -511,7 +523,7 @@ namespace MWGui { // remove both Trade and Dialogue (since you always trade with the NPC/creature that you have previously talked to) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } int TradeWindow::getMerchantGold() diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 4b03c8d90..514d24022 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -27,17 +27,23 @@ namespace MWGui public: TradeWindow(); - void startTrade(const MWWorld::Ptr& actor); + void setPtr(const MWWorld::Ptr& actor); + + void onFrame(float dt); + void clear() { resetReference(); } void borrowItem (int index, size_t count); void returnItem (int index, size_t count); int getMerchantServices(); - virtual void exit(); + virtual bool exit(); virtual void resetReference(); + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_TradeDone; + EventHandle_TradeDone eventTradeDone; + private: ItemView* mItemView; SortFilterItemModel* mSortModel; @@ -81,6 +87,7 @@ namespace MWGui void onFilterChanged(MyGUI::Widget* _sender); void onOfferButtonClicked(MyGUI::Widget* _sender); + void onAccept(MyGUI::EditBox* sender); void onCancelButtonClicked(MyGUI::Widget* _sender); void onMaxSaleButtonClicked(MyGUI::Widget* _sender); void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 5c4980de8..c6d1285f3 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -17,7 +17,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -51,7 +50,6 @@ namespace MWGui TrainingWindow::TrainingWindow() : WindowBase("openmw_trainingwindow.layout") - , mFadeTimeRemaining(0) , mTimeAdvancer(0.05f) { getWidget(mTrainingOptions, "TrainingOptions"); @@ -62,21 +60,22 @@ namespace MWGui mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &TrainingWindow::onTrainingProgressChanged); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &TrainingWindow::onTrainingFinished); - - mProgressBar.setVisible(false); } - void TrainingWindow::open() + void TrainingWindow::onOpen() { - center(); - } + if (mTimeAdvancer.isRunning()) + { + mProgressBar.setVisible(true); + setVisible(false); + } + else + mProgressBar.setVisible(false); - void TrainingWindow::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); + center(); } - void TrainingWindow::startTraining (MWWorld::Ptr actor) + void TrainingWindow::setPtr (const MWWorld::Ptr& actor) { mPtr = actor; @@ -135,7 +134,7 @@ namespace MWGui void TrainingWindow::onCancelButtonClicked (MyGUI::Widget *sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training); } void TrainingWindow::onTrainingSelected (MyGUI::Widget *sender) @@ -192,21 +191,18 @@ namespace MWGui // add gold to NPC trading gold pool npcStats.setGoldPool(npcStats.getGoldPool() + price); - // go back to game mode - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); - // advance time MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getWorld ()->advanceTime (2); + setVisible(false); mProgressBar.setVisible(true); mProgressBar.setProgress(0, 2); mTimeAdvancer.run(2); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.25); - mFadeTimeRemaining = 0.5; + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.25, false, 0.25); } void TrainingWindow::onTrainingProgressChanged(int cur, int total) @@ -217,18 +213,21 @@ namespace MWGui void TrainingWindow::onTrainingFinished() { mProgressBar.setVisible(false); + + // go back to game mode + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } void TrainingWindow::onFrame(float dt) { + checkReferenceAvailable(); mTimeAdvancer.onFrame(dt); + } - if (mFadeTimeRemaining <= 0) - return; - - mFadeTimeRemaining -= dt; - - if (mFadeTimeRemaining <= 0) - MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.25); + bool TrainingWindow::exit() + { + return !mTimeAdvancer.isRunning(); } + } diff --git a/apps/openmw/mwgui/trainingwindow.hpp b/apps/openmw/mwgui/trainingwindow.hpp index 7c4582062..2edad1f27 100644 --- a/apps/openmw/mwgui/trainingwindow.hpp +++ b/apps/openmw/mwgui/trainingwindow.hpp @@ -14,14 +14,18 @@ namespace MWGui public: TrainingWindow(); - virtual void open(); + virtual void onOpen(); - virtual void exit(); + bool exit(); - void startTraining(MWWorld::Ptr actor); + void setPtr(const MWWorld::Ptr& actor); void onFrame(float dt); + WindowBase* getProgressBar() { return &mProgressBar; } + + void clear() { resetReference(); } + protected: virtual void onReferenceUnavailable (); @@ -35,8 +39,6 @@ namespace MWGui MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; - float mFadeTimeRemaining; - WaitDialogProgressBar mProgressBar; TimeAdvancer mTimeAdvancer; }; diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 3a7afe497..7586bb66a 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -8,7 +8,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" @@ -45,11 +44,6 @@ namespace MWGui mSelect->getHeight()); } - void TravelWindow::exit() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); - } - void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) { int price; @@ -108,7 +102,7 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mDestinationsView->getChildAt(0)); } - void TravelWindow::startTravel(const MWWorld::Ptr& actor) + void TravelWindow::setPtr(const MWWorld::Ptr& actor) { center(); mPtr = actor; @@ -182,7 +176,9 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + + MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); // Teleports any followers, too. MWWorld::ActionTeleport action(interior ? cellname : "", pos, true); @@ -194,7 +190,7 @@ namespace MWGui void TravelWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); } void TravelWindow::updateLabels() @@ -212,7 +208,7 @@ namespace MWGui void TravelWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } void TravelWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 3230f897f..5ae466047 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -24,9 +24,7 @@ namespace MWGui public: TravelWindow(); - virtual void exit(); - - void startTravel(const MWWorld::Ptr& actor); + void setPtr (const MWWorld::Ptr& actor); protected: MyGUI::Button* mCancelButton; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index ba58a9c69..61febf315 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -1,6 +1,8 @@ #include "waitdialog.hpp" #include +#include +#include #include @@ -23,8 +25,6 @@ #include "../mwstate/charactermanager.hpp" -#include "widgets.hpp" - namespace MWGui { @@ -35,7 +35,7 @@ namespace MWGui getWidget(mProgressText, "ProgressText"); } - void WaitDialogProgressBar::open() + void WaitDialogProgressBar::onOpen() { center(); } @@ -72,21 +72,49 @@ namespace MWGui mWaitButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onWaitButtonClicked); mHourSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &WaitDialog::onHourSliderChangedPosition); + mCancelButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &WaitDialog::onKeyButtonPressed); + mWaitButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &WaitDialog::onKeyButtonPressed); + mUntilHealedButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &WaitDialog::onKeyButtonPressed); + mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &WaitDialog::onWaitingProgressChanged); mTimeAdvancer.eventInterrupted += MyGUI::newDelegate(this, &WaitDialog::onWaitingInterrupted); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &WaitDialog::onWaitingFinished); + } - mProgressBar.setVisible (false); + void WaitDialog::setPtr(const MWWorld::Ptr &ptr) + { + setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == 0); + + if (mUntilHealedButton->getVisible()) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mUntilHealedButton); + else + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); } - void WaitDialog::exit() + bool WaitDialog::exit() { - if(!mProgressBar.isVisible()) //Only exit if not currently waiting - MWBase::Environment::get().getWindowManager()->popGuiMode(); + return (!mTimeAdvancer.isRunning()); //Only exit if not currently waiting } - void WaitDialog::open() + void WaitDialog::clear() { + mSleeping = false; + mTimeAdvancer.stop(); + } + + void WaitDialog::onOpen() + { + if (mTimeAdvancer.isRunning()) + { + mProgressBar.setVisible(true); + setVisible(false); + return; + } + else + { + mProgressBar.setVisible(false); + } + if (!MWBase::Environment::get().getWindowManager ()->getRestEnabled ()) { MWBase::Environment::get().getWindowManager()->popGuiMode (); @@ -101,8 +129,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->popGuiMode (); } - setCanRest(canRest == 0); - onHourSliderChangedPosition(mHourSlider, 0); mHourSlider->setScrollPosition (0); @@ -177,13 +203,25 @@ namespace MWGui void WaitDialog::onCancelButtonClicked(MyGUI::Widget* sender) { - exit(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); } void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position) { mHourText->setCaptionWithReplacing (MyGUI::utility::toString(position+1) + " #{sRestMenu2}"); mManualHours = position+1; + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); + } + + void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) + { + if (key == MyGUI::KeyCode::ArrowDown) + mHourSlider->setScrollPosition(std::min(mHourSlider->getScrollPosition()+1, mHourSlider->getScrollRange()-1)); + else if (key == MyGUI::KeyCode::ArrowUp) + mHourSlider->setScrollPosition(std::max(static_cast(mHourSlider->getScrollPosition())-1, 0)); + else + return; + onHourSliderChangedPosition(mHourSlider, mHourSlider->getScrollPosition()); } void WaitDialog::onWaitingProgressChanged(int cur, int total) @@ -265,7 +303,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f); mProgressBar.setVisible (false); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed); mTimeAdvancer.stop(); } diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index 13df1f8ae..2aecb002f 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -13,7 +13,7 @@ namespace MWGui public: WaitDialogProgressBar(); - virtual void open(); + virtual void onOpen(); void setProgress(int cur, int total); @@ -27,18 +27,22 @@ namespace MWGui public: WaitDialog(); - virtual void open(); + void setPtr(const MWWorld::Ptr &ptr); - virtual void exit(); + virtual void onOpen(); - void onFrame(float dt); + virtual bool exit(); + + virtual void clear(); - void bedActivated() { setCanRest(true); } + void onFrame(float dt); bool getSleeping() { return mTimeAdvancer.isRunning() && mSleeping; } void wakeUp(); void autosave(); + WindowBase* getProgressBar() { return &mProgressBar; } + protected: MyGUI::TextBox* mDateTimeText; MyGUI::TextBox* mRestText; @@ -63,6 +67,7 @@ namespace MWGui void onWaitButtonClicked(MyGUI::Widget* sender); void onCancelButtonClicked(MyGUI::Widget* sender); void onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position); + void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); void onWaitingProgressChanged(int cur, int total); void onWaitingInterrupted(); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 744ef236f..45767bf01 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -105,7 +105,7 @@ namespace MWGui assignWidget(button, "StatValueButton"); if (button) { - mSkillNameWidget = button; + mSkillValueWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } } diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 99b74529e..a0e7eedde 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -15,6 +15,7 @@ using namespace MWGui; WindowBase::WindowBase(const std::string& parLayout) : Layout(parLayout) { + mMainWidget->setVisible(false); } void WindowBase::setVisible(bool visible) @@ -23,9 +24,9 @@ void WindowBase::setVisible(bool visible) mMainWidget->setVisible(visible); if (visible) - open(); + onOpen(); else if (wasVisible && !visible) - close(); + onClose(); // This is needed as invisible widgets can retain key focus. // Remove for MyGUI 3.2.2 @@ -64,16 +65,20 @@ WindowModal::WindowModal(const std::string& parLayout) { } -void WindowModal::open() +void WindowModal::onOpen() { - MyGUI::InputManager::getInstance ().addWidgetModal (mMainWidget); MWBase::Environment::get().getWindowManager()->addCurrentModal(this); //Set so we can escape it if needed + + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + MyGUI::InputManager::getInstance ().addWidgetModal (mMainWidget); + MyGUI::InputManager::getInstance().setKeyFocusWidget(focus); } -void WindowModal::close() +void WindowModal::onClose() { - MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); MWBase::Environment::get().getWindowManager()->removeCurrentModal(this); + + MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); } NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 43fb8901e..56901c95a 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -8,6 +8,11 @@ namespace MWBase class WindowManager; } +namespace MWWorld +{ + class Ptr; +} + namespace MWGui { class WindowManager; @@ -18,20 +23,35 @@ namespace MWGui public: WindowBase(const std::string& parLayout); + virtual MyGUI::Widget* getDefaultKeyFocus() { return NULL; } + // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; + /// Open this object in the GUI, for windows that support it + virtual void setPtr(const MWWorld::Ptr& ptr) {} + + /// Called every frame if the window is in an active GUI mode + virtual void onFrame(float duration) {} + /// Notify that window has been made visible - virtual void open() {} + virtual void onOpen() {} /// Notify that window has been hidden - virtual void close () {} + virtual void onClose () {} /// Gracefully exits the window - virtual void exit() {} + virtual bool exit() {return true;} /// Sets the visibility of the window virtual void setVisible(bool visible); /// Returns the visibility state of the window bool isVisible(); + void center(); + + /// Clear any state specific to the running game + virtual void clear() {} + + /// Called when GUI viewport changes size + virtual void onResChange(int width, int height) {} }; @@ -42,9 +62,9 @@ namespace MWGui { public: WindowModal(const std::string& parLayout); - virtual void open(); - virtual void close(); - virtual void exit() {} + virtual void onOpen(); + virtual void onClose(); + virtual bool exit() {return true;} }; /// A window that cannot be the target of a drag&drop action. diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 19ef989eb..30cbd6758 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -124,6 +125,7 @@ #include "controllers.hpp" #include "jailscreen.hpp" #include "itemchargeview.hpp" +#include "keyboardnavigation.hpp" namespace { @@ -151,38 +153,24 @@ namespace MWGui , mHud(NULL) , mMap(NULL) , mLocalMapRender(NULL) - , mMenu(NULL) , mToolTips(NULL) , mStatsWindow(NULL) , mMessageBoxManager(NULL) , mConsole(NULL) - , mJournal(NULL) , mDialogueWindow(NULL) - , mContainerWindow(NULL) , mDragAndDrop(NULL) , mInventoryWindow(NULL) , mScrollWindow(NULL) , mBookWindow(NULL) , mCountDialog(NULL) , mTradeWindow(NULL) - , mSpellBuyingWindow(NULL) - , mTravelWindow(NULL) , mSettingsWindow(NULL) , mConfirmationDialog(NULL) - , mAlchemyWindow(NULL) , mSpellWindow(NULL) , mQuickKeysMenu(NULL) , mLoadingScreen(NULL) - , mLevelupDialog(NULL) , mWaitDialog(NULL) - , mSpellCreationDialog(NULL) - , mEnchantingDialog(NULL) - , mTrainingWindow(NULL) - , mMerchantRepair(NULL) , mSoulgemDialog(NULL) - , mRepair(NULL) - , mRecharge(NULL) - , mCompanionWindow(NULL) , mVideoBackground(NULL) , mVideoWidget(NULL) , mWerewolfFader(NULL) @@ -199,8 +187,8 @@ namespace MWGui , mHitFaderEnabled(Settings::Manager::getBool ("hit fader", "GUI")) , mWerewolfOverlayEnabled(Settings::Manager::getBool ("werewolf overlay", "GUI")) , mHudEnabled(true) - , mGuiEnabled(true) , mCursorVisible(true) + , mCursorActive(false) , mPlayerName() , mPlayerRaceId() , mPlayerAttributes() @@ -217,6 +205,7 @@ namespace MWGui , mRestAllowed(true) , mFallbackMap(fallbackMap) , mShowOwned(0) + , mEncoding(encoding) , mVersionDescription(versionDescription) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); @@ -260,7 +249,10 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); + mKeyboardNavigation.reset(new KeyboardNavigation()); + mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); + mWindows.push_back(mLoadingScreen); //set up the hardware cursor manager mCursorManager = new SDLUtil::SDLCursorManager(); @@ -328,57 +320,155 @@ namespace MWGui mDragAndDrop = new DragAndDrop(); - mRecharge = new Recharge(); - mMenu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription); + Recharge* recharge = new Recharge(); + mGuiModeStates[GM_Recharge] = GuiModeState(recharge); + mWindows.push_back(recharge); + + MainMenu* menu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription); + mGuiModeStates[GM_MainMenu] = GuiModeState(menu); + mWindows.push_back(menu); + mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup()); mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue); + mWindows.push_back(mMap); mMap->renderGlobalMap(); trackWindow(mMap, "map"); + mStatsWindow = new StatsWindow(mDragAndDrop); + mWindows.push_back(mStatsWindow); trackWindow(mStatsWindow, "stats"); + + mInventoryWindow = new InventoryWindow(mDragAndDrop, mViewer->getSceneData()->asGroup(), mResourceSystem); + mWindows.push_back(mInventoryWindow); + + mSpellWindow = new SpellWindow(mDragAndDrop); + mWindows.push_back(mSpellWindow); + trackWindow(mSpellWindow, "spells"); + + mGuiModeStates[GM_Inventory] = GuiModeState({mMap, mInventoryWindow, mSpellWindow, mStatsWindow}); + mGuiModeStates[GM_None] = GuiModeState({mMap, mInventoryWindow, mSpellWindow, mStatsWindow}); + + mTradeWindow = new TradeWindow(); + mWindows.push_back(mTradeWindow); + trackWindow(mTradeWindow, "barter"); + mGuiModeStates[GM_Barter] = GuiModeState({mInventoryWindow, mTradeWindow}); + mConsole = new Console(w,h, mConsoleOnlyScripts); + mWindows.push_back(mConsole); trackWindow(mConsole, "console"); + mGuiModeStates[GM_Console] = GuiModeState(mConsole); bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); - mJournal = JournalWindow::create(JournalViewModel::create (), questList); + JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList, mEncoding); + mWindows.push_back(journal); + mGuiModeStates[GM_Journal] = GuiModeState(journal); + mGuiModeStates[GM_Journal].mCloseSound = "book close"; + mGuiModeStates[GM_Journal].mOpenSound = "book open"; + mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->getFloat()); - mInventoryWindow = new InventoryWindow(mDragAndDrop, mViewer->getSceneData()->asGroup(), mResourceSystem); - mTradeWindow = new TradeWindow(); - trackWindow(mTradeWindow, "barter"); - mSpellBuyingWindow = new SpellBuyingWindow(); - mTravelWindow = new TravelWindow(); + + SpellBuyingWindow* spellBuyingWindow = new SpellBuyingWindow(); + mWindows.push_back(spellBuyingWindow); + mGuiModeStates[GM_SpellBuying] = GuiModeState(spellBuyingWindow); + + TravelWindow* travelWindow = new TravelWindow(); + mWindows.push_back(travelWindow); + mGuiModeStates[GM_Travel] = GuiModeState(travelWindow); + mDialogueWindow = new DialogueWindow(); + mWindows.push_back(mDialogueWindow); trackWindow(mDialogueWindow, "dialogue"); - mContainerWindow = new ContainerWindow(mDragAndDrop); - trackWindow(mContainerWindow, "container"); + mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow); + mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete); + + ContainerWindow* containerWindow = new ContainerWindow(mDragAndDrop); + mWindows.push_back(containerWindow); + trackWindow(containerWindow, "container"); + mGuiModeStates[GM_Container] = GuiModeState({containerWindow, mInventoryWindow}); + mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); + mWindows.push_back(mHud); + mToolTips = new ToolTips(); + mScrollWindow = new ScrollWindow(); + mWindows.push_back(mScrollWindow); + mGuiModeStates[GM_Scroll] = GuiModeState(mScrollWindow); + mGuiModeStates[GM_Scroll].mOpenSound = "scroll"; + mGuiModeStates[GM_Scroll].mCloseSound = "scroll"; + mBookWindow = new BookWindow(); + mWindows.push_back(mBookWindow); + mGuiModeStates[GM_Book] = GuiModeState(mBookWindow); + mGuiModeStates[GM_Book].mOpenSound = "book open"; + mGuiModeStates[GM_Book].mCloseSound = "book close"; + mCountDialog = new CountDialog(); + mWindows.push_back(mCountDialog); + mSettingsWindow = new SettingsWindow(); + mWindows.push_back(mSettingsWindow); + mGuiModeStates[GM_Settings] = GuiModeState(mSettingsWindow); + mConfirmationDialog = new ConfirmationDialog(); - mAlchemyWindow = new AlchemyWindow(); - trackWindow(mAlchemyWindow, "alchemy"); - mSpellWindow = new SpellWindow(mDragAndDrop); - trackWindow(mSpellWindow, "spells"); + mWindows.push_back(mConfirmationDialog); + + AlchemyWindow* alchemyWindow = new AlchemyWindow(); + mWindows.push_back(alchemyWindow); + trackWindow(alchemyWindow, "alchemy"); + mGuiModeStates[GM_Alchemy] = GuiModeState(alchemyWindow); + mQuickKeysMenu = new QuickKeysMenu(); - mLevelupDialog = new LevelupDialog(); + mWindows.push_back(mQuickKeysMenu); + mGuiModeStates[GM_QuickKeysMenu] = GuiModeState(mQuickKeysMenu); + + LevelupDialog* levelupDialog = new LevelupDialog(); + mWindows.push_back(levelupDialog); + mGuiModeStates[GM_Levelup] = GuiModeState(levelupDialog); + mWaitDialog = new WaitDialog(); - mSpellCreationDialog = new SpellCreationDialog(); - mEnchantingDialog = new EnchantingDialog(); - mTrainingWindow = new TrainingWindow(); - mMerchantRepair = new MerchantRepair(); - mRepair = new Repair(); + mWindows.push_back(mWaitDialog); + mGuiModeStates[GM_Rest] = GuiModeState({mWaitDialog->getProgressBar(), mWaitDialog}); + + SpellCreationDialog* spellCreationDialog = new SpellCreationDialog(); + mWindows.push_back(spellCreationDialog); + mGuiModeStates[GM_SpellCreation] = GuiModeState(spellCreationDialog); + + EnchantingDialog* enchantingDialog = new EnchantingDialog(); + mWindows.push_back(enchantingDialog); + mGuiModeStates[GM_Enchanting] = GuiModeState(enchantingDialog); + + TrainingWindow* trainingWindow = new TrainingWindow(); + mWindows.push_back(trainingWindow); + mGuiModeStates[GM_Training] = GuiModeState({trainingWindow->getProgressBar(), trainingWindow}); + + MerchantRepair* merchantRepair = new MerchantRepair(); + mWindows.push_back(merchantRepair); + mGuiModeStates[GM_MerchantRepair] = GuiModeState(merchantRepair); + + Repair* repair = new Repair(); + mWindows.push_back(repair); + mGuiModeStates[GM_Repair] = GuiModeState(repair); + mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); - mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); - trackWindow(mCompanionWindow, "companion"); + + CompanionWindow* companionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); + mWindows.push_back(companionWindow); + trackWindow(companionWindow, "companion"); + mGuiModeStates[GM_Companion] = GuiModeState({mInventoryWindow, companionWindow}); + mJailScreen = new JailScreen(); + mWindows.push_back(mJailScreen); + mGuiModeStates[GM_Jail] = GuiModeState(mJailScreen); std::string werewolfFaderTex = "textures\\werewolfoverlay.dds"; if (mResourceSystem->getVFS()->exists(werewolfFaderTex)) + { mWerewolfFader = new ScreenFader(werewolfFaderTex); + mWindows.push_back(mWerewolfFader); + } mBlindnessFader = new ScreenFader("black"); + mWindows.push_back(mBlindnessFader); // fall back to player_hit_01.dds if bm_player_hit_01.dds is not available std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; @@ -390,14 +480,17 @@ namespace MWGui hitFaderCoord = MyGUI::FloatCoord(0.2, 0.25, 0.6, 0.5); } mHitFader = new ScreenFader(hitFaderTexture, hitFaderLayout, hitFaderCoord); + mWindows.push_back(mHitFader); mScreenFader = new ScreenFader("black"); + mWindows.push_back(mScreenFader); mDebugWindow = new DebugWindow(); + mWindows.push_back(mDebugWindow); mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); - mHud->setVisible(mHudEnabled); + mHud->setVisible(true); mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); @@ -416,85 +509,41 @@ namespace MWGui // Set up visibility updateVisible(); - - MWBase::Environment::get().getInputManager()->changeInputMode(false); } void WindowManager::setNewGame(bool newgame) { - // This method will always be called after loading a savegame or starting a new game - // Reset enemy, it could be a dangling pointer from a previous game - mHud->resetEnemy(); - if (newgame) { disallowAll(); delete mCharGen; mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); - mGuiModes.clear(); - MWBase::Environment::get().getInputManager()->changeInputMode(false); - mHud->unsetSelectedWeapon(); - mHud->unsetSelectedSpell(); - unsetForceHide(GW_ALL); } else allow(GW_ALL); - - mRestAllowed = !newgame; } WindowManager::~WindowManager() { + mKeyboardNavigation.reset(); + MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); - delete mConsole; + for (WindowBase* window : mWindows) + delete window; + mWindows.clear(); + delete mMessageBoxManager; - delete mHud; - delete mMap; delete mLocalMapRender; - delete mMenu; - delete mStatsWindow; - delete mJournal; - delete mDialogueWindow; - delete mContainerWindow; - delete mInventoryWindow; - delete mToolTips; delete mCharGen; delete mDragAndDrop; - delete mBookWindow; - delete mScrollWindow; - delete mTradeWindow; - delete mSpellBuyingWindow; - delete mTravelWindow; - delete mSettingsWindow; - delete mConfirmationDialog; - delete mAlchemyWindow; - delete mSpellWindow; - delete mLoadingScreen; - delete mLevelupDialog; - delete mWaitDialog; - delete mSpellCreationDialog; - delete mEnchantingDialog; - delete mTrainingWindow; - delete mCountDialog; - delete mQuickKeysMenu; - delete mMerchantRepair; - delete mRepair; delete mSoulgemDialog; - delete mRecharge; - delete mCompanionWindow; - delete mHitFader; - delete mWerewolfFader; - delete mScreenFader; - delete mBlindnessFader; - delete mDebugWindow; - delete mJailScreen; - delete mCursorManager; + delete mToolTips; cleanupGarbage(); @@ -525,210 +574,96 @@ namespace MWGui } } - void WindowManager::update() - { - cleanupGarbage(); - - mHud->update(); - - updateActivatedQuickKey (); - } - void WindowManager::updateVisible() { if (!mMap) return; // UI not created yet - // Start out by hiding everything except the HUD - mMap->setVisible(false); - mMenu->setVisible(false); - mStatsWindow->setVisible(false); - mConsole->setVisible(false); - mJournal->setVisible(false); - mDialogueWindow->setVisible(false); - mContainerWindow->setVisible(false); - mInventoryWindow->setVisible(false); - mScrollWindow->setVisible(false); - mBookWindow->setVisible(false); - mTradeWindow->setVisible(false); - mSpellBuyingWindow->setVisible(false); - mTravelWindow->setVisible(false); - mSettingsWindow->setVisible(false); - mAlchemyWindow->setVisible(false); - mSpellWindow->setVisible(false); - mQuickKeysMenu->setVisible(false); - mLevelupDialog->setVisible(false); - mWaitDialog->setVisible(false); - mSpellCreationDialog->setVisible(false); - mEnchantingDialog->setVisible(false); - mTrainingWindow->setVisible(false); - mMerchantRepair->setVisible(false); - mRepair->setVisible(false); - mCompanionWindow->setVisible(false); - mInventoryWindow->setTrading(false); - mRecharge->setVisible(false); - mVideoBackground->setVisible(false); - mJailScreen->setVisible(false); - mHud->setVisible(mHudEnabled && mGuiEnabled); - mToolTips->setVisible(mGuiEnabled); + bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper); + + mHud->setVisible(mHudEnabled && !loading); + mToolTips->setVisible(mHudEnabled && !loading); bool gameMode = !isGuiMode(); + MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); + mInputBlocker->setVisible (gameMode); - setCursorVisible(!gameMode); + + if (loading) + setCursorVisible(mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); + else + setCursorVisible(!gameMode); if (gameMode) setKeyFocusWidget (NULL); - if (!mGuiEnabled) - { - if (containsMode(GM_Console)) - mConsole->setVisible(true); - return; - } - // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); setWeaponVisibility((mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory))); setSpellVisibility((mAllowed & GW_Magic) && (!mSpellWindow->pinned() || (mForceHidden & GW_Magic))); setHMSVisibility((mAllowed & GW_Stats) && (!mStatsWindow->pinned() || (mForceHidden & GW_Stats))); - // If in game mode (or interactive messagebox), show only the pinned windows + mInventoryWindow->setGuiMode(getMode()); + + // If in game mode (or interactive messagebox), show the pinned windows if (mGuiModes.empty()) { - mInventoryWindow->setGuiMode(GM_None); mMap->setVisible(mMap->pinned() && !(mForceHidden & GW_Map) && (mAllowed & GW_Map)); mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats) && (mAllowed & GW_Stats)); mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory) && (mAllowed & GW_Inventory)); mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic) && (mAllowed & GW_Magic)); - return; } + else if (getMode() != GM_Inventory) + { + mMap->setVisible(false); + mStatsWindow->setVisible(false); + mSpellWindow->setVisible(false); + mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion); + } + + GuiMode mode = mGuiModes.back(); - if(mGuiModes.size() != 0) + mInventoryWindow->setTrading(mode == GM_Barter); + + if (getMode() == GM_Inventory) { - GuiMode mode = mGuiModes.back(); - - switch(mode) { - case GM_QuickKeysMenu: - mQuickKeysMenu->setVisible (true); - break; - case GM_MainMenu: - mMenu->setVisible(true); - break; - case GM_Settings: - mSettingsWindow->setVisible(true); - break; - case GM_Console: - mConsole->setVisible(true); - break; - case GM_Scroll: - mScrollWindow->setVisible(true); - break; - case GM_Book: - mBookWindow->setVisible(true); - break; - case GM_Alchemy: - mAlchemyWindow->setVisible(true); - break; - case GM_Rest: - mWaitDialog->setVisible(true); - break; - case GM_RestBed: - mWaitDialog->setVisible(true); - mWaitDialog->bedActivated(); - break; - case GM_Levelup: - mLevelupDialog->setVisible(true); - break; - case GM_Name: - case GM_Race: - case GM_Class: - case GM_ClassPick: - case GM_ClassCreate: - case GM_Birth: - case GM_ClassGenerate: - case GM_Review: - mCharGen->spawnDialog(mode); - break; - case GM_Inventory: - { - // First, compute the effective set of windows to show. - // This is controlled both by what windows the - // user has opened/closed (the 'shown' variable) and by what - // windows we are allowed to show (the 'allowed' var.) - int eff = mShown & mAllowed & ~mForceHidden; - - // Show the windows we want - mMap ->setVisible((eff & GW_Map) != 0); - mStatsWindow ->setVisible((eff & GW_Stats) != 0); - mInventoryWindow->setVisible((eff & GW_Inventory) != 0); - mInventoryWindow->setGuiMode(mode); - mSpellWindow ->setVisible((eff & GW_Magic) != 0); - break; - } - case GM_Container: - mContainerWindow->setVisible(true); - mInventoryWindow->setVisible(true); - mInventoryWindow->setGuiMode(mode); - break; - case GM_Companion: - mCompanionWindow->setVisible(true); - mInventoryWindow->setVisible(true); - mInventoryWindow->setGuiMode(mode); - break; - case GM_Dialogue: - mDialogueWindow->setVisible(true); - break; - case GM_Barter: - mInventoryWindow->setVisible(true); - mInventoryWindow->setTrading(true); - mInventoryWindow->setGuiMode(mode); - mTradeWindow->setVisible(true); - break; - case GM_SpellBuying: - mSpellBuyingWindow->setVisible(true); - break; - case GM_Travel: - mTravelWindow->setVisible(true); - break; - case GM_SpellCreation: - mSpellCreationDialog->setVisible(true); - break; - case GM_Recharge: - mRecharge->setVisible(true); - break; - case GM_Enchanting: - mEnchantingDialog->setVisible(true); - break; - case GM_Training: - mTrainingWindow->setVisible(true); - break; - case GM_MerchantRepair: - mMerchantRepair->setVisible(true); - break; - case GM_Repair: - mRepair->setVisible(true); - break; - case GM_Journal: - mJournal->setVisible(true); - break; - case GM_Jail: - mJailScreen->setVisible(true); - break; - case GM_LoadingWallpaper: - case GM_Loading: - // Don't need to show anything here - GM_LoadingWallpaper covers everything else anyway, - // GM_Loading uses a texture of the last rendered frame so everything previously visible will be rendered. - mHud->setVisible(false); - mToolTips->setVisible(false); - setCursorVisible(mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); - break; - default: - mwmp::Main::get().getGUIController()->WM_UpdateVisible(mode); - // Unsupported mode, switch back to game - break; - } + // For the inventory mode, compute the effective set of windows to show. + // This is controlled both by what windows the + // user has opened/closed (the 'shown' variable) and by what + // windows we are allowed to show (the 'allowed' var.) + int eff = mShown & mAllowed & ~mForceHidden; + mMap->setVisible(eff & GW_Map); + mInventoryWindow->setVisible(eff & GW_Inventory); + mSpellWindow->setVisible(eff & GW_Magic); + mStatsWindow->setVisible(eff & GW_Stats); + } + + switch (mode) + { + // FIXME: refactor chargen windows to use modes properly (or not use them at all) + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + mCharGen->spawnDialog(mode); + break; + default: + /* + Start of tes3mp addition + + Pass the GuiMode further on to the multiplayer-specific GUI controller + */ + mwmp::Main::get().getGUIController()->WM_UpdateVisible(mode); + /* + End of tes3mp addition + */ + break; } } @@ -824,97 +759,32 @@ namespace MWGui mGarbageDialogs.push_back(dialog); } - void WindowManager::exitCurrentGuiMode() { - switch(mGuiModes.back()) { - case GM_QuickKeysMenu: - mQuickKeysMenu->exit(); - break; - case GM_MainMenu: - removeGuiMode(GM_MainMenu); //Simple way to remove it - break; - case GM_Settings: - mSettingsWindow->exit(); - break; - case GM_Console: - mConsole->exit(); - break; - case GM_Scroll: - mScrollWindow->exit(); - break; - case GM_Book: - mBookWindow->exit(); - break; - case GM_Alchemy: - mAlchemyWindow->exit(); - break; - case GM_Rest: - mWaitDialog->exit(); - break; - case GM_RestBed: - mWaitDialog->exit(); - break; - case GM_Name: - case GM_Race: - case GM_Class: - case GM_ClassPick: - case GM_ClassCreate: - case GM_Birth: - case GM_ClassGenerate: - case GM_Review: - break; - case GM_Inventory: - removeGuiMode(GM_Inventory); //Simple way to remove it - break; - case GM_Container: - mContainerWindow->exit(); - break; - case GM_Companion: - mCompanionWindow->exit(); - break; - case GM_Dialogue: - mDialogueWindow->exit(); - break; - case GM_Barter: - mTradeWindow->exit(); - break; - case GM_SpellBuying: - mSpellBuyingWindow->exit(); - break; - case GM_Travel: - mTravelWindow->exit(); - break; - case GM_SpellCreation: - mSpellCreationDialog->exit(); - break; - case GM_Recharge: - mRecharge->exit(); - break; - case GM_Enchanting: - mEnchantingDialog->exit(); - break; - case GM_Training: - mTrainingWindow->exit(); - break; - case GM_MerchantRepair: - mMerchantRepair->exit(); - break; - case GM_Repair: - mRepair->exit(); - break; - case GM_Journal: - playSound("book close"); - removeGuiMode(GM_Journal); //Simple way to remove it - break; - default: - // Unsupported mode, switch back to game - break; + void WindowManager::exitCurrentGuiMode() + { + if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) + { + mDragAndDrop->finish(); + return; + } + + GuiModeState& state = mGuiModeStates[mGuiModes.back()]; + for (WindowBase* window : state.mWindows) + { + if (!window->exit()) + { + // unable to exit window, but give access to main menu + if (!MyGUI::InputManager::getInstance().isModalAny() && getMode() != GM_MainMenu) + pushGuiMode (GM_MainMenu); + return; + } } + + popGuiMode(); } void WindowManager::interactiveMessageBox(const std::string &message, const std::vector &buttons, bool block) { mMessageBoxManager->createInteractiveMessageBox(message, buttons); - MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); updateVisible(); if (block) @@ -926,6 +796,7 @@ namespace MWGui double dt = frameTimer.time_s(); frameTimer.setStartTick(); + mKeyboardNavigation->onFrame(); mMessageBoxManager->onFrame(dt); MWBase::Environment::get().getInputManager()->update(dt, true, false); @@ -1023,12 +894,29 @@ namespace MWGui void WindowManager::onFrame (float frameDuration) { + if (!mGuiModes.empty()) + { + GuiModeState& state = mGuiModeStates[mGuiModes.back()]; + for (WindowBase* window : state.mWindows) + window->onFrame(frameDuration); + } + else + { + // update pinned windows if visible + for (WindowBase* window : mGuiModeStates[GM_Inventory].mWindows) + if (window->isVisible()) + window->onFrame(frameDuration); + } + + if (!mCurrentModals.empty()) + mCurrentModals.back()->onFrame(frameDuration); + + mKeyboardNavigation->onFrame(); + mMessageBoxManager->onFrame(frameDuration); mToolTips->onFrame(frameDuration); - mMenu->update(frameDuration); - if (mLocalMapRender) mLocalMapRender->cleanupCameras(); @@ -1036,50 +924,20 @@ namespace MWGui MWBase::StateManager::State_NoGame) return; - if (mDragAndDrop->mIsOnDragAndDrop) - { - assert(mDragAndDrop->mDraggedWidget); - mDragAndDrop->mDraggedWidget->setPosition(MyGUI::InputManager::getInstance().getMousePosition()); - } - - mDialogueWindow->onFrame(); - - mInventoryWindow->onFrame(); + mDragAndDrop->onFrame(); updateMap(); - mStatsWindow->onFrame(frameDuration); - mMap->onFrame(frameDuration); - mSpellWindow->onFrame(frameDuration); - - mWaitDialog->onFrame(frameDuration); - mHud->onFrame(frameDuration); - mTrainingWindow->onFrame (frameDuration); - - mTrainingWindow->checkReferenceAvailable(); - mDialogueWindow->checkReferenceAvailable(); - mTradeWindow->checkReferenceAvailable(); - mSpellBuyingWindow->checkReferenceAvailable(); - mSpellCreationDialog->checkReferenceAvailable(); - mEnchantingDialog->checkReferenceAvailable(); - mContainerWindow->checkReferenceAvailable(); - mCompanionWindow->checkReferenceAvailable(); - mConsole->checkReferenceAvailable(); - mCompanionWindow->onFrame(); - mJailScreen->onFrame(frameDuration); - - if (mWerewolfFader) - mWerewolfFader->update(frameDuration); - mBlindnessFader->update(frameDuration); - mHitFader->update(frameDuration); - mScreenFader->update(frameDuration); - mDebugWindow->onFrame(frameDuration); if (mCharGen) mCharGen->onFrame(frameDuration); + + updateActivatedQuickKey (); + + cleanupGarbage(); } void WindowManager::changeCell(const MWWorld::CellStore* cell) @@ -1220,7 +1078,16 @@ namespace MWGui void WindowManager::setCursorVisible(bool visible) { + if (visible == mCursorVisible) + return; mCursorVisible = visible; + if (!visible) + mCursorActive = false; + } + + void WindowManager::setCursorActive(bool active) + { + mCursorActive = active; } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) @@ -1305,54 +1172,88 @@ namespace MWGui it->first->setSize(size); } - mConsole->onResChange(x, y); - mMenu->onResChange(x, y); - mSettingsWindow->center(); - mAlchemyWindow->center(); - mScrollWindow->center(); - mBookWindow->center(); - mQuickKeysMenu->center(); - mSpellBuyingWindow->center(); + for (WindowBase* window : mWindows) + window->onResChange(x, y); + + // TODO: check if any windows are now off-screen and move them back if so + } + + void WindowManager::onCursorChange(const std::string &name) + { + mCursorManager->cursorChanged(name); } void WindowManager::pushGuiMode(GuiMode mode) + { + pushGuiMode(mode, MWWorld::Ptr()); + } + + void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) { if (mode==GM_Inventory && mAllowed==GW_None) return; - - // If this mode already exists somewhere in the stack, just bring it to the front. - if (std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end()) + if (mGuiModes.empty() || mGuiModes.back() != mode) { - mGuiModes.erase(std::find(mGuiModes.begin(), mGuiModes.end(), mode)); - } + // If this mode already exists somewhere in the stack, just bring it to the front. + if (std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end()) + { + mGuiModes.erase(std::find(mGuiModes.begin(), mGuiModes.end(), mode)); + } - mGuiModes.push_back(mode); + if (!mGuiModes.empty()) + { + mKeyboardNavigation->saveFocus(mGuiModes.back()); + mGuiModeStates[mGuiModes.back()].update(false); + } + mGuiModes.push_back(mode); - bool gameMode = !isGuiMode(); - MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); + mGuiModeStates[mode].update(true); + playSound(mGuiModeStates[mode].mOpenSound); + } + for (WindowBase* window : mGuiModeStates[mode].mWindows) + window->setPtr(arg); + + mKeyboardNavigation->restoreFocus(mode); updateVisible(); } - void WindowManager::onCursorChange(const std::string &name) + void WindowManager::popGuiMode(bool noSound) { - mCursorManager->cursorChanged(name); - } + if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) + { + mDragAndDrop->finish(); + } - void WindowManager::popGuiMode() - { if (!mGuiModes.empty()) + { + const GuiMode mode = mGuiModes.back(); + mKeyboardNavigation->saveFocus(mode); mGuiModes.pop_back(); + mGuiModeStates[mode].update(false); + if (!noSound) + playSound(mGuiModeStates[mode].mCloseSound); + } - bool gameMode = !isGuiMode(); - MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); + if (!mGuiModes.empty()) + { + const GuiMode mode = mGuiModes.back(); + mGuiModeStates[mode].update(true); + mKeyboardNavigation->restoreFocus(mode); + } updateVisible(); } - void WindowManager::removeGuiMode(GuiMode mode) + void WindowManager::removeGuiMode(GuiMode mode, bool noSound) { + if (!mGuiModes.empty() && mGuiModes.back() == mode) + { + popGuiMode(noSound); + return; + } + std::vector::iterator it = mGuiModes.begin(); while (it != mGuiModes.end()) { @@ -1362,9 +1263,6 @@ namespace MWGui ++it; } - bool gameMode = !isGuiMode(); - MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); - updateVisible(); } @@ -1477,7 +1375,6 @@ namespace MWGui End of tes3mp addition */ - MWGui::DialogueWindow* WindowManager::getDialogueWindow() { return mDialogueWindow; } MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; } MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } @@ -1510,6 +1407,7 @@ namespace MWGui void WindowManager::disallowAll() { mAllowed = GW_None; + mRestAllowed = false; mBookWindow->setInventoryAllowed (false); mScrollWindow->setInventoryAllowed (false); @@ -1651,11 +1549,11 @@ namespace MWGui return mSubtitlesEnabled; } - bool WindowManager::toggleGui() + bool WindowManager::toggleHud() { - mGuiEnabled = !mGuiEnabled; + mHudEnabled = !mHudEnabled; updateVisible(); - return mGuiEnabled; + return mHudEnabled; } bool WindowManager::getRestEnabled() @@ -1681,52 +1579,11 @@ namespace MWGui mMap->addVisitedLocation (name, x, y); } - void WindowManager::startSpellMaking(MWWorld::Ptr actor) - { - pushGuiMode(GM_SpellCreation); - mSpellCreationDialog->startSpellMaking (actor); - } - - void WindowManager::startEnchanting (MWWorld::Ptr actor) - { - pushGuiMode(GM_Enchanting); - mEnchantingDialog->startEnchanting (actor); - } - - void WindowManager::startSelfEnchanting(MWWorld::Ptr soulgem) - { - mEnchantingDialog->startSelfEnchanting(soulgem); - } - - void WindowManager::startTraining(MWWorld::Ptr actor) - { - pushGuiMode(GM_Training); - mTrainingWindow->startTraining(actor); - } - - void WindowManager::startRepair(MWWorld::Ptr actor) - { - pushGuiMode(GM_MerchantRepair); - mMerchantRepair->startRepair(actor); - } - - void WindowManager::startRepairItem(MWWorld::Ptr item) - { - pushGuiMode(MWGui::GM_Repair); - mRepair->startRepairItem(item); - } - const Translation::Storage& WindowManager::getTranslationDataStorage() const { return mTranslationDataStorage; } - void WindowManager::showCompanionWindow(MWWorld::Ptr actor) - { - pushGuiMode(MWGui::GM_Companion); - mCompanionWindow->openCompanion(actor); - } - void WindowManager::changePointer(const std::string &name) { MyGUI::PointerManager::getInstance().setPointer(name); @@ -1736,7 +1593,6 @@ namespace MWGui void WindowManager::showSoulgemDialog(MWWorld::Ptr item) { mSoulgemDialog->show(item); - MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); updateVisible(); } @@ -1780,14 +1636,9 @@ namespace MWGui return mLoadingScreen; } - void WindowManager::startRecharge(MWWorld::Ptr soulgem) - { - mRecharge->start(soulgem); - } - bool WindowManager::getCursorVisible() { - return mCursorVisible; + return mCursorVisible && mCursorActive; } void WindowManager::trackWindow(Layout *layout, const std::string &name) @@ -1821,37 +1672,25 @@ namespace MWGui void WindowManager::clear() { + for (WindowBase* window : mWindows) + window->clear(); + if (mLocalMapRender) mLocalMapRender->clear(); - mMap->clear(); - mQuickKeysMenu->clear(); mMessageBoxManager->clear(); - mTrainingWindow->resetReference(); - mDialogueWindow->resetReference(); - mTradeWindow->resetReference(); - mSpellBuyingWindow->resetReference(); - mSpellCreationDialog->resetReference(); - mEnchantingDialog->resetReference(); - mContainerWindow->resetReference(); - mCompanionWindow->resetReference(); - mConsole->resetReference(); - - mToolTips->setFocusObject(MWWorld::ConstPtr()); - - mInventoryWindow->clear(); + mToolTips->setFocusObject(MWWorld::Ptr()); mSelectedSpell.clear(); - mCustomMarkers.clear(); mForceHidden = GW_None; + mRestAllowed = true; - setWerewolfOverlay(false); + while (!mGuiModes.empty()) + popGuiMode(); - mGuiModes.clear(); - MWBase::Environment::get().getInputManager()->changeInputMode(false); updateVisible(); } @@ -1909,7 +1748,7 @@ namespace MWGui { return !MyGUI::InputManager::getInstance().isModalAny() // TODO: remove this, once we have properly serialized the state of open windows - && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest || getMode() == GM_RestBed))); + && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest))); } void WindowManager::playVideo(const std::string &name, bool allowSkipping) @@ -1943,7 +1782,8 @@ namespace MWGui if (mVideoWidget->hasAudioStream()) MWBase::Environment::get().getSoundManager()->pauseSounds( - MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie)); + ~MWSound::Type::Movie & MWSound::Type::Mask + ); osg::Timer frameTimer; while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { @@ -1993,16 +1833,51 @@ namespace MWGui void WindowManager::exitCurrentModal() { if (!mCurrentModals.empty()) - mCurrentModals.top()->exit(); + { + WindowModal* window = mCurrentModals.back(); + if (!window->exit()) + return; + window->setVisible(false); + } + } + + void WindowManager::addCurrentModal(WindowModal *input) + { + if (mCurrentModals.empty()) + mKeyboardNavigation->saveFocus(getMode()); + + mCurrentModals.push_back(input); + mKeyboardNavigation->restoreFocus(-1); + + mKeyboardNavigation->setModalWindow(input->mMainWidget); + mKeyboardNavigation->setDefaultFocus(input->mMainWidget, input->getDefaultKeyFocus()); } void WindowManager::removeCurrentModal(WindowModal* input) { - // Only remove the top if it matches the current pointer. A lot of things hide their visibility before showing it, - //so just popping the top would cause massive issues. if(!mCurrentModals.empty()) - if(input == mCurrentModals.top()) - mCurrentModals.pop(); + { + if(input == mCurrentModals.back()) + { + mCurrentModals.pop_back(); + mKeyboardNavigation->saveFocus(-1); + } + else + { + auto found = std::find(mCurrentModals.begin(), mCurrentModals.end(), input); + if (found != mCurrentModals.end()) + mCurrentModals.erase(found); + else + std::cerr << " warning: can't find modal window " << input << std::endl; + } + } + if (mCurrentModals.empty()) + { + mKeyboardNavigation->setModalWindow(NULL); + mKeyboardNavigation->restoreFocus(getMode()); + } + else + mKeyboardNavigation->setModalWindow(mCurrentModals.back()->mMainWidget); } void WindowManager::onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) @@ -2045,25 +1920,25 @@ namespace MWGui updateVisible(); } - void WindowManager::fadeScreenIn(const float time, bool clearQueue) + void WindowManager::fadeScreenIn(const float time, bool clearQueue, float delay) { if (clearQueue) mScreenFader->clearQueue(); - mScreenFader->fadeOut(time); + mScreenFader->fadeOut(time, delay); } - void WindowManager::fadeScreenOut(const float time, bool clearQueue) + void WindowManager::fadeScreenOut(const float time, bool clearQueue, float delay) { if (clearQueue) mScreenFader->clearQueue(); - mScreenFader->fadeIn(time); + mScreenFader->fadeIn(time, delay); } - void WindowManager::fadeScreenTo(const int percent, const float time, bool clearQueue) + void WindowManager::fadeScreenTo(const int percent, const float time, bool clearQueue, float delay) { if (clearQueue) mScreenFader->clearQueue(); - mScreenFader->fadeTo(percent, time); + mScreenFader->fadeTo(percent, time, delay); } void WindowManager::setBlindness(const int percent) @@ -2130,12 +2005,9 @@ namespace MWGui void WindowManager::playSound(const std::string& soundId, float volume, float pitch) { - MWBase::Environment::get().getSoundManager()->playSound(soundId, volume, pitch, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoEnv); - } - - void WindowManager::setConsoleSelectedObject(const MWWorld::Ptr &object) - { - mConsole->setSelectedObject(object); + if (soundId.empty()) + return; + MWBase::Environment::get().getSoundManager()->playSound(soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } void WindowManager::updateSpellWindow() @@ -2144,40 +2016,9 @@ namespace MWGui mSpellWindow->updateSpells(); } - void WindowManager::startTravel(const MWWorld::Ptr &actor) - { - pushGuiMode(GM_Travel); - mTravelWindow->startTravel(actor); - } - - void WindowManager::startSpellBuying(const MWWorld::Ptr &actor) - { - pushGuiMode(GM_SpellBuying); - mSpellBuyingWindow->startSpellBuying(actor, 0); - } - - void WindowManager::startTrade(const MWWorld::Ptr &actor) - { - pushGuiMode(GM_Barter); - mTradeWindow->startTrade(actor); - } - - void WindowManager::openContainer(const MWWorld::Ptr &container, bool loot) - { - pushGuiMode(GM_Container); - mContainerWindow->openContainer(container, loot); - } - - void WindowManager::showBook(const MWWorld::Ptr &item, bool showTakeButton) - { - pushGuiMode(GM_Book); - mBookWindow->openBook(item, showTakeButton); - } - - void WindowManager::showScroll(const MWWorld::Ptr &item, bool showTakeButton) + void WindowManager::setConsoleSelectedObject(const MWWorld::Ptr &object) { - pushGuiMode(GM_Scroll); - mScrollWindow->openScroll(item, showTakeButton); + mConsole->setSelectedObject(object); } std::string WindowManager::correctIconPath(const std::string& path) @@ -2185,9 +2026,12 @@ namespace MWGui return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); } - std::string WindowManager::correctBookartPath(const std::string& path, int width, int height) + std::string WindowManager::correctBookartPath(const std::string& path, int width, int height, bool* exists) { - return Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); + std::string corrected = Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); + if (exists) + *exists = mResourceSystem->getVFS()->exists(corrected); + return corrected; } std::string WindowManager::correctTexturePath(const std::string& path) @@ -2293,4 +2137,40 @@ namespace MWGui return mTextColours; } + bool WindowManager::injectKeyPress(MyGUI::KeyCode key, unsigned int text) + { + if (!mKeyboardNavigation->injectKeyPress(key, text)) + { + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + bool widgetActive = MyGUI::InputManager::getInstance().injectKeyPress(key, text); + if (!widgetActive || !focus) + return false; + // FIXME: MyGUI doesn't allow widgets to state if a given key was actually used, so make a guess + if (focus->getTypeName().find("Button") != std::string::npos) + { + switch (key.getValue()) + { + case MyGUI::KeyCode::ArrowDown: + case MyGUI::KeyCode::ArrowUp: + case MyGUI::KeyCode::ArrowLeft: + case MyGUI::KeyCode::ArrowRight: + case MyGUI::KeyCode::Return: + case MyGUI::KeyCode::NumpadEnter: + case MyGUI::KeyCode::Space: + return true; + default: + return false; + } + } + return false; + } + else + return true; + } + + void WindowManager::GuiModeState::update(bool visible) + { + for (unsigned int i=0; isetVisible(visible); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 82df6534c..04af36ed1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -122,6 +122,7 @@ namespace MWGui class ScreenFader; class DebugWindow; class JailScreen; + class KeyboardNavigation; class WindowManager : public MWBase::WindowManager { @@ -145,21 +146,15 @@ namespace MWGui /// (and will continually update the window while doing so) virtual void playVideo(const std::string& name, bool allowSkipping); - /** - * Should be called each frame to update windows/gui elements. - * This could mean updating sizes of gui elements or opening - * new dialogs. - */ - virtual void update(); - /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget); virtual void setNewGame(bool newgame); - virtual void pushGuiMode(GuiMode mode); - virtual void popGuiMode(); - virtual void removeGuiMode(GuiMode mode); ///< can be anywhere in the stack + virtual void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg); + virtual void pushGuiMode (GuiMode mode); + virtual void popGuiMode(bool noSound=false); + virtual void removeGuiMode(GuiMode mode, bool noSound=false); ///< can be anywhere in the stack virtual void goToJail(int days); @@ -184,7 +179,6 @@ namespace MWGui virtual bool isAllowed(GuiWindow wnd) const; /// \todo investigate, if we really need to expose every single lousy UI element to the outside world - virtual MWGui::DialogueWindow* getDialogueWindow(); virtual MWGui::InventoryWindow* getInventoryWindow(); virtual MWGui::CountDialog* getCountDialog(); virtual MWGui::ConfirmationDialog* getConfirmationDialog(); @@ -289,8 +283,8 @@ namespace MWGui virtual void showCrosshair(bool show); virtual bool getSubtitlesEnabled(); - /// Turn visibility of *all* GUI elements on or off (HUD and all windows, except the console) - virtual bool toggleGui(); + /// Turn visibility of HUD on or off + virtual bool toggleHud(); virtual void disallowMouse(); virtual void allowMouse(); @@ -365,21 +359,6 @@ namespace MWGui virtual void updatePlayer(); - virtual void showCompanionWindow(MWWorld::Ptr actor); - virtual void startSpellMaking(MWWorld::Ptr actor); - virtual void startEnchanting(MWWorld::Ptr actor); - virtual void startSelfEnchanting(MWWorld::Ptr soulgem); - virtual void startTraining(MWWorld::Ptr actor); - virtual void startRepair(MWWorld::Ptr actor); - virtual void startRepairItem(MWWorld::Ptr item); - virtual void startRecharge(MWWorld::Ptr soulgem); - virtual void startTravel(const MWWorld::Ptr& actor); - virtual void startSpellBuying(const MWWorld::Ptr &actor); - virtual void startTrade(const MWWorld::Ptr &actor); - virtual void openContainer(const MWWorld::Ptr &container, bool loot); - virtual void showBook(const MWWorld::Ptr& item, bool showTakeButton); - virtual void showScroll(const MWWorld::Ptr& item, bool showTakeButton); - virtual void showSoulgemDialog (MWWorld::Ptr item); virtual void changePointer (const std::string& name); @@ -392,6 +371,9 @@ namespace MWGui virtual bool getCursorVisible(); + /// Call when mouse cursor or buttons are used. + virtual void setCursorActive(bool active); + /// Clear all savegame-specific data virtual void clear(); @@ -407,7 +389,7 @@ namespace MWGui /// Sets the current Modal /** Used to send exit command to active Modal when Esc is pressed **/ - virtual void addCurrentModal(WindowModal* input) {mCurrentModals.push(input);} + virtual void addCurrentModal(WindowModal* input); /// Removes the top Modal /** Used when one Modal adds another Modal @@ -417,11 +399,11 @@ namespace MWGui virtual void pinWindow (MWGui::GuiWindow window); /// Fade the screen in, over \a time seconds - virtual void fadeScreenIn(const float time, bool clearQueue); + virtual void fadeScreenIn(const float time, bool clearQueue, float delay); /// Fade the screen out to black, over \a time seconds - virtual void fadeScreenOut(const float time, bool clearQueue); + virtual void fadeScreenOut(const float time, bool clearQueue, float delay); /// Fade the screen to a specified percentage of black, over \a time seconds - virtual void fadeScreenTo(const int percent, const float time, bool clearQueue); + virtual void fadeScreenTo(const int percent, const float time, bool clearQueue, float delay); /// Darken the screen to a specified percentage virtual void setBlindness(const int percent); @@ -439,7 +421,7 @@ namespace MWGui // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path); - virtual std::string correctBookartPath(const std::string& path, int width, int height); + virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr); virtual std::string correctTexturePath(const std::string& path); virtual bool textureExists(const std::string& path); @@ -448,6 +430,8 @@ namespace MWGui virtual const MWGui::TextColours& getTextColours(); + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text); + private: const MWWorld::ESMStore* mStore; Resource::ResourceSystem* mResourceSystem; @@ -468,7 +452,7 @@ namespace MWGui MWWorld::Ptr mSelectedEnchantItem; MWWorld::Ptr mSelectedWeapon; - std::stack mCurrentModals; + std::vector mCurrentModals; // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map window). CustomMarkerCollection mCustomMarkers; @@ -476,38 +460,24 @@ namespace MWGui HUD *mHud; MapWindow *mMap; MWRender::LocalMap* mLocalMapRender; - MainMenu *mMenu; ToolTips *mToolTips; StatsWindow *mStatsWindow; MessageBoxManager *mMessageBoxManager; Console *mConsole; - JournalWindow* mJournal; DialogueWindow *mDialogueWindow; - ContainerWindow *mContainerWindow; DragAndDrop* mDragAndDrop; InventoryWindow *mInventoryWindow; ScrollWindow* mScrollWindow; BookWindow* mBookWindow; CountDialog* mCountDialog; TradeWindow* mTradeWindow; - SpellBuyingWindow* mSpellBuyingWindow; - TravelWindow* mTravelWindow; SettingsWindow* mSettingsWindow; ConfirmationDialog* mConfirmationDialog; - AlchemyWindow* mAlchemyWindow; SpellWindow* mSpellWindow; QuickKeysMenu* mQuickKeysMenu; LoadingScreen* mLoadingScreen; - LevelupDialog* mLevelupDialog; WaitDialog* mWaitDialog; - SpellCreationDialog* mSpellCreationDialog; - EnchantingDialog* mEnchantingDialog; - TrainingWindow* mTrainingWindow; - MerchantRepair* mMerchantRepair; SoulgemDialog* mSoulgemDialog; - Repair* mRepair; - Recharge* mRecharge; - CompanionWindow* mCompanionWindow; MyGUI::ImageBox* mVideoBackground; VideoWidget* mVideoWidget; ScreenFader* mWerewolfFader; @@ -517,6 +487,8 @@ namespace MWGui DebugWindow* mDebugWindow; JailScreen* mJailScreen; + std::vector mWindows; + Translation::Storage& mTranslationDataStorage; CharacterCreation* mCharGen; @@ -528,8 +500,8 @@ namespace MWGui bool mHitFaderEnabled; bool mWerewolfOverlayEnabled; bool mHudEnabled; - bool mGuiEnabled; bool mCursorVisible; + bool mCursorActive; void setCursorVisible(bool visible); @@ -542,6 +514,27 @@ namespace MWGui std::map mPlayerSkillValues; MyGUI::Gui *mGui; // Gui + + struct GuiModeState + { + GuiModeState(WindowBase* window) + { + mWindows.push_back(window); + } + GuiModeState(const std::vector& windows) + : mWindows(windows) {} + GuiModeState() {} + + void update(bool visible); + + std::vector mWindows; + + std::string mCloseSound; + std::string mOpenSound; + }; + // Defines the windows that should be shown in a particular GUI mode. + std::map mGuiModeStates; + // The currently active stack of GUI modes (top mode is the one we are in). std::vector mGuiModes; SDLUtil::SDLCursorManager* mCursorManager; @@ -569,10 +562,14 @@ namespace MWGui int mShowOwned; + ToUTF8::FromType mEncoding; + std::string mVersionDescription; MWGui::TextColours mTextColours; + std::unique_ptr mKeyboardNavigation; + /** * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Supported syntax: diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 6ce192559..816f9e18d 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -174,28 +174,52 @@ namespace MWInput void InputManager::setPlayerControlsEnabled(bool enabled) { - int nPlayerChannels = 18; - int playerChannels[] = {A_Activate, A_AutoMove, A_AlwaysRun, A_ToggleWeapon, + int playerChannels[] = {A_AutoMove, A_AlwaysRun, A_ToggleWeapon, A_ToggleSpell, A_Rest, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_Use, A_Journal}; - for(int i = 0; i < nPlayerChannels; i++) { + for(size_t i = 0; i < sizeof(playerChannels)/sizeof(playerChannels[0]); i++) { int pc = playerChannels[i]; mInputBinder->getChannel(pc)->setEnabled(enabled); } } - void InputManager::channelChanged(ICS::Channel* channel, float currentValue, float previousValue) + void InputManager::handleGuiArrowKey(int action) { - if (mDragDrop) + if (SDL_IsTextInputActive()) return; + MyGUI::KeyCode key; + switch (action) + { + case A_MoveLeft: + key = MyGUI::KeyCode::ArrowLeft; + break; + case A_MoveRight: + key = MyGUI::KeyCode::ArrowRight; + break; + case A_MoveForward: + key = MyGUI::KeyCode::ArrowUp; + break; + case A_MoveBackward: + default: + key = MyGUI::KeyCode::ArrowDown; + break; + } + MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0); + } + + void InputManager::channelChanged(ICS::Channel* channel, float currentValue, float previousValue) + { resetIdleTime (); int action = channel->getNumber(); + if (mDragDrop && action != A_GameMenu && action != A_Inventory) + return; + if((previousValue == 1 || previousValue == 0) && (currentValue==1 || currentValue==0)) { //Is a normal button press, so don't change it at all @@ -220,7 +244,10 @@ namespace MWInput if (mControlSwitch["playercontrols"]) { if (action == A_Use) - mPlayer->setAttackingOrSpell(currentValue != 0); + { + MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState(); + mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing); + } else if (action == A_Jump) mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); } @@ -231,9 +258,7 @@ namespace MWInput switch (action) { case A_GameMenu: - if(!(MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running - && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu)) - toggleMainMenu (); + toggleMainMenu (); break; case A_Screenshot: screenshot(); @@ -246,8 +271,13 @@ namespace MWInput break; case A_Activate: resetIdleTime(); - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - activate(); + activate(); + break; + case A_MoveLeft: + case A_MoveRight: + case A_MoveForward: + case A_MoveBackward: + handleGuiArrowKey(action); break; case A_Journal: toggleJournal (); @@ -301,7 +331,7 @@ namespace MWInput showQuickKeysMenu(); break; case A_ToggleHUD: - MWBase::Environment::get().getWindowManager()->toggleGui(); + MWBase::Environment::get().getWindowManager()->toggleHud(); break; case A_ToggleDebug: MWBase::Environment::get().getWindowManager()->toggleDebugWindow(); @@ -380,8 +410,6 @@ namespace MWInput mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); mInputManager->capture(disableEvents); - // inject some fake mouse movement to force updating MyGUI's widget states - MyGUI::InputManager::getInstance().injectMouseMove( int(mGuiCursorX), int(mGuiCursorY), mMouseWheel); if (mControlsDisabled) { @@ -419,6 +447,7 @@ namespace MWInput MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); mInputManager->warpMouse(static_cast(mGuiCursorX/mInvUiScalingFactor), static_cast(mGuiCursorY/mInvUiScalingFactor)); + MWBase::Environment::get().getWindowManager()->setCursorActive(true); } } if (mMouseLookEnabled) @@ -685,13 +714,17 @@ namespace MWInput SDL_StopTextInput(); bool consumed = false; - if (kc != OIS::KC_UNASSIGNED) + if (kc != OIS::KC_UNASSIGNED && !mInputBinder->detectingBindingState()) { - consumed = SDL_IsTextInputActive() && - ( !(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym)); // Little trick to check if key is printable - bool guiFocus = MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); - setPlayerControlsEnabled(!guiFocus); + consumed = MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); + if (SDL_IsTextInputActive() && // Little trick to check if key is printable + ( !(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym))) + consumed = true; + setPlayerControlsEnabled(!consumed); } + if (arg.repeat) + return; + if (!mControlsDisabled && !consumed) mInputBinder->keyPressed (arg); mJoystickLastUsed = false; @@ -710,7 +743,8 @@ namespace MWInput mJoystickLastUsed = false; OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); - setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc))); + if (!mInputBinder->detectingBindingState()) + setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc))); mInputBinder->keyReleased (arg); } @@ -731,6 +765,7 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); } } + MWBase::Environment::get().getWindowManager()->setCursorActive(true); } setPlayerControlsEnabled(!guiMode); @@ -775,6 +810,10 @@ namespace MWInput mMouseWheel = int(arg.z); MyGUI::InputManager::getInstance().injectMouseMove( int(mGuiCursorX), int(mGuiCursorY), mMouseWheel); + // FIXME: inject twice to force updating focused widget states (tooltips) resulting from changing the viewport by scroll wheel + MyGUI::InputManager::getInstance().injectMouseMove( int(mGuiCursorX), int(mGuiCursorY), mMouseWheel); + + MWBase::Environment::get().getWindowManager()->setCursorActive(true); } if (mMouseLookEnabled && !mControlsDisabled) @@ -1106,12 +1145,10 @@ namespace MWInput && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_MainMenu && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { - MWBase::Environment::get().getWindowManager()->playSound ("book open"); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); } else if(MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Journal)) { - MWBase::Environment::get().getWindowManager()->playSound ("book close"); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Journal); } } @@ -1148,7 +1185,12 @@ namespace MWInput void InputManager::activate() { - if (mControlSwitch["playercontrols"]) + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + if (!SDL_IsTextInputActive()) + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Return, 0); + } + else if (mControlSwitch["playercontrols"]) mPlayer->activate(); } @@ -1283,6 +1325,17 @@ namespace MWInput control->setInitialValue(0.0f); mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } + + if (i == A_LookLeftRight && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_4) && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_6)) + { + mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_6, ICS::Control::INCREASE); + mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_4, ICS::Control::DECREASE); + } + if (i == A_LookUpDown && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_8) && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_2)) + { + mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_2, ICS::Control::INCREASE); + mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_8, ICS::Control::DECREASE); + } } } } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 8809f44cd..cba7fc743 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -214,6 +214,7 @@ namespace MWInput void updateIdleTime(float dt); void setPlayerControlsEnabled(bool enabled); + void handleGuiArrowKey(int action); void updateCursorMode(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0bb6d2c5d..aced04812 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -87,9 +87,7 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a } } else - { - actor.getClass().getContainerStore(actor).remove(item, 1, actor); - } + actor.getClass().getInventoryStore(actor).remove(item, 1, actor, true); } class CheckActorCommanded : public MWMechanics::EffectSourceVisitor @@ -235,6 +233,9 @@ namespace MWMechanics gem->getContainerStore()->unstack(*gem, caster); gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); + // Restack the gem with other gems with the same soul + gem->getContainerStore()->restack(*gem); + mTrapped = true; if (caster == getPlayer()) @@ -443,10 +444,15 @@ namespace MWMechanics End of tes3mp addition */ } - + // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc()) { + // Check if the creature is too far + static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(); + if (sqrDist > fAlarmRadius * fAlarmRadius) + return; + bool followerOrEscorter = false; for (std::list::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it) { @@ -1046,7 +1052,7 @@ namespace MWMechanics // ...But, only the player makes a sound. if(isPlayer) MWBase::Environment::get().getSoundManager()->playSound("torch out", - 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoEnv); + 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } } } @@ -1063,7 +1069,8 @@ namespace MWMechanics if (player.getClass().getNpcStats(player).isWerewolf()) return; - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()) + if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat() + && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); static const int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); @@ -1170,6 +1177,39 @@ namespace MWMechanics } } + bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) + { + if (!actor.getClass().isActor()) + return false; + + // If an observer is NPC, check if he detected an actor + if (!observer.isEmpty() && observer.getClass().isNpc()) + { + return + MWBase::Environment::get().getWorld()->getLOS(observer, actor) && + MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); + } + + // Otherwise check if any actor in AI processing range sees the target actor + std::vector actors; + osg::Vec3f position (actor.getRefData().getPosition().asVec3()); + getObjectsInRange(position, aiProcessingDistance, actors); + for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + if (*it == actor) + continue; + + bool result = + MWBase::Environment::get().getWorld()->getLOS(*it, actor) && + MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, *it); + + if (result) + return true; + } + + return false; + } + void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) { PtrActorMap::iterator iter = mActors.find(old); @@ -1198,6 +1238,46 @@ namespace MWMechanics } } + void Actors::updateCombatMusic () + { + MWWorld::Ptr player = getPlayer(); + int hostilesCount = 0; // need to know this to play Battle music + + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) + { + bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() + <= sqrAiProcessingDistance; + + if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) + { + if (iter->first != player) + { + MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; + } + } + } + } + + // check if we still have any player enemies to switch music + static int currentMusic = 0; + + if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && + MWBase::Environment::get().getSoundManager()->isMusicPlaying())) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + currentMusic = 1; + } + else if (currentMusic != 2 && hostilesCount > 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + currentMusic = 2; + } + + } + void Actors::update (float duration, bool paused) { if(!paused) @@ -1215,8 +1295,6 @@ namespace MWMechanics MWWorld::Ptr player = getPlayer(); - int hostilesCount = 0; // need to know this to play Battle music - /// \todo move update logic to Actor class where appropriate std::map > cachedAllies; // will be filled as engageCombat iterates @@ -1342,12 +1420,22 @@ namespace MWMechanics float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; - for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + + // Unconsious actor can not track target + // Also actors in combat and pursue mode do not bother to headtrack + if (!stats.getKnockedDown() && + !stats.getAiSequence().isInCombat() && + !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue)) { - if (it->first == iter->first) - continue; - updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance); + for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + { + if (it->first == iter->first) + continue; + updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance); + } } + iter->second->getCharacterController()->setHeadTrackTarget(headTrackTarget); } @@ -1359,8 +1447,6 @@ namespace MWMechanics CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), iter->second->getAiState(), duration); - - if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; } } /* @@ -1436,21 +1522,6 @@ namespace MWMechanics killDeadActors(); - // check if we still have any player enemies to switch music - static int currentMusic = 0; - - if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && - MWBase::Environment::get().getSoundManager()->isMusicPlaying())) - { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - currentMusic = 1; - } - else if (currentMusic != 2 && hostilesCount > 0) - { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - currentMusic = 2; - } - static float sneakTimer = 0.f; // times update of sneak icon // if player is in sneak state see if anyone detects him @@ -1517,6 +1588,8 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); } } + + updateCombatMusic(); } void Actors::killDeadActors() @@ -1798,6 +1871,17 @@ namespace MWMechanics } } + bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) + { + for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + { + if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) + return true; + } + + return false; + } + std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) { std::list list; @@ -1983,8 +2067,9 @@ namespace MWMechanics { std::string id = reader.getHString(); int count; - reader.getHNT (count, "COUN"); - mDeathCount[id] = count; + reader.getHNT(count, "COUN"); + if (MWBase::Environment::get().getWorld()->getStore().find(id)) + mDeathCount[id] = count; } } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 680e3c9cb..c3f7b2b42 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -57,6 +57,10 @@ namespace MWMechanics PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { return mActors.end(); } + /// Check if the target actor was detected by an observer + /// If the observer is a non-NPC, check all actors in AI processing distance as observers + bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects (const MWWorld::Ptr& ptr); @@ -77,6 +81,9 @@ namespace MWMechanics void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); ///< Deregister all actors (except for \a ignore) in the given cell. + void updateCombatMusic(); + ///< Update combat music state + void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement @@ -121,15 +128,17 @@ namespace MWMechanics bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); - void forceStateUpdate(const MWWorld::Ptr &ptr); + void forceStateUpdate(const MWWorld::Ptr &ptr); - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); - void skipAnimation(const MWWorld::Ptr& ptr); - bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); - void persistAnimationStates(); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); + void skipAnimation(const MWWorld::Ptr& ptr); + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); + void persistAnimationStates(); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); + bool isAnyObjectInRange(const osg::Vec3f& position, float radius); + void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId); ///Returns the list of actors which are siding with the given actor in fights diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 1d761ae6b..6dd98905a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -26,6 +26,7 @@ #include "../mwrender/animation.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" @@ -440,7 +441,7 @@ namespace MWMechanics int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) { - if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i)) + if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) { points.push_back(pathgrid->mPoints[static_cast(i)]); } @@ -558,6 +559,22 @@ namespace MWMechanics void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { + // get the range of the target's weapon + MWWorld::Ptr targetWeapon = MWWorld::Ptr(); + const MWWorld::Class& targetClass = target.getClass(); + + if (targetClass.hasInventoryStore(target)) + { + MWMechanics::WeaponType weapType = WeapType_None; + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); + if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) + targetWeapon = *weaponSlot; + } + + bool targetUsesRanged = false; + float rangeAttackOfTarget = ActionWeapon(targetWeapon).getCombatRange(targetUsesRanged); + if (mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); @@ -566,28 +583,6 @@ namespace MWMechanics // dodge movements (for NPCs and bipedal creatures) else if (actor.getClass().isBipedal(actor)) { - // get the range of the target's weapon - float rangeAttackOfTarget = 0.f; - MWWorld::Ptr targetWeapon = MWWorld::Ptr(); - const MWWorld::Class& targetClass = target.getClass(); - - if (targetClass.hasInventoryStore(target)) - { - MWMechanics::WeaponType weapType = WeapType_None; - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); - if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) - targetWeapon = *weaponSlot; - } - - std::shared_ptr targetWeaponAction (new ActionWeapon(targetWeapon)); - - if (targetWeaponAction.get()) - { - bool isRangedCombat = false; - rangeAttackOfTarget = targetWeaponAction->getCombatRange(isRangedCombat); - } - // apply sideway movement (kind of dodging) with some probability // if actor is within range of target's weapon if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) @@ -598,16 +593,18 @@ namespace MWMechanics } } - // Below behavior for backing up during ranged combat differs from vanilla. - // Vanilla is observed as backing up only as far as fCombatDistance or - // opponent's weapon range, or not backing up if opponent is also using a ranged weapon - if (isDistantCombat && distToTarget < rangeAttack / 4) + // Backing up behaviour + // Actor backs up slightly further away than opponent's weapon range + // (in vanilla - only as far as oponent's weapon range), + // or not at all if opponent is using a ranged weapon + if (isDistantCombat) { // actor should not back up into water if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f)) return; - mMovement.mPosition[1] = -1; + if (!targetUsesRanged && distToTarget <= rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon + mMovement.mPosition[1] = -1; } } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index d44498966..df636dd56 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -115,6 +115,7 @@ namespace MWMechanics isRanged = false; static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); + static const float fProjectileMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); if (mWeapon.isEmpty()) { @@ -128,7 +129,7 @@ namespace MWMechanics if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) { isRanged = true; - return 1000.f; + return fProjectileMaxSpeed; } else return weapon->mData.mReach * fCombatDistance; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index f837ad2ee..398e84448 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -14,6 +14,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "steering.hpp" @@ -100,14 +101,23 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr { bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander - mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first + + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // check if actor can move along z-axis + bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || world->isFlying(actor); + + // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. + if (actorCanMoveByZ || getTypeId() != TypeIdWander) + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -220,20 +230,23 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur } } -bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) { - const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - // check if actor can move along z-axis - bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || world->isFlying(actor); - - // don't use pathgrid when actor can move in 3 dimensions - bool isPathClear = actorCanMoveByZ; + const ESM::CellId& id = cell->getCell()->getCellId(); + // static cache is OK for now, pathgrids can never change during runtime + typedef std::map > CacheMap; + static CacheMap cache; + CacheMap::iterator found = cache.find(id); + if (found == cache.end()) + { + cache.insert(std::make_pair(id, std::unique_ptr(new MWMechanics::PathgridGraph(cell)))); + } + return *cache[id].get(); +} - if (!isPathClear - && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) +{ + if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST) { // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index acbd87908..06b4adf61 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -27,6 +27,7 @@ namespace MWMechanics const float AI_REACTION_TIME = 0.25f; class CharacterController; + class PathgridGraph; /// \brief Base class for AI packages class AiPackage @@ -110,7 +111,7 @@ namespace MWMechanics /// If a shortcut is possible then path will be cleared and filled with the destination point. /// \param destInLOS If not NULL function will return ray cast check result /// \return If can shortcut the path - bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear); /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); @@ -119,6 +120,8 @@ namespace MWMechanics void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index f7e529cfe..c3aff89b8 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -80,9 +80,13 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + ESM::Position aPos = actor.getRefData().getPosition(); + float pathTolerance = 100.0; - if (pathTo(actor, dest, duration, 100)) { + if (pathTo(actor, dest, duration, pathTolerance) && + std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction + { /* Start of tes3mp addition diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index b44b187ad..8199170dc 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -17,6 +17,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" @@ -200,6 +201,7 @@ namespace MWMechanics stopWalking(actor, storage); currentCell = actor.getCell(); storage.mPopulateAvailableNodes = true; + mStoredInitialActorPosition = false; } mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); @@ -216,7 +218,7 @@ namespace MWMechanics ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) storage.setState(Wander_Walking); @@ -263,9 +265,20 @@ namespace MWMechanics getAllowedNodes(actor, currentCell->getCell(), storage); } + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if(actorCanMoveByZ && mDistance > 0) { + // Typically want to idle for a short time before the next wander + if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) { + wanderNearStart(actor, storage, mDistance); + } + + storage.mCanWanderAlongPathGrid = false; + } // If the package has a wander distance but no pathgrid is available, // randomly idle or wander near spawn point - if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { + else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 96) { wanderNearStart(actor, storage, mDistance); @@ -348,7 +361,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { @@ -371,7 +384,7 @@ namespace MWMechanics do { // Determine a random location within radius of original position const float pi = 3.14159265359f; - const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; + const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); @@ -382,7 +395,7 @@ namespace MWMechanics // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || (isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) { - mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); + mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell())); mPathFinder.addPointToPath(destinationPosition); if (mPathFinder.isPathConstructed()) @@ -450,11 +463,10 @@ namespace MWMechanics // Check if an idle actor is too close to a door - if so start walking storage.mDoorCheckDuration += duration; - static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); - if (storage.mDoorCheckDuration >= DOOR_CHECK_INTERVAL) { storage.mDoorCheckDuration = 0; // restart timer + static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); if (mDistance && // actor is not intended to be stationary proximityToDoor(actor, distance*1.6f)) { @@ -530,11 +542,10 @@ namespace MWMechanics void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) { - static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); - if (mObstacleCheck.isEvading()) { // first check if we're walking into a door + static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); if (proximityToDoor(actor, distance)) { // remove allowed points then select another random destination @@ -661,13 +672,14 @@ namespace MWMechanics { unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); + ToWorldCoordinates(dest, storage.mCell->getCell()); // actor position is already in world coordinates ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { @@ -800,20 +812,80 @@ namespace MWMechanics int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; - state.moveIn(new AiWanderStorage()); + ESM::Pathgrid::Point worldDest = dest; + ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + + bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + + // add offset only if the selected pathgrid is occupied by another actor + if (isPathGridOccupied) + { + ESM::Pathgrid::PointList points; + getNeighbouringNodes(dest, actor.getCell(), points); + + // there are no neighbouring nodes, nowhere to move + if (points.empty()) + return; + + int initialSize = points.size(); + bool isOccupied = false; + // AI will try to move the NPC towards every neighboring node until suitable place will be found + for (int i = 0; i < initialSize; i++) + { + int randomIndex = Misc::Rng::rollDice(points.size()); + ESM::Pathgrid::Point connDest = points[randomIndex]; + + // add an offset towards random neighboring node + osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest); + float length = dir.length(); + dir.normalize(); + + for (int j = 1; j <= 3; j++) + { + // move for 5-15% towards random neighboring node + dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f)); + worldDest = dest; + ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + + isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + + if (!isOccupied) + break; + } + + if (!isOccupied) + break; + + // Will try an another neighboring node + points.erase(points.begin()+randomIndex); + } + + // there is no free space, nowhere to move + if (isOccupied) + return; + } + + // place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground. + // Adding 20 in adjustPosition() is not enough. + dest.mZ += 60; - dest.mX += OffsetToPreventOvercrowding(); - dest.mY += OffsetToPreventOvercrowding(); ToWorldCoordinates(dest, actor.getCell()->getCell()); + state.moveIn(new AiWanderStorage()); + MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); } - int AiWander::OffsetToPreventOvercrowding() + void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points) { - return static_cast(20 * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + + int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest)); + + getPathGridGraph(currentCell).getNeighbouringPoints(index, points); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) @@ -854,7 +926,7 @@ namespace MWMechanics { osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance && - cellStore->isPointConnected(closestPointIndex, counter)) + getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter)) { storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); pointIndex = counter; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 01d889e2f..6266a7708 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -104,6 +104,8 @@ namespace MWMechanics bool mHasDestination; osg::Vec3f mDestination; + void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points); + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 124468641..48705dc72 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -262,22 +262,16 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co void MWMechanics::Alchemy::removeIngredients() { - bool needsUpdate = false; - for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty()) { iter->getContainerStore()->remove(*iter, 1, mAlchemist); if (iter->getRefData().getCount()<1) - { - needsUpdate = true; *iter = MWWorld::Ptr(); - } } - if (needsUpdate) - updateEffects(); + updateEffects(); } void MWMechanics::Alchemy::addPotion (const std::string& name) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 66b889e95..6e6570b00 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -202,6 +202,8 @@ static const StateInfo sMovementList[] = { { CharState_TurnLeft, "turnleft" }, { CharState_TurnRight, "turnright" }, + { CharState_SwimTurnLeft, "swimturnleft" }, + { CharState_SwimTurnRight, "swimturnright" }, }; static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; @@ -261,32 +263,62 @@ void CharacterController::refreshHitRecoilAnims() bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); if(mHitState == CharState_None) { if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) && mAnimation->hasAnimation("knockout")) { - mHitState = CharState_KnockOut; - mCurrentHit = "knockout"; + mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds + if (isSwimming && mAnimation->hasAnimation("swimknockout")) + { + mHitState = CharState_SwimKnockOut; + mCurrentHit = "swimknockout"; + } + else + { + mHitState = CharState_KnockOut; + mCurrentHit = "knockout"; + } + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); } else if(knockdown && mAnimation->hasAnimation("knockdown")) { - mHitState = CharState_KnockDown; - mCurrentHit = "knockdown"; + if (isSwimming && mAnimation->hasAnimation("swimknockdown")) + { + mHitState = CharState_SwimKnockDown; + mCurrentHit = "swimknockdown"; + } + else + { + mHitState = CharState_KnockDown; + mCurrentHit = "knockdown"; + } + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } else if (recovery) { - std::string anim = chooseRandomGroup("hit"); - if (mAnimation->hasAnimation(anim)) + std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit"); + if (isSwimming && mAnimation->hasAnimation(anim)) { - mHitState = CharState_Hit; + mHitState = CharState_SwimHit; mCurrentHit = anim; mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } + else + { + anim = chooseRandomGroup("hit"); + if (mAnimation->hasAnimation(anim)) + { + mHitState = CharState_Hit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + } } else if (block && mAnimation->hasAnimation("shield")) { @@ -298,7 +330,7 @@ void CharacterController::refreshHitRecoilAnims() } // Cancel upper body animations - if (mHitState == CharState_KnockDown || mHitState == CharState_KnockOut) + if (isKnockedOut() || isKnockedDown()) { if (mUpperBodyState > UpperCharState_WeapEquiped) { @@ -323,9 +355,10 @@ void CharacterController::refreshHitRecoilAnims() mPtr.getClass().getCreatureStats(mPtr).setBlock(false); mHitState = CharState_None; } - else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0 + && mTimeUntilWake <= 0) { - mHitState = CharState_KnockDown; + mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; mAnimation->disable(mCurrentHit); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); } @@ -357,21 +390,29 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState } } - if(mJumpState == JumpState_InAir) + if (!mCurrentJump.empty()) { mAnimation->disable(mCurrentJump); - mCurrentJump = jumpAnimName; - if (mAnimation->hasAnimation("jump")) - mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, + mCurrentJump.clear(); + } + + if(mJumpState == JumpState_InAir) + { + if (mAnimation->hasAnimation(jumpAnimName)) + { + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); + mCurrentJump = jumpAnimName; + } } - else + else if (mJumpState == JumpState_Landing) { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - if (mAnimation->hasAnimation("jump")) + if (mAnimation->hasAnimation(jumpAnimName)) + { mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); + mCurrentJump = jumpAnimName; + } } } } @@ -551,7 +592,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed if ((mUpperBodyState != UpperCharState_Nothing - || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight) + || (mMovementState != CharState_None && !isTurning()) || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) idle = CharState_None; @@ -637,6 +678,12 @@ void CharacterController::playDeath(float startpoint, CharacterState death) case CharState_SwimDeath: mCurrentDeath = "swimdeath"; break; + case CharState_SwimDeathKnockDown: + mCurrentDeath = "swimdeathknockdown"; + break; + case CharState_SwimDeathKnockOut: + mCurrentDeath = "swimdeathknockout"; + break; case CharState_DeathKnockDown: mCurrentDeath = "deathknockdown"; break; @@ -704,7 +751,15 @@ void CharacterController::playRandomDeath(float startpoint) MWBase::Environment::get().getWorld()->useDeathCamera(); } - if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) + if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) + { + mDeathState = CharState_SwimDeathKnockDown; + } + else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) + { + mDeathState = CharState_SwimDeathKnockOut; + } + else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) { mDeathState = CharState_SwimDeath; } @@ -744,6 +799,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSecondsOfRunning(0) , mTurnAnimationThreshold(0) , mAttackingOrSpell(false) + , mTimeUntilWake(0.f) { if(!mAnimation) return; @@ -873,8 +929,8 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: { // 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); + sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, + MWSound::PlayMode::NoPlayerLocal); } else { @@ -906,16 +962,17 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) { - if (groupname == "attack1") + if (groupname == "attack1" || groupname == "swimattack1") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if (groupname == "attack2") + else if (groupname == "attack2" || groupname == "swimattack2") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if (groupname == "attack3") + else if (groupname == "attack3" || groupname == "swimattack3") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else mPtr.getClass().hit(mPtr, mAttackStrength); } - else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 + else if (!groupname.empty() + && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) && evt.compare(off, len, "start") == 0) { std::multimap::const_iterator hitKey = key; @@ -935,11 +992,11 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: } if (!hasHitKey) { - if (groupname == "attack1") + if (groupname == "attack1" || groupname == "swimattack1") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if (groupname == "attack2") + else if (groupname == "attack2" || groupname == "swimattack2") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if (groupname == "attack3") + else if (groupname == "attack3" || groupname == "swimattack3") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); } } @@ -1095,13 +1152,14 @@ bool CharacterController::updateCreatureState() } if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); int roll = Misc::Rng::rollDice(3); // [0, 2] if (roll == 0) - mCurrentWeapon = "attack1"; + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; else if (roll == 1) - mCurrentWeapon = "attack2"; + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; else - mCurrentWeapon = "attack3"; + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; } if (!mCurrentWeapon.empty()) @@ -1176,8 +1234,13 @@ bool CharacterController::updateWeaponState() priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; - if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut - && mHitState != CharState_Hit) + + // We should not play equipping animation and sound during weapon->weapon transition + bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && + mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; + + if(weaptype != mWeaponType && !isKnockedOut() && + !isKnockedDown() && !isRecovery()) { forcestateupdate = true; @@ -1198,13 +1261,16 @@ bool CharacterController::updateWeaponState() else { getWeaponGroup(weaptype, weapgroup); - mAnimation->showWeapons(false); mAnimation->setWeaponGroup(weapgroup); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; + if (!isStillWeapon) + { + mAnimation->showWeapons(false); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_EquipingWeap; + } if(isWerewolf) { @@ -1218,7 +1284,7 @@ bool CharacterController::updateWeaponState() } } - if(!soundid.empty()) + if(!soundid.empty() && !isStillWeapon) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); @@ -1237,8 +1303,8 @@ bool CharacterController::updateWeaponState() && mWeaponType == WeapType_None) { if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) - sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, - MWBase::SoundManager::Play_Loop); + sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, + MWSound::PlayMode::Loop); } else sndMgr->stopSound3D(mPtr, "WolfRun"); @@ -1272,16 +1338,26 @@ bool CharacterController::updateWeaponState() bool animPlaying; if(mAttackingOrSpell) { - mIdleState = CharState_None; + MWWorld::Ptr player = getPlayer(); + + // We should reset player's idle animation in the first-person mode. + if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) + mIdleState = CharState_None; + + // In other cases we should not break swim and sneak animations + if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) + mIdleState = CharState_None; + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + mAttackStrength = 0; if(mWeaponType == WeapType_Spell) { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation mAttackingOrSpell = false; - if (mPtr == getPlayer()) + if (mPtr == player) { MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } @@ -1291,7 +1367,7 @@ bool CharacterController::updateWeaponState() // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == getPlayer()) + if (mPtr == player) { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); @@ -1322,7 +1398,7 @@ bool CharacterController::updateWeaponState() MWMechanics::CastSpell cast(mPtr, NULL); cast.playSpellCastingEffects(spellid); - const ESM::Spell *spell = store.get().find(spellid); + const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::MagicEffect *effect; @@ -1388,9 +1464,8 @@ bool CharacterController::updateWeaponState() if(!resultMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); if(!resultSound.empty()) - MWBase::Environment::get().getSoundManager()->playSound3D(target, - resultSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, - MWBase::SoundManager::Play_Normal); + MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, + 1.0f, 1.0f); } else if (ammunition) { @@ -1436,13 +1511,13 @@ bool CharacterController::updateWeaponState() } animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) mAttackStrength = complete; } else { animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) { float attackStrength = complete; if (!mPtr.getClass().isNpc()) @@ -1480,7 +1555,7 @@ bool CharacterController::updateWeaponState() complete = 0.f; mUpperBodyState = UpperCharState_MaxAttackToMinHit; } - else if (mHitState == CharState_KnockDown) + else if (isKnockedDown()) { if (mUpperBodyState > UpperCharState_WeapEquiped) mUpperBodyState = UpperCharState_WeapEquiped; @@ -1663,6 +1738,9 @@ void CharacterController::update(float duration) updateMagicEffects(); + if (isKnockedOut()) + mTimeUntilWake -= duration; + bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if(!cls.isActor()) @@ -1755,7 +1833,6 @@ void CharacterController::update(float duration) mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; isrunning = isrunning && mHasMovedInXY; - // advance athletics if(mHasMovedInXY && mPtr == getPlayer()) { @@ -1822,20 +1899,11 @@ void CharacterController::update(float duration) if(sneak || inwater || flying) vec.z() = 0.0f; - if (inwater || flying) - cls.getCreatureStats(mPtr).land(); - bool inJump = true; if(!onground && !flying && !inwater) { // In the air (either getting up —ascending part of jump— or falling). - if (world->isSlowFalling(mPtr)) - { - // SlowFalling spell effect is active, do not keep previous fall height - cls.getCreatureStats(mPtr).land(); - } - forcestateupdate = (mJumpState != JumpState_InAir); jumpstate = JumpState_InAir; @@ -1881,7 +1949,7 @@ void CharacterController::update(float duration) } } } - else if(mJumpState == JumpState_InAir) + else if(mJumpState == JumpState_InAir && !inwater && !flying) { forcestateupdate = true; jumpstate = JumpState_Landing; @@ -1919,7 +1987,8 @@ void CharacterController::update(float duration) } else { - jumpstate = JumpState_None; + jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; + vec.z() = 0.0f; inJump = false; @@ -1946,28 +2015,31 @@ void CharacterController::update(float duration) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - else if(rot.z() != 0.0f && !inwater && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) + else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) { if(rot.z() > 0.0f) - movestate = CharState_TurnRight; + { + movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; + mAnimation->disable(mCurrentJump); + } else if(rot.z() < 0.0f) - movestate = CharState_TurnLeft; + { + movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + mAnimation->disable(mCurrentJump); + } } } mTurnAnimationThreshold -= duration; - if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft) + if (isTurning()) mTurnAnimationThreshold = 0.05f; - else if (movestate == CharState_None && (mMovementState == CharState_TurnRight || mMovementState == CharState_TurnLeft) + else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0) { movestate = mMovementState; } - if (onground) - cls.getCreatureStats(mPtr).land(); - - if(movestate != CharState_None && movestate != CharState_TurnLeft && movestate != CharState_TurnRight) + if(movestate != CharState_None && !isTurning()) clearAnimQueue(); if(mAnimQueue.empty() || inwater || sneak) @@ -1993,7 +2065,7 @@ void CharacterController::update(float duration) if (inJump) mMovementAnimationControlled = false; - if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + if (isTurning()) { if (duration > 0) mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); @@ -2006,7 +2078,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut) + if(!isKnockedDown() && !isKnockedOut()) { if (rot != osg::Vec3f()) world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); @@ -2158,76 +2230,71 @@ void CharacterController::unpersistAnimationState() bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) { if(!mAnimation || !mAnimation->hasAnimation(groupname)) - { - std::cerr<< "Animation "<getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && + mAnimation->isPlaying(groupname)) { - // If this animation is a looped animation (has a "loop start" key) that is already playing - // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count - // and remove any other animations that were queued. - // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly. - if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && - mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && - mAnimation->isPlaying(groupname)) - { - float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); + float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); - if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key - endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); + if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key + endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); - if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) - { - mAnimQueue.resize(1); - return true; - } + if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) + { + mAnimQueue.resize(1); + return true; } + } - count = std::max(count, 1); + count = std::max(count, 1); - AnimationQueueEntry entry; - entry.mGroup = groupname; - entry.mLoopCount = count-1; - entry.mPersist = persist; + AnimationQueueEntry entry; + entry.mGroup = groupname; + entry.mLoopCount = count-1; + entry.mPersist = persist; - if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) - { - clearAnimQueue(); - mAnimQueue.push_back(entry); + if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) + { + clearAnimQueue(); + mAnimQueue.push_back(entry); - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); - mIdleState = CharState_SpecialIdle; - bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); - mAnimation->play(groupname, Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, - ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); + mIdleState = CharState_SpecialIdle; + bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); + mAnimation->play(groupname, Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, + ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); - /* - Start of tes3mp addition + /* + Start of tes3mp addition - If we are the cell authority over this actor, we need to record this new - animation for it - */ - if (mwmp::Main::get().getCellController()->isLocalActor(mPtr)) - { - mwmp::LocalActor *actor = mwmp::Main::get().getCellController()->getLocalActor(mPtr); - actor->animation.groupname = groupname; - actor->animation.mode = mode; - actor->animation.count = count; - actor->animation.persist = persist; - } - /* - End of tes3mp addition - */ - } - else if(mode == 0) + If we are the cell authority over this actor, we need to record this new + animation for it + */ + if (mwmp::Main::get().getCellController()->isLocalActor(mPtr)) { - mAnimQueue.resize(1); - mAnimQueue.push_back(entry); + mwmp::LocalActor *actor = mwmp::Main::get().getCellController()->getLocalActor(mPtr); + actor->animation.groupname = groupname; + actor->animation.mode = mode; + actor->animation.count = count; + actor->animation.persist = persist; } + /* + End of tes3mp addition + */ + } + else if(mode == 0) + { + mAnimQueue.resize(1); + mAnimQueue.push_back(entry); } return true; } @@ -2368,9 +2435,30 @@ bool CharacterController::isReadyToBlock() const return updateCarriedLeftVisible(mWeaponType); } +bool CharacterController::isKnockedDown() const +{ + return mHitState == CharState_KnockDown || + mHitState == CharState_SwimKnockDown; +} + bool CharacterController::isKnockedOut() const { - return mHitState == CharState_KnockOut; + return mHitState == CharState_KnockOut || + mHitState == CharState_SwimKnockOut; +} + +bool CharacterController::isTurning() const +{ + return mMovementState == CharState_TurnLeft || + mMovementState == CharState_TurnRight || + mMovementState == CharState_SwimTurnLeft || + mMovementState == CharState_SwimTurnRight; +} + +bool CharacterController::isRecovery() const +{ + return mHitState == CharState_Hit || + mHitState == CharState_SwimHit; } bool CharacterController::isAttackingOrSpell() const diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 9bcad0994..016d88289 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -88,6 +88,8 @@ enum CharacterState { CharState_TurnLeft, CharState_TurnRight, + CharState_SwimTurnLeft, + CharState_SwimTurnRight, CharState_Jump, @@ -97,12 +99,17 @@ enum CharacterState { CharState_Death4, CharState_Death5, CharState_SwimDeath, + CharState_SwimDeathKnockDown, + CharState_SwimDeathKnockOut, CharState_DeathKnockDown, CharState_DeathKnockOut, CharState_Hit, + CharState_SwimHit, CharState_KnockDown, CharState_KnockOut, + CharState_SwimKnockDown, + CharState_SwimKnockOut, CharState_Block }; @@ -197,6 +204,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool mAttackingOrSpell; + float mTimeUntilWake; + void setAttackTypeBasedOnMovement(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); @@ -265,9 +274,12 @@ public: bool isAttackPrepairing() const; bool isReadyToBlock() const; + bool isKnockedDown() const; bool isKnockedOut() const; + bool isRecovery() const; bool isSneaking() const; bool isRunning() const; + bool isTurning() const; bool isAttackingOrSpell() const; void setAttackingOrSpell(bool attackingOrSpell); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index d59bddc90..071f61ab0 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -497,26 +497,6 @@ namespace MWMechanics } } - bool isEnvironmentCompatible(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) - { - const MWWorld::Class& attackerClass = attacker.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - // If attacker is fish, victim must be in water - if (attackerClass.isPureWaterCreature(attacker)) - { - return world->isWading(victim); - } - - // If attacker can't swim, victim must not be in water - if (!attackerClass.canSwim(attacker)) - { - return !world->isSwimming(victim); - } - - return true; - } - float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) { osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 12961dc4b..8b93ca204 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -39,10 +39,6 @@ void getHandToHandDamage (const MWWorld::Ptr& attacker, const MWWorld::Ptr& vict /// Apply the fatigue loss incurred by attacking with the given weapon (weapon may be empty = hand-to-hand) void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength); -/// Can attacker operate in victim's environment? -/// e.g. If attacker is a fish, is victim in water? Or, if attacker can't swim, is victim on land? -bool isEnvironmentCompatible(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); - float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b922c9d74..ce1e67b89 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -436,6 +436,11 @@ namespace MWMechanics mObjects.update(duration, paused); } + bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) + { + return mActors.isActorDetected(actor, observer); + } + bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr) { return mActors.isAttackPrepairing(ptr); @@ -861,17 +866,75 @@ namespace MWMechanics mAI = true; } - bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim) + bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) + { + static std::set boundItemIDCache; + + // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's for some reason + if (boundItemIDCache.empty()) + { + // Build a list of known bound item ID's + const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); + + for (MWWorld::Store::iterator currentIteration = gameSettings.begin(); currentIteration != gameSettings.end(); ++currentIteration) + { + const ESM::GameSetting ¤tSetting = *currentIteration; + std::string currentGMSTID = currentSetting.mId; + Misc::StringUtils::lowerCaseInPlace(currentGMSTID); + + // Don't bother checking this GMST if it's not a sMagicBound* one. + const std::string& toFind = "smagicbound"; + if (currentGMSTID.compare(0, toFind.length(), toFind) != 0) + continue; + + // All sMagicBound* GMST's should be of type string + std::string currentGMSTValue = currentSetting.getString(); + Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); + + boundItemIDCache.insert(currentGMSTValue); + } + } + + // Perform bound item check and assign the Flag_Bound bit if it passes + std::string tempItemID = item.getCellRef().getRefId(); + Misc::StringUtils::lowerCaseInPlace(tempItemID); + + if (boundItemIDCache.count(tempItemID) != 0) + return true; + + return false; + } + + bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) { - const MWWorld::CellRef& cellref = item.getCellRef(); + if (target.isEmpty()) + return true; + + const MWWorld::CellRef& cellref = target.getCellRef(); // there is no harm to use unlocked doors - if (item.getClass().isDoor() && cellref.getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty()) + if (target.getClass().isDoor() && cellref.getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty()) return true; - // TODO: implement a better check to check if item is owned bed - if (item.getClass().isActivator() && item.getClass().getScript(item).compare(0, 3, "Bed") != 0) + // TODO: implement a better check to check if target is owned bed + if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0) return true; + if (target.getClass().isNpc()) + { + if (target.getClass().getCreatureStats(target).isDead()) + return true; + + if (target.getClass().getCreatureStats(target).getAiSequence().isInCombat()) + return true; + + // check if a player tries to pickpocket a target NPC + if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak) + || target.getClass().getCreatureStats(target).getKnockedDown()) + return false; + + return true; + } + const std::string& owner = cellref.getOwner(); bool isOwned = !owner.empty() && owner != "player"; @@ -1026,21 +1089,24 @@ namespace MWMechanics } void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, - int count) + int count, bool alarm) { if (ptr != getPlayer()) return; MWWorld::Ptr victim; + bool isAllowed = true; const MWWorld::CellRef* ownerCellRef = &item.getCellRef(); if (!container.isEmpty()) { // Inherit the owner of the container ownerCellRef = &container.getCellRef(); + isAllowed = isAllowedToUse(ptr, container, victim); } else { + isAllowed = isAllowedToUse(ptr, item, victim); if (!item.getCellRef().hasContentFile()) { // this is a manually placed item, which means it was already stolen @@ -1048,23 +1114,33 @@ namespace MWMechanics } } - if (isAllowedToUse(ptr, item, victim)) + if (isAllowed) return; Owner owner; - owner.first = ownerCellRef->getOwner(); owner.second = false; - if (owner.first.empty()) + if (!container.isEmpty() && container.getClass().isActor()) { - owner.first = ownerCellRef->getFaction(); - owner.second = true; + // "container" is an actor inventory, so just take actor's ID + owner.first = ownerCellRef->getRefId(); } + else + { + owner.first = ownerCellRef->getOwner(); + if (owner.first.empty()) + { + owner.first = ownerCellRef->getFaction(); + owner.second = true; + } + } + Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; - commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); + if (alarm) + commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } @@ -1102,6 +1178,10 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).isDead()) continue; + // Unconsious actor can not report about crime + if (it->getClass().getCreatureStats(*it).getKnockedDown()) + continue; + if ((*it == victim && victimAware) || (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) // Murder crime can be reported even if no one saw it (hearing is enough, I guess). @@ -1129,6 +1209,10 @@ namespace MWMechanics End of tes3mp addition */ + // NPC will complain about theft even if he will do nothing about it + if (type == OT_Theft || type == OT_Pickpocket) + MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + crimeSeen = true; } } @@ -1224,12 +1308,16 @@ namespace MWMechanics // Tell everyone (including the original reporter) in alarm range for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { - if ( *it == player + if (*it == player || !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue; if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; + // Unconsious actor can not report about crime and should not become hostile + if (it->getClass().getCreatureStats(*it).getKnockedDown()) + continue; + // Player's followers should not attack player, or try to arrest him if (it->getClass().getCreatureStats(*it).getAiSequence().hasPackage(AiPackage::TypeIdFollow)) { @@ -1245,9 +1333,7 @@ namespace MWMechanics { reported = true; - if (type == OT_Theft || type == OT_Pickpocket) - MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - else if (type == OT_Trespassing) + if (type == OT_Trespassing) MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); } @@ -1549,6 +1635,11 @@ namespace MWMechanics mActors.getObjectsInRange(position, radius, objects); } + bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius) + { + return mActors.isAnyObjectInRange(position, radius); + } + std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); @@ -1619,6 +1710,12 @@ namespace MWMechanics bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { + // Don't become aggressive if a calm effect is active, since it would cause combat to cycle on/off as + // combat is activated here and then canceled by the calm effect + if ((ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0) + || (!ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0)) + return false; + int disposition = 50; if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr, true); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 7660d651e..8908dd6ab 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -145,7 +145,7 @@ namespace MWMechanics /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, - int count); + int count, bool alarm = true); /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item); /// Attempt sleeping in a bed. If this is illegal, call commitCrime. @@ -168,6 +168,9 @@ 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); + /// Check if there are actors in selected range + virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius); + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); @@ -201,6 +204,10 @@ namespace MWMechanics /// Is \a ptr casting spell or using weapon now? virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const; + /// Check if the target actor was detected by an observer + /// If the observer is a non-NPC, check all actors in AI processing distance as observers + virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); + virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer); /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). @@ -210,8 +217,10 @@ namespace MWMechanics /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); - /// @return is \a ptr allowed to take/use \a cellref or is it a crime? - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim); + virtual bool isBoundItem(const MWWorld::Ptr& item); + + /// @return is \a ptr allowed to take/use \a target or is it a crime? + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim); virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf); virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index cc0fd1a7f..efe6d2491 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -422,7 +422,7 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int for (int i=0; i<7; ++i) { if (faction.mData.mSkills[i] != -1) - skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getModified())); + skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getBase())); } if (skills.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 5c0456096..1dccb6c9a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -5,16 +5,16 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "pathgrid.hpp" #include "coordinateconverter.hpp" namespace { // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, - const MWWorld::CellStore *cell, + const MWMechanics::PathgridGraph *graph, const osg::Vec3f& pos, int start) { assert(grid && !grid->mPoints.empty()); @@ -31,7 +31,7 @@ namespace if (potentialDistBetween < closestDistanceReachable) { // found a closer one - if (cell->isPointConnected(start, counter)) + if (graph->isPointConnected(start, counter)) { closestDistanceReachable = potentialDistBetween; closestReachableIndex = counter; @@ -45,7 +45,7 @@ namespace } // post-condition: start and endpoint must be connected - assert(cell->isPointConnected(start, closestReachableIndex)); + assert(graph->isPointConnected(start, closestReachableIndex)); // AiWander has logic that depends on whether a path was created, deleting // allowed nodes if not. Hence a path needs to be created even if the start @@ -120,8 +120,8 @@ namespace MWMechanics } PathFinder::PathFinder() - : mPathgrid(NULL), - mCell(NULL) + : mPathgrid(NULL) + , mCell(NULL) { } @@ -169,14 +169,15 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { mPath.clear(); + // TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph if(mCell != cell || !mPathgrid) { mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + mPathgrid = pathgridGraph.getPathgrid(); } // Refer to AiWander reseach topic on openmw forums for some background. @@ -200,7 +201,7 @@ namespace MWMechanics int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); - std::pair endNode = getClosestReachablePoint(mPathgrid, cell, + std::pair endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph, endPointInLocalCoords, startNode); @@ -228,7 +229,7 @@ namespace MWMechanics } else { - mPath = mCell->aStarSearch(startNode, endNode.first); + mPath = pathgridGraph.aStarSearch(startNode, endNode.first); // convert supplied path to world coordinates for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) @@ -301,18 +302,18 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, pathgridGraph); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, pathgridGraph); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 945a7f927..ebc22f10d 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -14,6 +14,8 @@ namespace MWWorld namespace MWMechanics { + class PathgridGraph; + float distance(const ESM::Pathgrid::Point& point, float x, float y, float); float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); float getZAngleToDir(const osg::Vec3f& dir); @@ -54,7 +56,7 @@ namespace MWMechanics void clearPath(); void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. @@ -89,7 +91,7 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); void addPointToPath(const ESM::Pathgrid::Point &point) { diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index c557beadd..c0122a861 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -49,7 +49,7 @@ namespace namespace MWMechanics { - PathgridGraph::PathgridGraph() + PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) : mCell(NULL) , mPathgrid(NULL) , mIsExterior(0) @@ -58,6 +58,7 @@ namespace MWMechanics , mSCCId(0) , mSCCIndex(0) { + load(cell); } /* @@ -130,6 +131,11 @@ namespace MWMechanics return true; } + const ESM::Pathgrid *PathgridGraph::getPathgrid() const + { + return mPathgrid; + } + // v is the pathgrid point index (some call them vertices) void PathgridGraph::recursiveStrongConnect(int v) { @@ -214,6 +220,16 @@ namespace MWMechanics return (mGraph[start].componentId == mGraph[end].componentId); } + void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const + { + for(int i = 0; i < static_cast (mGraph[index].edges.size()); i++) + { + int neighbourIndex = mGraph[index].edges[i].index; + if (neighbourIndex != index) + nodes.push_back(mPathgrid->mPoints[neighbourIndex]); + } + } + /* * NOTE: Based on buildPath2(), please check git history if interested * Should consider using a 3rd party library version (e.g. boost) diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index d90cb47cd..0c71c4561 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -20,14 +20,19 @@ namespace MWMechanics class PathgridGraph { public: - PathgridGraph(); + PathgridGraph(const MWWorld::CellStore* cell); bool load(const MWWorld::CellStore *cell); + const ESM::Pathgrid* getPathgrid() const; + // returns true if end point is strongly connected (i.e. reachable // from start point) both start and end are pathgrid point indexes bool isPointConnected(const int start, const int end) const; + // get neighbouring nodes for index node and put them to "nodes" vector + void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const; + // the input parameters are pathgrid point indexes // the output list is in local (internal cells) or world (external // cells) coordinates diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 91262753b..27c83249e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -131,7 +131,7 @@ namespace MWMechanics return castChance; } - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); @@ -151,6 +151,9 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; + if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode) + return 0; + if (spell->mData.mFlags & ESM::Spell::F_Always) return 100; @@ -165,11 +168,11 @@ namespace MWMechanics return std::max(0.f, std::min(100.f, castChance)); } - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor, effectiveSchool, cap); + return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); } @@ -345,7 +348,7 @@ namespace MWMechanics { } - void CastSpell::launchMagicBolt (const ESM::EffectList& effects) + void CastSpell::launchMagicBolt () { osg::Vec3f fallbackDirection (0,1,0); @@ -356,8 +359,7 @@ namespace MWMechanics osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, effects, - mCaster, mSourceName, fallbackDirection); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, @@ -553,6 +555,13 @@ namespace MWMechanics appliedLastingEffects.push_back(effect); + // Unequip all items, if a spell with the ExtraSpell effect was casted + if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target)) + { + MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target); + store.unequipAll(target); + } + // Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc()) || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) @@ -865,7 +874,7 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); if (launchProjectile) - launchMagicBolt(enchantment->mEffects); + launchMagicBolt(); else if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); @@ -973,7 +982,7 @@ namespace MWMechanics if (!mTarget.isEmpty()) inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); - launchMagicBolt(spell->mEffects); + launchMagicBolt(); return true; } @@ -1017,9 +1026,9 @@ namespace MWMechanics float y = roll / std::min(x, 100.f); y *= 0.25f * x; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - effect.mDuration = static_cast(y); - else effect.mDuration = 1; + else + effect.mDuration = static_cast(y); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 9991c583d..1f5ef45bd 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -31,11 +31,12 @@ namespace MWMechanics * @param actor calculate spell success chance for this actor (depends on actor's skills) * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here * @param cap cap the result to 100%? + * @param checkMagicka check magicka? * @note actor can be an NPC or a creature * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. */ - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); @@ -106,7 +107,7 @@ namespace MWMechanics void playSpellCastingEffects(const std::string &spellid); /// Launch a bolt with the given effects. - void launchMagicBolt (const ESM::EffectList& effects); + void launchMagicBolt (); /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. diff --git a/apps/openmw/mwmp/CellController.cpp b/apps/openmw/mwmp/CellController.cpp index aac250775..466a63c58 100644 --- a/apps/openmw/mwmp/CellController.cpp +++ b/apps/openmw/mwmp/CellController.cpp @@ -338,14 +338,14 @@ bool CellController::isSameCell(const ESM::Cell& cell, const ESM::Cell& otherCel return false; } -void CellController::openContainer(const MWWorld::Ptr &container, bool loot) +void CellController::openContainer(const MWWorld::Ptr &container) { // Record this as the player's current open container - Main::get().getLocalPlayer()->storeCurrentContainer(container, loot); + Main::get().getLocalPlayer()->storeCurrentContainer(container); const auto &cellRef = container.getCellRef(); - LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Container \"%s\" (%d) is opened. Loot: %s", - cellRef.getRefId().c_str(), cellRef.getRefNum().mIndex, loot ? "true" : "false"); + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Container \"%s\" (%d) is opened", + cellRef.getRefId().c_str(), cellRef.getRefNum().mIndex); for (const auto &ptr : container.getClass().getContainerStore(container)) { diff --git a/apps/openmw/mwmp/CellController.hpp b/apps/openmw/mwmp/CellController.hpp index ae2de1862..4b28043d3 100644 --- a/apps/openmw/mwmp/CellController.hpp +++ b/apps/openmw/mwmp/CellController.hpp @@ -59,7 +59,7 @@ namespace mwmp bool isSameCell(const ESM::Cell& cell, const ESM::Cell& otherCell); - void openContainer(const MWWorld::Ptr& container, bool loot); + void openContainer(const MWWorld::Ptr& container); void closeContainer(const MWWorld::Ptr& container); int getCellSize() const; diff --git a/apps/openmw/mwmp/GUI/GUIChat.cpp b/apps/openmw/mwmp/GUI/GUIChat.cpp index 7e464ce47..414f40269 100644 --- a/apps/openmw/mwmp/GUI/GUIChat.cpp +++ b/apps/openmw/mwmp/GUI/GUIChat.cpp @@ -46,7 +46,7 @@ namespace mwmp delay = 3; // 3 sec. } - void GUIChat::open() + void GUIChat::onOpen() { // Give keyboard focus to the combo box whenever the console is // turned on @@ -54,7 +54,7 @@ namespace mwmp windowState = CHAT_ENABLED; } - void GUIChat::close() + void GUIChat::onClose() { // Apparently, hidden widgets can retain key focus // Remove for MyGUI 3.2.2 @@ -62,9 +62,10 @@ namespace mwmp SetEditState(0); } - void GUIChat::exit() + bool GUIChat::exit() { //WindowBase::exit(); + return true; } void GUIChat::acceptCommand(MyGUI::EditBox *_sender) diff --git a/apps/openmw/mwmp/GUI/GUIChat.hpp b/apps/openmw/mwmp/GUI/GUIChat.hpp index ca75ec21d..059882561 100644 --- a/apps/openmw/mwmp/GUI/GUIChat.hpp +++ b/apps/openmw/mwmp/GUI/GUIChat.hpp @@ -43,11 +43,10 @@ namespace mwmp void Update(float dt); + virtual void onOpen(); + virtual void onClose(); - virtual void open(); - virtual void close(); - - virtual void exit(); + virtual bool exit(); void setFont(const std::string &fntName); diff --git a/apps/openmw/mwmp/GUI/TextInputDialog.cpp b/apps/openmw/mwmp/GUI/TextInputDialog.cpp index 484bb64d0..eaa687f09 100644 --- a/apps/openmw/mwmp/GUI/TextInputDialog.cpp +++ b/apps/openmw/mwmp/GUI/TextInputDialog.cpp @@ -55,13 +55,18 @@ namespace mwmp setText("TextNote", note); } - void TextInputDialog::open() + void TextInputDialog::onOpen() { - WindowModal::open(); + WindowModal::onOpen(); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } + bool TextInputDialog::exit() + { + return false; + } + // widget controls void TextInputDialog::onOkClicked(MyGUI::Widget *_sender) diff --git a/apps/openmw/mwmp/GUI/TextInputDialog.hpp b/apps/openmw/mwmp/GUI/TextInputDialog.hpp index 1c755e12d..a79759ffd 100644 --- a/apps/openmw/mwmp/GUI/TextInputDialog.hpp +++ b/apps/openmw/mwmp/GUI/TextInputDialog.hpp @@ -29,7 +29,8 @@ namespace mwmp void setEditPassword(bool value); - virtual void open(); + virtual void onOpen(); + virtual bool exit(); /** Event : Dialog finished, OK button clicked.\n signature : void method()\n diff --git a/apps/openmw/mwmp/GUIController.cpp b/apps/openmw/mwmp/GUIController.cpp index 9f4678819..cff86664a 100644 --- a/apps/openmw/mwmp/GUIController.cpp +++ b/apps/openmw/mwmp/GUIController.cpp @@ -43,7 +43,7 @@ mwmp::GUIController::GUIController(): mInputBox(0), mListBox(0) mChat = nullptr; keySay = SDL_SCANCODE_Y; keyChatMode = SDL_SCANCODE_F2; - calledMessageBox = false; + calledInteractiveMessage = false; } mwmp::GUIController::~GUIController() @@ -118,10 +118,7 @@ void mwmp::GUIController::showDialogList(const mwmp::BasePlayer::GUIMessageBox & void mwmp::GUIController::showMessageBox(const BasePlayer::GUIMessageBox &guiMessageBox) { MWBase::WindowManager *windowManager = MWBase::Environment::get().getWindowManager(); - std::vector buttons; - buttons.push_back("Ok"); - windowManager->interactiveMessageBox(guiMessageBox.label, buttons); - calledMessageBox = true; + windowManager->messageBox(guiMessageBox.label); } std::vector splitString(const std::string &str, char delim = ';') @@ -139,7 +136,7 @@ void mwmp::GUIController::showCustomMessageBox(const BasePlayer::GUIMessageBox & MWBase::WindowManager *windowManager = MWBase::Environment::get().getWindowManager(); std::vector buttons = splitString(guiMessageBox.buttons); windowManager->interactiveMessageBox(guiMessageBox.label, buttons); - calledMessageBox = true; + calledInteractiveMessage = true; } void mwmp::GUIController::showInputBox(const BasePlayer::GUIMessageBox &guiMessageBox) @@ -158,12 +155,13 @@ void mwmp::GUIController::showInputBox(const BasePlayer::GUIMessageBox &guiMessa mInputBox->eventDone += MyGUI::newDelegate(this, &GUIController::onInputBoxDone); + mInputBox->setVisible(true); } void mwmp::GUIController::onInputBoxDone(MWGui::WindowBase *parWindow) { //MWBase::WindowManager *windowManager = MWBase::Environment::get().getWindowManager(); - LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "GUIController::OnInputBoxDone: %s.",mInputBox->getTextInput().c_str()); + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "GUIController::onInputBoxDone: %s.",mInputBox->getTextInput().c_str()); Main::get().getLocalPlayer()->guiMessageBox.data = mInputBox->getTextInput(); Main::get().getNetworking()->getPlayerPacket(ID_GUI_MESSAGEBOX)->setPlayer(Main::get().getLocalPlayer()); @@ -206,10 +204,10 @@ void mwmp::GUIController::update(float dt) // checked somewhere else int pressedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(false); - if (pressedButton != -1 && calledMessageBox) + if (pressedButton != -1 && calledInteractiveMessage) { LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Pressed: %d", pressedButton); - calledMessageBox = false; + calledInteractiveMessage = false; Main::get().getLocalPlayer()->guiMessageBox.data = MyGUI::utility::toString(pressedButton); Main::get().getNetworking()->getPlayerPacket(ID_GUI_MESSAGEBOX)->setPlayer(Main::get().getLocalPlayer()); Main::get().getNetworking()->getPlayerPacket(ID_GUI_MESSAGEBOX)->Send(); diff --git a/apps/openmw/mwmp/GUIController.hpp b/apps/openmw/mwmp/GUIController.hpp index 8e6efba15..db42d692f 100644 --- a/apps/openmw/mwmp/GUIController.hpp +++ b/apps/openmw/mwmp/GUIController.hpp @@ -69,7 +69,7 @@ namespace mwmp int keyChatMode; long id; - bool calledMessageBox; + bool calledInteractiveMessage; TextInputDialog *mInputBox; GUIDialogList *mListBox; void onInputBoxDone(MWGui::WindowBase* parWindow); diff --git a/apps/openmw/mwmp/LocalPlayer.cpp b/apps/openmw/mwmp/LocalPlayer.cpp index 62e8c7727..674726ef8 100644 --- a/apps/openmw/mwmp/LocalPlayer.cpp +++ b/apps/openmw/mwmp/LocalPlayer.cpp @@ -741,7 +741,7 @@ void LocalPlayer::addTopics() env.getDialogueManager()->addTopic(topicId); if (env.getWindowManager()->containsMode(MWGui::GM_Dialogue)) - env.getDialogueManager()->updateTopics(); + env.getDialogueManager()->updateActorKnownTopics(); } } @@ -1478,12 +1478,11 @@ void LocalPlayer::storeCellState(ESM::Cell cell, int stateType) cellStateChanges.cellStates.push_back(cellState); } -void LocalPlayer::storeCurrentContainer(const MWWorld::Ptr &container, bool loot) +void LocalPlayer::storeCurrentContainer(const MWWorld::Ptr &container) { currentContainer.refId = container.getCellRef().getRefId(); currentContainer.refNumIndex = container.getCellRef().getRefNum().mIndex; currentContainer.mpNum = container.getCellRef().getMpNum(); - currentContainer.loot = loot; } void LocalPlayer::playAnimation() diff --git a/apps/openmw/mwmp/LocalPlayer.hpp b/apps/openmw/mwmp/LocalPlayer.hpp index b39ffab01..41c170c8d 100644 --- a/apps/openmw/mwmp/LocalPlayer.hpp +++ b/apps/openmw/mwmp/LocalPlayer.hpp @@ -90,7 +90,7 @@ namespace mwmp void clearCurrentContainer(); void storeCellState(ESM::Cell cell, int stateType); - void storeCurrentContainer(const MWWorld::Ptr& container, bool loot); + void storeCurrentContainer(const MWWorld::Ptr& container); void playAnimation(); void playSpeech(); diff --git a/apps/openmw/mwmp/WorldEvent.cpp b/apps/openmw/mwmp/WorldEvent.cpp index 7e87d4821..be39c458a 100644 --- a/apps/openmw/mwmp/WorldEvent.cpp +++ b/apps/openmw/mwmp/WorldEvent.cpp @@ -146,7 +146,7 @@ void WorldEvent::editContainers(MWWorld::CellStore* cellStore) currentContainer->mpNum == ptrFound.getCellRef().getMpNum()) { MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - MWBase::Environment::get().getWindowManager()->openContainer(ptrFound, currentContainer->loot); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, ptrFound); } } } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 790ae2099..36041a85e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,5 +1,6 @@ #include "physicssystem.hpp" +#include #include #include @@ -356,10 +357,9 @@ namespace MWPhysics osg::Vec3f nextpos = newPosition + velocity * remainingTime; // If not able to fly, don't allow to swim up into the air - if(newPosition.z() < swimlevel && - !isFlying && // can't fly + if(!isFlying && // can't fly nextpos.z() > swimlevel && // but about to go above water - newPosition.z() <= swimlevel) + newPosition.z() < swimlevel) { const osg::Vec3f down(0,0,-1); velocity = slide(velocity, down); @@ -683,6 +683,7 @@ namespace MWPhysics , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(parentNode) + , mPhysicsDt(1.f / 60.f) { mResourceSystem->addResourceManager(mShapeManager.get()); @@ -695,6 +696,18 @@ namespace MWPhysics // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. mCollisionWorld->setForceUpdateAllAabbs(false); + + // Check if a user decided to override a physics system FPS + const char* env = getenv("OPENMW_PHYSICS_FPS"); + if (env) + { + float physFramerate = std::atof(env); + if (physFramerate > 0) + { + mPhysicsDt = 1.f / physFramerate; + std::cerr << "Warning: physics framerate was overriden (a new value is " << physFramerate << ")." << std::endl; + } + } } PhysicsSystem::~PhysicsSystem() @@ -1030,7 +1043,7 @@ namespace MWPhysics bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) { Actor* physactor = getActor(actor); - return physactor->getOnGround(); + return physactor && physactor->getOnGround(); } bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) @@ -1357,13 +1370,12 @@ namespace MWPhysics mMovementResults.clear(); mTimeAccum += dt; - const float physicsDt = 1.f/60.0f; const int maxAllowedSteps = 20; - int numSteps = mTimeAccum / (physicsDt); + int numSteps = mTimeAccum / (mPhysicsDt); numSteps = std::min(numSteps, maxAllowedSteps); - mTimeAccum -= numSteps * physicsDt; + mTimeAccum -= numSteps * mPhysicsDt; if (numSteps) { @@ -1404,14 +1416,16 @@ 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)); + bool flying = world->isFlying(iter->first); + + bool wasOnGround = physicActor->getOnGround(); osg::Vec3f position = physicActor->getPosition(); float oldHeight = position.z(); bool positionChanged = false; for (int i=0; igetPtr(), physicActor, iter->second, physicsDt, - world->isFlying(iter->first), - waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, mPhysicsDt, + flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions); if (position != physicActor->getPosition()) positionChanged = true; physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct @@ -1419,13 +1433,16 @@ namespace MWPhysics if (positionChanged) mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject()); - float interpolationFactor = mTimeAccum / physicsDt; + float interpolationFactor = mTimeAccum / mPhysicsDt; osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); float heightDiff = position.z() - oldHeight; - if (heightDiff < 0) - iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + if ((wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1) + stats.land(); + else if (heightDiff < 0) + stats.addToFallHeight(-heightDiff); mMovementResults.push_back(std::make_pair(iter->first, interpolated)); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index aaf55e2b6..3ef9990f5 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -220,6 +220,8 @@ namespace MWPhysics osg::ref_ptr mParentNode; + float mPhysicsDt; + PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); }; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index df9b8545a..16ce9b436 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -536,7 +536,7 @@ namespace MWRender return mKeyframes->mTextKeys; } - void Animation::addAnimSource(const std::string &model) + void Animation::addAnimSource(const std::string &model, const std::string& baseModel) { std::string kfname = model; Misc::StringUtils::lowerCaseInPlace(kfname); @@ -565,7 +565,7 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) { - std::cerr << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << model << " (referenced by " << kfname << ")" << std::endl; + std::cerr << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")" << std::endl; continue; } @@ -625,7 +625,7 @@ namespace MWRender float Animation::getStartTime(const std::string &groupname) const { - for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); @@ -638,7 +638,7 @@ namespace MWRender float Animation::getTextKeyTime(const std::string &textKey) const { - for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); @@ -757,8 +757,6 @@ namespace MWRender break; } } - if(iter == mAnimSources.rend()) - std::cerr<< "Failed to find animation "<second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) + && (stopkey->second.size() < stoptag.size() || stopkey->second.compare(0,stoptag.size(), stoptag) != 0)) ++stopkey; if(stopkey == keys.rend()) return false; @@ -1670,7 +1668,7 @@ namespace MWRender { setObjectRoot(model, false, false, false); if (animated) - addAnimSource(model); + addAnimSource(model, model); if (!ptr.getClass().getEnchantment(ptr).empty()) addGlow(mObjectRoot, getEnchantmentColor(ptr)); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 5aec80f5d..8ac78babc 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -309,11 +309,12 @@ protected: */ void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); - /** Adds the keyframe controllers in the specified model as a new animation source. Note that the .nif - * file extension will be replaced with .kf. + /** Adds the keyframe controllers in the specified model as a new animation source. * @note Later added animation sources have the highest priority when it comes to finding a particular animation. + * @param model The file to add the keyframes for. Note that the .nif file extension will be replaced with .kf. + * @param baseModel The filename of the mObjectRoot, only used for error messages. */ - void addAnimSource(const std::string &model); + void addAnimSource(const std::string &model, const std::string& baseModel); /** Adds an additional light to the given node using the specified ESM record. */ void addExtraLight(osg::ref_ptr parent, const ESM::Light *light); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 2ad362b33..827b576c3 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -33,8 +33,8 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif"); - addAnimSource(model); + addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(model, model); } } @@ -51,8 +51,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const setObjectRoot(model, true, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif"); - addAnimSource(model); + addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(model, model); mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); @@ -118,7 +118,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item)); const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e4d0abf7b..08e376f08 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -388,9 +388,6 @@ void NpcAnimation::rebuild() { updateNpcBase(); - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); - MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); } @@ -485,25 +482,25 @@ void NpcAnimation::updateNpcBase() { const std::string base = "meshes\\xbase_anim.nif"; if (smodel != base) - addAnimSource(base); + addAnimSource(base, smodel); - addAnimSource(smodel); + addAnimSource(smodel, smodel); if(!isWerewolf) { if(mNpc->mModel.length() > 0) - addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS())); + addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()), smodel); if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) - addAnimSource("meshes\\xargonian_swimkna.nif"); + addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } } else { const std::string base = "meshes\\xbase_anim.1st.nif"; if (smodel != base) - addAnimSource(base); + addAnimSource(base, smodel); - addAnimSource(smodel); + addAnimSource(smodel, smodel); mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); @@ -651,6 +648,9 @@ void NpcAnimation::updateParts() if (wasArrowAttached) attachArrow(); + + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } @@ -770,8 +770,9 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mSoundIds[type] = csi->getClass().getSound(*csi); if (!mSoundIds[type].empty()) { - MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, mSoundIds[type], 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, - MWBase::SoundManager::Play_Loop); + MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, mSoundIds[type], + 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop + ); } } } @@ -916,6 +917,8 @@ void NpcAnimation::showWeapons(bool showWeapon) attachArrow(); } } + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else { @@ -941,6 +944,8 @@ void NpcAnimation::showCarriedLeft(bool show) if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else removeIndividualPart(ESM::PRT_Shield); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4d4a36f6c..4fbcdc4d5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -253,6 +253,9 @@ namespace MWRender mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); + mSky->setCamera(mViewer->getCamera()); + mSky->setRainIntensityUniform(mWater->getRainIntensityUniform()); + source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); mStateUpdater = new StateUpdater; @@ -279,11 +282,14 @@ namespace MWRender mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); 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)); + + mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); + mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); + updateProjectionMatrix(); } RenderingManager::~RenderingManager() @@ -530,8 +536,10 @@ namespace MWRender void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) { if(mPlayerAnimation.get()) + { + setupPlayer(ptr); mPlayerAnimation->updatePtr(ptr); - + } mCamera->attachTo(ptr); } @@ -793,7 +801,6 @@ namespace MWRender void RenderingManager::notifyWorldSpaceChanged() { mEffectManager->clear(); - mWater->clearRipples(); } void RenderingManager::clear() @@ -834,6 +841,7 @@ namespace MWRender player.getRefData().setBaseNode(mPlayerNode); + mWater->removeEmitter(player); mWater->addEmitter(player); } @@ -886,6 +894,9 @@ namespace MWRender if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); + + mUniformNear->set(mNearClip); + mUniformFar->set(mViewDistance); } void RenderingManager::updateTextureFiltering() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e575456d9..f0087e43d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -83,6 +83,9 @@ namespace MWRender SceneUtil::UnrefQueue* getUnrefQueue(); Terrain::World* getTerrain(); + osg::Uniform* mUniformNear; + osg::Uniform* mUniformFar; + void preloadCommonAssets(); double getReferenceTime() const; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index e6aa013ec..6c599fc3f 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -25,6 +25,9 @@ #include #include +#include +#include + #include #include @@ -48,7 +51,6 @@ namespace { - osg::ref_ptr createAlphaTrackingUnlitMaterial() { osg::ref_ptr mat = new osg::Material; @@ -1093,6 +1095,8 @@ private: SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) : mSceneManager(sceneManager) + , mCamera(NULL) + , mRainIntensityUniform(NULL) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) @@ -1135,6 +1139,11 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } +void SkyManager::setRainIntensityUniform(osg::Uniform *uniform) +{ + mRainIntensityUniform = uniform; +} + void SkyManager::create() { assert(!mCreated); @@ -1236,9 +1245,11 @@ private: class AlphaFader : public SceneUtil::StateSetUpdater { public: - AlphaFader() + /// @param alphaUpdate variable which to update with alpha value + AlphaFader(float *alphaUpdate) : mAlpha(1.f) { + mAlphaUpdate = alphaUpdate; } void setAlpha(float alpha) @@ -1257,15 +1268,19 @@ public: { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); + + if (mAlphaUpdate) + *mAlphaUpdate = mAlpha; } // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: - SetupVisitor() + SetupVisitor(float *alphaUpdate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { + mAlphaUpdate = alphaUpdate; } virtual void apply(osg::Node &node) @@ -1276,14 +1291,16 @@ public: { SceneUtil::CompositeStateSetUpdater* composite = NULL; osg::Callback* callback = node.getUpdateCallback(); + while (callback) { if ((composite = dynamic_cast(callback))) break; + callback = callback->getNestedCallback(); } - osg::ref_ptr alphaFader (new AlphaFader); + osg::ref_ptr alphaFader (new AlphaFader(mAlphaUpdate)); if (composite) composite->addController(alphaFader); @@ -1293,6 +1310,7 @@ public: mAlphaFaders.push_back(alphaFader); } } + traverse(node); } @@ -1303,15 +1321,21 @@ public: private: std::vector > mAlphaFaders; + float *mAlphaUpdate; }; -private: +protected: float mAlpha; + float *mAlphaUpdate; }; class RainFader : public AlphaFader { public: + RainFader(float *alphaUpdate): AlphaFader(alphaUpdate) + { + } + virtual void setDefaults(osg::StateSet* stateset) { osg::ref_ptr mat (new osg::Material); @@ -1320,6 +1344,93 @@ public: mat->setColorMode(osg::Material::OFF); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); } + + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + AlphaFader::apply(stateset,nv); + *mAlphaUpdate = mAlpha * 2.0; // mAlpha is limited to 0.6 so multiply by 2 to reach full intensity + } +}; + +void SkyManager::setCamera(osg::Camera *camera) +{ + mCamera = camera; +} + +class WrapAroundOperator : public osgParticle::Operator +{ +public: + WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator() + { + mCamera = camera; + mWrapRange = wrapRange; + mHalfWrapRange = mWrapRange / 2.0; + mPreviousCameraPosition = getCameraPosition(); + } + + virtual osg::Object *cloneType() const override + { + return NULL; + } + + virtual osg::Object *clone(const osg::CopyOp &op) const override + { + return NULL; + } + + virtual void operate(osgParticle::Particle *P, double dt) override + { + } + + virtual void operateParticles(osgParticle::ParticleSystem *ps, double dt) override + { + osg::Vec3 position = getCameraPosition(); + osg::Vec3 positionDifference = position - mPreviousCameraPosition; + + osg::Matrix toWorld, toLocal; + + std::vector worldMatrices = ps->getWorldMatrices(); + + if (!worldMatrices.empty()) + { + toWorld = worldMatrices[0]; + toLocal.invert(toWorld); + } + + for (int i = 0; i < ps->numParticles(); ++i) + { + osgParticle::Particle *p = ps->getParticle(i); + p->setPosition(toWorld.preMult(p->getPosition())); + p->setPosition(p->getPosition() - positionDifference); + + for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions + { + osg::Vec3 pos = p->getPosition(); + + if (pos[j] < -mHalfWrapRange[j]) + pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); + else if (pos[j] > mHalfWrapRange[j]) + pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; + + p->setPosition(pos); + } + + p->setPosition(toLocal.preMult(p->getPosition())); + } + + mPreviousCameraPosition = position; + } + +protected: + osg::Camera *mCamera; + osg::Vec3 mPreviousCameraPosition; + osg::Vec3 mWrapRange; + osg::Vec3 mHalfWrapRange; + + osg::Vec3 getCameraPosition() + { + return mCamera->getInverseViewMatrix().getTrans(); + } }; void SkyManager::createRain() @@ -1330,6 +1441,8 @@ void SkyManager::createRain() mRainNode = new osg::Group; mRainParticleSystem = new osgParticle::ParticleSystem; + osg::Vec3 rainRange = osg::Vec3(600,600,600); + mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); @@ -1355,8 +1468,8 @@ void SkyManager::createRain() emitter->setParticleSystem(mRainParticleSystem); osg::ref_ptr placer (new osgParticle::BoxPlacer); - placer->setXRange(-300, 300); // Rain_Diameter - placer->setYRange(-300, 300); + placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); // Rain_Diameter + placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); placer->setZRange(300, 300); emitter->setPlacer(placer); @@ -1371,11 +1484,16 @@ void SkyManager::createRain() osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); updater->addParticleSystem(mRainParticleSystem); + osg::ref_ptr program (new osgParticle::ModularProgram); + program->addOperator(new WrapAroundOperator(mCamera,rainRange)); + program->setParticleSystem(mRainParticleSystem); + mRainNode->addChild(program); + mRainNode->addChild(emitter); mRainNode->addChild(mRainParticleSystem); mRainNode->addChild(updater); - mRainFader = new RainFader; + mRainFader = new RainFader(&mWeatherAlpha); mRainNode->addUpdateCallback(mRainFader); mRainNode->addCullCallback(mUnderwaterSwitch); mRainNode->setNodeMask(Mask_WeatherParticles); @@ -1416,9 +1534,33 @@ int SkyManager::getSecundaPhase() const return mSecunda->getPhaseInt(); } +bool SkyManager::isEnabled() +{ + return mEnabled; +} + +bool SkyManager::hasRain() +{ + return mRainNode != NULL; +} + void SkyManager::update(float duration) { - if (!mEnabled) return; + if (!mEnabled) + { + if (mRainIntensityUniform) + mRainIntensityUniform->set((float) 0.0); + + return; + } + + if (mRainIntensityUniform) + { + if (mIsStorm || (!hasRain() && !mParticleNode)) + mRainIntensityUniform->set((float) 0.0); + else + mRainIntensityUniform->set((float) mWeatherAlpha); + } if (mIsStorm) { @@ -1427,6 +1569,7 @@ void SkyManager::update(float duration) if (mParticleNode) mParticleNode->setAttitude(quat); + mCloudNode->setAttitude(quat); } else @@ -1523,17 +1666,35 @@ void SkyManager::setWeather(const WeatherResult& weather) mParticleNode->setNodeMask(Mask_WeatherParticles); mRootNode->addChild(mParticleNode); } + mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(new SceneUtil::FrameTimeSource)); mParticleEffect->accept(assignVisitor); - AlphaFader::SetupVisitor alphaFaderSetupVisitor; + AlphaFader::SetupVisitor alphaFaderSetupVisitor(&mWeatherAlpha); + mParticleEffect->accept(alphaFaderSetupVisitor); mParticleFaders = alphaFaderSetupVisitor.getAlphaFaders(); SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; mParticleEffect->accept(disableFreezeOnCullVisitor); + + if (!weather.mIsStorm) + { + SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); + mParticleEffect->accept(findPSVisitor); + + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + { + osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); + + osg::ref_ptr program (new osgParticle::ModularProgram); + program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); + program->setParticleSystem(ps); + mParticleNode->addChild(program); + } + } } } @@ -1609,7 +1770,8 @@ void SkyManager::setWeather(const WeatherResult& weather) mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); float nextStarsOpacity = weather.mNightFade * weather.mGlareView; - if(weather.mNight && mStarsOpacity != nextStarsOpacity) + + if (weather.mNight && mStarsOpacity != nextStarsOpacity) { mStarsOpacity = nextStarsOpacity; @@ -1620,6 +1782,7 @@ void SkyManager::setWeather(const WeatherResult& weather) if (mRainFader) mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? + for (std::vector >::const_iterator it = mParticleFaders.begin(); it != mParticleFaders.end(); ++it) (*it)->setAlpha(weather.mEffectFade); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 863ecc77d..4357d468c 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -4,9 +4,16 @@ #include #include #include +#include #include #include +#include + +namespace osg +{ + class Camera; +} namespace osg { @@ -137,6 +144,10 @@ namespace MWRender void sunDisable(); + bool isEnabled(); + + bool hasRain(); + void setRainSpeed(float speed); void setStormDirection(const osg::Vec3f& direction); @@ -156,6 +167,10 @@ namespace MWRender void listAssetsToPreload(std::vector& models, std::vector& textures); + void setCamera(osg::Camera *camera); + + void setRainIntensityUniform(osg::Uniform *uniform); + private: void create(); ///< no need to call this, automatically done on first enable() @@ -166,6 +181,9 @@ namespace MWRender Resource::SceneManager* mSceneManager; + osg::Camera *mCamera; + osg::Uniform *mRainIntensityUniform; + osg::ref_ptr mRootNode; osg::ref_ptr mEarlyRenderBinRoot; @@ -234,6 +252,8 @@ namespace MWRender bool mEnabled; bool mSunEnabled; + float mWeatherAlpha; + osg::Vec4f mMoonScriptColor; }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 0ed389bdf..c4dffb7a4 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -336,8 +336,7 @@ public: void setWaterLevel(float waterLevel) { - setViewMatrix(osg::Matrix::translate(0,0,-waterLevel) * osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,waterLevel)); - + setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,1), osg::Vec3d(0,0,waterLevel))); } @@ -412,14 +411,21 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem createSimpleWaterStateSet(geom2, mFallback->getFallbackFloat("Water_Map_Alpha")); geom2->setNodeMask(Mask_SimpleWater); mWaterNode->addChild(geom2); - + mSceneRoot->addChild(mWaterNode); setHeight(mTop); + mRainIntensityUniform = new osg::Uniform("rainIntensity",(float) 0.0); + updateWaterMaterial(); } +osg::Uniform *Water::getRainIntensityUniform() +{ + return mRainIntensityUniform.get(); +} + void Water::updateWaterMaterial() { if (mReflection) @@ -511,6 +517,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R osg::ref_ptr fragmentShader (shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); + if (normalMap->getImage()) normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); @@ -525,6 +532,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R 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); @@ -546,6 +554,8 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + shaderStateset->addUniform(mRainIntensityUniform.get()); + osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 33b314c51..a4fd1ed36 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -50,6 +51,8 @@ namespace MWRender { static const int CELL_SIZE = 8192; + osg::ref_ptr mRainIntensityUniform; + osg::ref_ptr mParent; osg::ref_ptr mSceneRoot; osg::ref_ptr mWaterNode; @@ -110,6 +113,8 @@ namespace MWRender void update(float dt); void processChangedSettings(const Settings::CategorySettingVector& settings); + + osg::Uniform *getRainIntensityUniform(); }; } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 76130be24..c98e0fc5a 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -372,21 +372,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr observer = R()(runtime); + MWWorld::Ptr observer = R()(runtime, false); // required=false + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true); - if(!actor.getClass().isActor() || !observer.getClass().isActor()) - { - runtime.push(0); - return; - } - - Interpreter::Type_Integer value = - MWBase::Environment::get().getWorld()->getLOS(observer, actor) && - MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); + Interpreter::Type_Integer value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer); runtime.push (value); } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 0c2e38db6..88372eb76 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -165,8 +165,7 @@ namespace MWScript if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) itemName = iter->getClass().getName(*iter); - // Actors should not equip a replacement when items are removed with RemoveItem - int numRemoved = store.remove(item, count, ptr, false); + int numRemoved = store.remove(item, count, ptr); // Spawn a messagebox (only for items removed from player's inventory) if ((numRemoved > 0) diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 3295134e6..1e488467b 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -23,6 +23,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" @@ -163,7 +164,7 @@ namespace MWScript runtime.pop(); arg0 = arg0 -1; } - dialogue->askQuestion(question,choice); + dialogue->addChoice(question,choice); } } }; @@ -187,7 +188,7 @@ namespace MWScript preventing infinite greeting loops */ if (!MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Dialogue)) - MWBase::Environment::get().getDialogueManager()->startDialogue(ptr); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, ptr); /* End of tes3mp change (major) */ diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 851a6a29c..e999097db 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -453,5 +453,6 @@ op 0x2000302: Fixme op 0x2000303: Fixme, explicit op 0x2000304: Show op 0x2000305: Show, explicit +op 0x2000306: OnActivate, explicit -opcodes 0x2000304-0x3ffffff unused +opcodes 0x2000307-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index cea502d2e..e96322427 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -67,7 +67,7 @@ namespace MWScript MWWorld::Ptr bed = R()(runtime, false); if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWMechanics::getPlayer(), - bed)) + bed)) /* Start of tes3mp change (minor) @@ -77,7 +77,7 @@ namespace MWScript if (!mwmp::Main::get().getLocalPlayer()->bedRestAllowed) MWBase::Environment::get().getWindowManager()->messageBox("You are not allowed to rest in beds."); else - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_RestBed); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest, bed); } /* End of tes3mp change (minor) @@ -233,7 +233,7 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - bool state = MWBase::Environment::get().getWindowManager()->toggleGui(); + bool state = MWBase::Environment::get().getWindowManager()->toggleHud(); runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); if (!state) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 6622fb4cf..148c39b19 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -157,16 +157,14 @@ namespace MWScript } }; + template class OpOnActivate : public Interpreter::Opcode0 { public: virtual void execute (Interpreter::Runtime& runtime) { - InterpreterContext& context = - static_cast (runtime.getContext()); - - MWWorld::Ptr ptr = context.getReference(); + MWWorld::Ptr ptr = R()(runtime); runtime.push (ptr.getRefData().onActivate()); } @@ -1350,7 +1348,8 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); - interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); + interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); + interpreter.installSegment5 (Compiler::Misc::opcodeOnActivateExplicit, new OpOnActivate); interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate); interpreter.installSegment5 (Compiler::Misc::opcodeActivateExplicit, new OpActivate); interpreter.installSegment3 (Compiler::Misc::opcodeLock, new OpLock); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 5645fc296..c66944837 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -108,7 +108,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound(sound, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoEnv); + MWBase::Environment::get().getSoundManager()->playSound(sound, 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } }; @@ -127,7 +127,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound(sound, volume, pitch, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoEnv); + MWBase::Environment::get().getSoundManager()->playSound(sound, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } }; @@ -148,9 +148,9 @@ namespace MWScript runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, - MWBase::SoundManager::Play_TypeSfx, - mLoop ? MWBase::SoundManager::Play_LoopRemoveAtDistance - : MWBase::SoundManager::Play_Normal); + MWSound::Type::Sfx, + mLoop ? MWSound::PlayMode::LoopRemoveAtDistance + : MWSound::PlayMode::Normal); } }; @@ -177,9 +177,9 @@ namespace MWScript runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, - MWBase::SoundManager::Play_TypeSfx, - mLoop ? MWBase::SoundManager::Play_LoopRemoveAtDistance - : MWBase::SoundManager::Play_Normal); + MWSound::Type::Sfx, + mLoop ? MWSound::PlayMode::LoopRemoveAtDistance + : MWSound::PlayMode::Normal); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 90b53170b..17ae6b304 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1310,6 +1310,14 @@ namespace MWScript if (mNegativeEffect != -1) currentValue -= effects.get(mNegativeEffect).getMagnitude(); + // GetResist* should take in account elemental shields + if (mPositiveEffect == ESM::MagicEffect::ResistFire) + currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistShock) + currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistFrost) + currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); + int ret = static_cast(currentValue); runtime.push(ret); } @@ -1336,6 +1344,14 @@ namespace MWScript if (mNegativeEffect != -1) currentValue -= effects.get(mNegativeEffect).getMagnitude(); + // SetResist* should take in account elemental shields + if (mPositiveEffect == ESM::MagicEffect::ResistFire) + currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistShock) + currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistFrost) + currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); + int arg = runtime[0].mInteger; runtime.pop(); effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index ca1ceb58b..d976d5773 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -232,11 +232,6 @@ namespace MWScript if (!ptr.isInCell()) return; - if (ptr == MWMechanics::getPlayer()) - { - MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); - } - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; @@ -246,6 +241,8 @@ namespace MWScript float ay = ptr.getRefData().getPosition().pos[1]; float az = ptr.getRefData().getPosition().pos[2]; + // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. + MWWorld::Ptr updated = ptr; if(axis == "x") { @@ -257,6 +254,17 @@ namespace MWScript } else if(axis == "z") { + // We should not place actors under ground + if (ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(osg::Vec3f(ax, ay, az)); + + if (pos < terrainHeight) + pos = terrainHeight; + } + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); } else diff --git a/apps/openmw/mwsound/alext.h b/apps/openmw/mwsound/alext.h new file mode 100644 index 000000000..4b9a15537 --- /dev/null +++ b/apps/openmw/mwsound/alext.h @@ -0,0 +1,466 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2008 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef AL_ALEXT_H +#define AL_ALEXT_H + +#include +/* Define int64_t and uint64_t types */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#elif defined(_WIN32) && defined(__GNUC__) +#include +#elif defined(_WIN32) +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include +#endif + +#include "alc.h" +#include "al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef AL_LOKI_IMA_ADPCM_format +#define AL_LOKI_IMA_ADPCM_format 1 +#define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 +#define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 +#endif + +#ifndef AL_LOKI_WAVE_format +#define AL_LOKI_WAVE_format 1 +#define AL_FORMAT_WAVE_EXT 0x10002 +#endif + +#ifndef AL_EXT_vorbis +#define AL_EXT_vorbis 1 +#define AL_FORMAT_VORBIS_EXT 0x10003 +#endif + +#ifndef AL_LOKI_quadriphonic +#define AL_LOKI_quadriphonic 1 +#define AL_FORMAT_QUAD8_LOKI 0x10004 +#define AL_FORMAT_QUAD16_LOKI 0x10005 +#endif + +#ifndef AL_EXT_float32 +#define AL_EXT_float32 1 +#define AL_FORMAT_MONO_FLOAT32 0x10010 +#define AL_FORMAT_STEREO_FLOAT32 0x10011 +#endif + +#ifndef AL_EXT_double +#define AL_EXT_double 1 +#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 +#define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 +#endif + +#ifndef AL_EXT_MULAW +#define AL_EXT_MULAW 1 +#define AL_FORMAT_MONO_MULAW_EXT 0x10014 +#define AL_FORMAT_STEREO_MULAW_EXT 0x10015 +#endif + +#ifndef AL_EXT_ALAW +#define AL_EXT_ALAW 1 +#define AL_FORMAT_MONO_ALAW_EXT 0x10016 +#define AL_FORMAT_STEREO_ALAW_EXT 0x10017 +#endif + +#ifndef ALC_LOKI_audio_channel +#define ALC_LOKI_audio_channel 1 +#define ALC_CHAN_MAIN_LOKI 0x500001 +#define ALC_CHAN_PCM_LOKI 0x500002 +#define ALC_CHAN_CD_LOKI 0x500003 +#endif + +#ifndef AL_EXT_MCFORMATS +#define AL_EXT_MCFORMATS 1 +#define AL_FORMAT_QUAD8 0x1204 +#define AL_FORMAT_QUAD16 0x1205 +#define AL_FORMAT_QUAD32 0x1206 +#define AL_FORMAT_REAR8 0x1207 +#define AL_FORMAT_REAR16 0x1208 +#define AL_FORMAT_REAR32 0x1209 +#define AL_FORMAT_51CHN8 0x120A +#define AL_FORMAT_51CHN16 0x120B +#define AL_FORMAT_51CHN32 0x120C +#define AL_FORMAT_61CHN8 0x120D +#define AL_FORMAT_61CHN16 0x120E +#define AL_FORMAT_61CHN32 0x120F +#define AL_FORMAT_71CHN8 0x1210 +#define AL_FORMAT_71CHN16 0x1211 +#define AL_FORMAT_71CHN32 0x1212 +#endif + +#ifndef AL_EXT_MULAW_MCFORMATS +#define AL_EXT_MULAW_MCFORMATS 1 +#define AL_FORMAT_MONO_MULAW 0x10014 +#define AL_FORMAT_STEREO_MULAW 0x10015 +#define AL_FORMAT_QUAD_MULAW 0x10021 +#define AL_FORMAT_REAR_MULAW 0x10022 +#define AL_FORMAT_51CHN_MULAW 0x10023 +#define AL_FORMAT_61CHN_MULAW 0x10024 +#define AL_FORMAT_71CHN_MULAW 0x10025 +#endif + +#ifndef AL_EXT_IMA4 +#define AL_EXT_IMA4 1 +#define AL_FORMAT_MONO_IMA4 0x1300 +#define AL_FORMAT_STEREO_IMA4 0x1301 +#endif + +#ifndef AL_EXT_STATIC_BUFFER +#define AL_EXT_STATIC_BUFFER 1 +typedef ALvoid (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); +#endif +#endif + +#ifndef ALC_EXT_EFX +#define ALC_EXT_EFX 1 +#include "efx.h" +#endif + +#ifndef ALC_EXT_disconnect +#define ALC_EXT_disconnect 1 +#define ALC_CONNECTED 0x313 +#endif + +#ifndef ALC_EXT_thread_local_context +#define ALC_EXT_thread_local_context 1 +typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); +typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); +#ifdef AL_ALEXT_PROTOTYPES +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); +#endif +#endif + +#ifndef AL_EXT_source_distance_model +#define AL_EXT_source_distance_model 1 +#define AL_SOURCE_DISTANCE_MODEL 0x200 +#endif + +#ifndef AL_SOFT_buffer_sub_data +#define AL_SOFT_buffer_sub_data 1 +#define AL_BYTE_RW_OFFSETS_SOFT 0x1031 +#define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 +typedef ALvoid (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); +#endif +#endif + +#ifndef AL_SOFT_loop_points +#define AL_SOFT_loop_points 1 +#define AL_LOOP_POINTS_SOFT 0x2015 +#endif + +#ifndef AL_EXT_FOLDBACK +#define AL_EXT_FOLDBACK 1 +#define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" +#define AL_FOLDBACK_EVENT_BLOCK 0x4112 +#define AL_FOLDBACK_EVENT_START 0x4111 +#define AL_FOLDBACK_EVENT_STOP 0x4113 +#define AL_FOLDBACK_MODE_MONO 0x4101 +#define AL_FOLDBACK_MODE_STEREO 0x4102 +typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei); +typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK); +typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void); +#ifdef AL_ALEXT_PROTOTYPES +AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback); +AL_API void AL_APIENTRY alRequestFoldbackStop(void); +#endif +#endif + +#ifndef ALC_EXT_DEDICATED +#define ALC_EXT_DEDICATED 1 +#define AL_DEDICATED_GAIN 0x0001 +#define AL_EFFECT_DEDICATED_DIALOGUE 0x9001 +#define AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT 0x9000 +#endif + +#ifndef AL_SOFT_buffer_samples +#define AL_SOFT_buffer_samples 1 +/* Channel configurations */ +#define AL_MONO_SOFT 0x1500 +#define AL_STEREO_SOFT 0x1501 +#define AL_REAR_SOFT 0x1502 +#define AL_QUAD_SOFT 0x1503 +#define AL_5POINT1_SOFT 0x1504 +#define AL_6POINT1_SOFT 0x1505 +#define AL_7POINT1_SOFT 0x1506 + +/* Sample types */ +#define AL_BYTE_SOFT 0x1400 +#define AL_UNSIGNED_BYTE_SOFT 0x1401 +#define AL_SHORT_SOFT 0x1402 +#define AL_UNSIGNED_SHORT_SOFT 0x1403 +#define AL_INT_SOFT 0x1404 +#define AL_UNSIGNED_INT_SOFT 0x1405 +#define AL_FLOAT_SOFT 0x1406 +#define AL_DOUBLE_SOFT 0x1407 +#define AL_BYTE3_SOFT 0x1408 +#define AL_UNSIGNED_BYTE3_SOFT 0x1409 + +/* Storage formats */ +#define AL_MONO8_SOFT 0x1100 +#define AL_MONO16_SOFT 0x1101 +#define AL_MONO32F_SOFT 0x10010 +#define AL_STEREO8_SOFT 0x1102 +#define AL_STEREO16_SOFT 0x1103 +#define AL_STEREO32F_SOFT 0x10011 +#define AL_QUAD8_SOFT 0x1204 +#define AL_QUAD16_SOFT 0x1205 +#define AL_QUAD32F_SOFT 0x1206 +#define AL_REAR8_SOFT 0x1207 +#define AL_REAR16_SOFT 0x1208 +#define AL_REAR32F_SOFT 0x1209 +#define AL_5POINT1_8_SOFT 0x120A +#define AL_5POINT1_16_SOFT 0x120B +#define AL_5POINT1_32F_SOFT 0x120C +#define AL_6POINT1_8_SOFT 0x120D +#define AL_6POINT1_16_SOFT 0x120E +#define AL_6POINT1_32F_SOFT 0x120F +#define AL_7POINT1_8_SOFT 0x1210 +#define AL_7POINT1_16_SOFT 0x1211 +#define AL_7POINT1_32F_SOFT 0x1212 + +/* Buffer attributes */ +#define AL_INTERNAL_FORMAT_SOFT 0x2008 +#define AL_BYTE_LENGTH_SOFT 0x2009 +#define AL_SAMPLE_LENGTH_SOFT 0x200A +#define AL_SEC_LENGTH_SOFT 0x200B + +typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*); +typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*); +typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*); +typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); +#ifdef AL_ALEXT_PROTOTYPES +AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); +AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); +AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data); +AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); +#endif +#endif + +#ifndef AL_SOFT_direct_channels +#define AL_SOFT_direct_channels 1 +#define AL_DIRECT_CHANNELS_SOFT 0x1033 +#endif + +#ifndef ALC_SOFT_loopback +#define ALC_SOFT_loopback 1 +#define ALC_FORMAT_CHANNELS_SOFT 0x1990 +#define ALC_FORMAT_TYPE_SOFT 0x1991 + +/* Sample types */ +#define ALC_BYTE_SOFT 0x1400 +#define ALC_UNSIGNED_BYTE_SOFT 0x1401 +#define ALC_SHORT_SOFT 0x1402 +#define ALC_UNSIGNED_SHORT_SOFT 0x1403 +#define ALC_INT_SOFT 0x1404 +#define ALC_UNSIGNED_INT_SOFT 0x1405 +#define ALC_FLOAT_SOFT 0x1406 + +/* Channel configurations */ +#define ALC_MONO_SOFT 0x1500 +#define ALC_STEREO_SOFT 0x1501 +#define ALC_QUAD_SOFT 0x1503 +#define ALC_5POINT1_SOFT 0x1504 +#define ALC_6POINT1_SOFT 0x1505 +#define ALC_7POINT1_SOFT 0x1506 + +typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); +typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum); +typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei); +#ifdef AL_ALEXT_PROTOTYPES +ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName); +ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type); +ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +#endif +#endif + +#ifndef AL_EXT_STEREO_ANGLES +#define AL_EXT_STEREO_ANGLES 1 +#define AL_STEREO_ANGLES 0x1030 +#endif + +#ifndef AL_EXT_SOURCE_RADIUS +#define AL_EXT_SOURCE_RADIUS 1 +#define AL_SOURCE_RADIUS 0x1031 +#endif + +#ifndef AL_SOFT_source_latency +#define AL_SOFT_source_latency 1 +#define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 +#define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 +typedef int64_t ALint64SOFT; +typedef uint64_t ALuint64SOFT; +typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); +typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); +typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); +typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*); +typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*); +typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*); +typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT); +typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT); +typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*); +typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*); +typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*); +typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*); +#ifdef AL_ALEXT_PROTOTYPES +AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); +AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); +AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values); +AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value); +AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3); +AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values); +AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); +AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); +AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values); +AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value); +AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3); +AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values); +#endif +#endif + +#ifndef ALC_EXT_DEFAULT_FILTER_ORDER +#define ALC_EXT_DEFAULT_FILTER_ORDER 1 +#define ALC_DEFAULT_FILTER_ORDER 0x1100 +#endif + +#ifndef AL_SOFT_deferred_updates +#define AL_SOFT_deferred_updates 1 +#define AL_DEFERRED_UPDATES_SOFT 0xC002 +typedef ALvoid (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); +typedef ALvoid (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void); +AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void); +#endif +#endif + +#ifndef AL_SOFT_block_alignment +#define AL_SOFT_block_alignment 1 +#define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C +#define AL_PACK_BLOCK_ALIGNMENT_SOFT 0x200D +#endif + +#ifndef AL_SOFT_MSADPCM +#define AL_SOFT_MSADPCM 1 +#define AL_FORMAT_MONO_MSADPCM_SOFT 0x1302 +#define AL_FORMAT_STEREO_MSADPCM_SOFT 0x1303 +#endif + +#ifndef AL_SOFT_source_length +#define AL_SOFT_source_length 1 +/*#define AL_BYTE_LENGTH_SOFT 0x2009*/ +/*#define AL_SAMPLE_LENGTH_SOFT 0x200A*/ +/*#define AL_SEC_LENGTH_SOFT 0x200B*/ +#endif + +#ifndef ALC_SOFT_pause_device +#define ALC_SOFT_pause_device 1 +typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device); +typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device); +#ifdef AL_ALEXT_PROTOTYPES +ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device); +ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); +#endif +#endif + +#ifndef AL_EXT_BFORMAT +#define AL_EXT_BFORMAT 1 +#define AL_FORMAT_BFORMAT2D_8 0x20021 +#define AL_FORMAT_BFORMAT2D_16 0x20022 +#define AL_FORMAT_BFORMAT2D_FLOAT32 0x20023 +#define AL_FORMAT_BFORMAT3D_8 0x20031 +#define AL_FORMAT_BFORMAT3D_16 0x20032 +#define AL_FORMAT_BFORMAT3D_FLOAT32 0x20033 +#endif + +#ifndef AL_EXT_MULAW_BFORMAT +#define AL_EXT_MULAW_BFORMAT 1 +#define AL_FORMAT_BFORMAT2D_MULAW 0x10031 +#define AL_FORMAT_BFORMAT3D_MULAW 0x10032 +#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 + +#ifndef AL_SOFT_gain_clamp_ex +#define AL_SOFT_gain_clamp_ex 1 +#define AL_GAIN_LIMIT_SOFT 0x200E +#endif + +#ifndef AL_SOFT_source_resampler +#define AL_SOFT_source_resampler +#define AL_NUM_RESAMPLERS_SOFT 0x1210 +#define AL_DEFAULT_RESAMPLER_SOFT 0x1211 +#define AL_SOURCE_RESAMPLER_SOFT 0x1212 +#define AL_RESAMPLER_NAME_SOFT 0x1213 +typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); +#ifdef AL_ALEXT_PROTOTYPES +AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); +#endif +#endif + +#ifndef AL_SOFT_source_spatialize +#define AL_SOFT_source_spatialize +#define AL_SOURCE_SPATIALIZE_SOFT 0x1214 +#define AL_AUTO_SOFT 0x0002 +#endif + +#ifndef ALC_SOFT_output_limiter +#define ALC_SOFT_output_limiter +#define ALC_OUTPUT_LIMITER_SOFT 0x199A +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/openmw/mwsound/efx-presets.h b/apps/openmw/mwsound/efx-presets.h new file mode 100644 index 000000000..8539fd517 --- /dev/null +++ b/apps/openmw/mwsound/efx-presets.h @@ -0,0 +1,402 @@ +/* Reverb presets for EFX */ + +#ifndef EFX_PRESETS_H +#define EFX_PRESETS_H + +#ifndef EFXEAXREVERBPROPERTIES_DEFINED +#define EFXEAXREVERBPROPERTIES_DEFINED +typedef struct { + float flDensity; + float flDiffusion; + float flGain; + float flGainHF; + float flGainLF; + float flDecayTime; + float flDecayHFRatio; + float flDecayLFRatio; + float flReflectionsGain; + float flReflectionsDelay; + float flReflectionsPan[3]; + float flLateReverbGain; + float flLateReverbDelay; + float flLateReverbPan[3]; + float flEchoTime; + float flEchoDepth; + float flModulationTime; + float flModulationDepth; + float flAirAbsorptionGainHF; + float flHFReference; + float flLFReference; + float flRoomRolloffFactor; + int iDecayHFLimit; +} EFXEAXREVERBPROPERTIES, *LPEFXEAXREVERBPROPERTIES; +#endif + +/* Default Presets */ + +#define EFX_REVERB_PRESET_GENERIC \ + { 1.0000f, 1.0000f, 0.3162f, 0.8913f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PADDEDCELL \ + { 0.1715f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.1700f, 0.1000f, 1.0000f, 0.2500f, 0.0010f, { 0.0000f, 0.0000f, 0.0000f }, 1.2691f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ROOM \ + { 0.4287f, 1.0000f, 0.3162f, 0.5929f, 1.0000f, 0.4000f, 0.8300f, 1.0000f, 0.1503f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.0629f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_BATHROOM \ + { 0.1715f, 1.0000f, 0.3162f, 0.2512f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.6531f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 3.2734f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_LIVINGROOM \ + { 0.9766f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.5000f, 0.1000f, 1.0000f, 0.2051f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2805f, 0.0040f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_STONEROOM \ + { 1.0000f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 2.3100f, 0.6400f, 1.0000f, 0.4411f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1003f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_AUDITORIUM \ + { 1.0000f, 1.0000f, 0.3162f, 0.5781f, 1.0000f, 4.3200f, 0.5900f, 1.0000f, 0.4032f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7170f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CONCERTHALL \ + { 1.0000f, 1.0000f, 0.3162f, 0.5623f, 1.0000f, 3.9200f, 0.7000f, 1.0000f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.9977f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CAVE \ + { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 2.9100f, 1.3000f, 1.0000f, 0.5000f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.7063f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_ARENA \ + { 1.0000f, 1.0000f, 0.3162f, 0.4477f, 1.0000f, 7.2400f, 0.3300f, 1.0000f, 0.2612f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.0186f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_HANGAR \ + { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 10.0500f, 0.2300f, 1.0000f, 0.5000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2560f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CARPETEDHALLWAY \ + { 0.4287f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 0.3000f, 0.1000f, 1.0000f, 0.1215f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.1531f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_HALLWAY \ + { 0.3645f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 1.4900f, 0.5900f, 1.0000f, 0.2458f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.6615f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_STONECORRIDOR \ + { 1.0000f, 1.0000f, 0.3162f, 0.7612f, 1.0000f, 2.7000f, 0.7900f, 1.0000f, 0.2472f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 1.5758f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ALLEY \ + { 1.0000f, 0.3000f, 0.3162f, 0.7328f, 1.0000f, 1.4900f, 0.8600f, 1.0000f, 0.2500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.9954f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.9500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FOREST \ + { 1.0000f, 0.3000f, 0.3162f, 0.0224f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.0525f, 0.1620f, { 0.0000f, 0.0000f, 0.0000f }, 0.7682f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CITY \ + { 1.0000f, 0.5000f, 0.3162f, 0.3981f, 1.0000f, 1.4900f, 0.6700f, 1.0000f, 0.0730f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1427f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_MOUNTAINS \ + { 1.0000f, 0.2700f, 0.3162f, 0.0562f, 1.0000f, 1.4900f, 0.2100f, 1.0000f, 0.0407f, 0.3000f, { 0.0000f, 0.0000f, 0.0000f }, 0.1919f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_QUARRY \ + { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0000f, 0.0610f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.7000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PLAIN \ + { 1.0000f, 0.2100f, 0.3162f, 0.1000f, 1.0000f, 1.4900f, 0.5000f, 1.0000f, 0.0585f, 0.1790f, { 0.0000f, 0.0000f, 0.0000f }, 0.1089f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PARKINGLOT \ + { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 1.6500f, 1.5000f, 1.0000f, 0.2082f, 0.0080f, { 0.0000f, 0.0000f, 0.0000f }, 0.2652f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_SEWERPIPE \ + { 0.3071f, 0.8000f, 0.3162f, 0.3162f, 1.0000f, 2.8100f, 0.1400f, 1.0000f, 1.6387f, 0.0140f, { 0.0000f, 0.0000f, 0.0000f }, 3.2471f, 0.0210f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_UNDERWATER \ + { 0.3645f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 1.4900f, 0.1000f, 1.0000f, 0.5963f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 7.0795f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 1.1800f, 0.3480f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_DRUGGED \ + { 0.4287f, 0.5000f, 0.3162f, 1.0000f, 1.0000f, 8.3900f, 1.3900f, 1.0000f, 0.8760f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 3.1081f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_DIZZY \ + { 0.3645f, 0.6000f, 0.3162f, 0.6310f, 1.0000f, 17.2300f, 0.5600f, 1.0000f, 0.1392f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4937f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.8100f, 0.3100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_PSYCHOTIC \ + { 0.0625f, 0.5000f, 0.3162f, 0.8404f, 1.0000f, 7.5600f, 0.9100f, 1.0000f, 0.4864f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 2.4378f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 4.0000f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +/* Castle Presets */ + +#define EFX_REVERB_PRESET_CASTLE_SMALLROOM \ + { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 1.2200f, 0.8300f, 0.3100f, 0.8913f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE \ + { 1.0000f, 0.8900f, 0.3162f, 0.3162f, 0.1000f, 2.3200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_MEDIUMROOM \ + { 1.0000f, 0.9300f, 0.3162f, 0.2818f, 0.1000f, 2.0400f, 0.8300f, 0.4600f, 0.6310f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1550f, 0.0300f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_LARGEROOM \ + { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.1259f, 2.5300f, 0.8300f, 0.5000f, 0.4467f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1850f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_LONGPASSAGE \ + { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 3.4200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_HALL \ + { 1.0000f, 0.8100f, 0.3162f, 0.2818f, 0.1778f, 3.1400f, 0.7900f, 0.6200f, 0.1778f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_CUPBOARD \ + { 1.0000f, 0.8900f, 0.3162f, 0.2818f, 0.1000f, 0.6700f, 0.8700f, 0.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 3.5481f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CASTLE_COURTYARD \ + { 1.0000f, 0.4200f, 0.3162f, 0.4467f, 0.1995f, 2.1300f, 0.6100f, 0.2300f, 0.2239f, 0.1600f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3700f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_CASTLE_ALCOVE \ + { 1.0000f, 0.8900f, 0.3162f, 0.5012f, 0.1000f, 1.6400f, 0.8700f, 0.3100f, 1.0000f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } + +/* Factory Presets */ + +#define EFX_REVERB_PRESET_FACTORY_SMALLROOM \ + { 0.3645f, 0.8200f, 0.3162f, 0.7943f, 0.5012f, 1.7200f, 0.6500f, 1.3100f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.1190f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE \ + { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 2.5300f, 0.6500f, 1.3100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_MEDIUMROOM \ + { 0.4287f, 0.8200f, 0.2512f, 0.7943f, 0.5012f, 2.7600f, 0.6500f, 1.3100f, 0.2818f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1740f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_LARGEROOM \ + { 0.4287f, 0.7500f, 0.2512f, 0.7079f, 0.6310f, 4.2400f, 0.5100f, 1.3100f, 0.1778f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2310f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_LONGPASSAGE \ + { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 4.0600f, 0.6500f, 1.3100f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_HALL \ + { 0.4287f, 0.7500f, 0.3162f, 0.7079f, 0.6310f, 7.4300f, 0.5100f, 1.3100f, 0.0631f, 0.0730f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_CUPBOARD \ + { 0.3071f, 0.6300f, 0.2512f, 0.7943f, 0.5012f, 0.4900f, 0.6500f, 1.3100f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.1070f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_COURTYARD \ + { 0.3071f, 0.5700f, 0.3162f, 0.3162f, 0.6310f, 2.3200f, 0.2900f, 0.5600f, 0.2239f, 0.1400f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2900f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_FACTORY_ALCOVE \ + { 0.3645f, 0.5900f, 0.2512f, 0.7943f, 0.5012f, 3.1400f, 0.6500f, 1.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1140f, 0.1000f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } + +/* Ice Palace Presets */ + +#define EFX_REVERB_PRESET_ICEPALACE_SMALLROOM \ + { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 1.5100f, 1.5300f, 0.2700f, 0.8913f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1640f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_SHORTPASSAGE \ + { 1.0000f, 0.7500f, 0.3162f, 0.5623f, 0.2818f, 1.7900f, 1.4600f, 0.2800f, 0.5012f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_MEDIUMROOM \ + { 1.0000f, 0.8700f, 0.3162f, 0.5623f, 0.4467f, 2.2200f, 1.5300f, 0.3200f, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_LARGEROOM \ + { 1.0000f, 0.8100f, 0.3162f, 0.5623f, 0.4467f, 3.1400f, 1.5300f, 0.3200f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_LONGPASSAGE \ + { 1.0000f, 0.7700f, 0.3162f, 0.5623f, 0.3981f, 3.0100f, 1.4600f, 0.2800f, 0.7943f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.0400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_HALL \ + { 1.0000f, 0.7600f, 0.3162f, 0.4467f, 0.5623f, 5.4900f, 1.5300f, 0.3800f, 0.1122f, 0.0540f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0520f, { 0.0000f, 0.0000f, 0.0000f }, 0.2260f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_CUPBOARD \ + { 1.0000f, 0.8300f, 0.3162f, 0.5012f, 0.2239f, 0.7600f, 1.5300f, 0.2600f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1430f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_COURTYARD \ + { 1.0000f, 0.5900f, 0.3162f, 0.2818f, 0.3162f, 2.0400f, 1.2000f, 0.3800f, 0.3162f, 0.1730f, { 0.0000f, 0.0000f, 0.0000f }, 0.3162f, 0.0430f, { 0.0000f, 0.0000f, 0.0000f }, 0.2350f, 0.4800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_ICEPALACE_ALCOVE \ + { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 2.7600f, 1.4600f, 0.2800f, 1.1220f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1610f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } + +/* Space Station Presets */ + +#define EFX_REVERB_PRESET_SPACESTATION_SMALLROOM \ + { 0.2109f, 0.7000f, 0.3162f, 0.7079f, 0.8913f, 1.7200f, 0.8200f, 0.5500f, 0.7943f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 0.1880f, 0.2600f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE \ + { 0.2109f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 3.5700f, 0.5000f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1720f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM \ + { 0.2109f, 0.7500f, 0.3162f, 0.6310f, 0.8913f, 3.0100f, 0.5000f, 0.5500f, 0.3981f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2090f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_LARGEROOM \ + { 0.3645f, 0.8100f, 0.3162f, 0.6310f, 0.8913f, 3.8900f, 0.3800f, 0.6100f, 0.3162f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2330f, 0.2800f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE \ + { 0.4287f, 0.8200f, 0.3162f, 0.6310f, 0.8913f, 4.6200f, 0.6200f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_HALL \ + { 0.4287f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 7.1100f, 0.3800f, 0.6100f, 0.1778f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2500f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_CUPBOARD \ + { 0.1715f, 0.5600f, 0.3162f, 0.7079f, 0.8913f, 0.7900f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1810f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPACESTATION_ALCOVE \ + { 0.2109f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.1600f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1920f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } + +/* Wooden Galleon Presets */ + +#define EFX_REVERB_PRESET_WOODEN_SMALLROOM \ + { 1.0000f, 1.0000f, 0.3162f, 0.1122f, 0.3162f, 0.7900f, 0.3200f, 0.8700f, 1.0000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE \ + { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.7500f, 0.5000f, 0.8700f, 0.8913f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_MEDIUMROOM \ + { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.2818f, 1.4700f, 0.4200f, 0.8200f, 0.8913f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_LARGEROOM \ + { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.2818f, 2.6500f, 0.3300f, 0.8200f, 0.8913f, 0.0660f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_LONGPASSAGE \ + { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.3162f, 1.9900f, 0.4000f, 0.7900f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4467f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_HALL \ + { 1.0000f, 1.0000f, 0.3162f, 0.0794f, 0.2818f, 3.4500f, 0.3000f, 0.8200f, 0.8913f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_CUPBOARD \ + { 1.0000f, 1.0000f, 0.3162f, 0.1413f, 0.3162f, 0.5600f, 0.4600f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_COURTYARD \ + { 1.0000f, 0.6500f, 0.3162f, 0.0794f, 0.3162f, 1.7900f, 0.3500f, 0.7900f, 0.5623f, 0.1230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_WOODEN_ALCOVE \ + { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.2200f, 0.6200f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } + +/* Sports Presets */ + +#define EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM \ + { 1.0000f, 1.0000f, 0.3162f, 0.4467f, 0.7943f, 6.2600f, 0.5100f, 1.1000f, 0.0631f, 0.1830f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPORT_SQUASHCOURT \ + { 1.0000f, 0.7500f, 0.3162f, 0.3162f, 0.7943f, 2.2200f, 0.9100f, 1.1600f, 0.4467f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1260f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPORT_SMALLSWIMMINGPOOL \ + { 1.0000f, 0.7000f, 0.3162f, 0.7943f, 0.8913f, 2.7600f, 1.2500f, 1.1400f, 0.6310f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_SPORT_LARGESWIMMINGPOOL \ + { 1.0000f, 0.8200f, 0.3162f, 0.7943f, 1.0000f, 5.4900f, 1.3100f, 1.1400f, 0.4467f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2220f, 0.5500f, 1.1590f, 0.2100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_SPORT_GYMNASIUM \ + { 1.0000f, 0.8100f, 0.3162f, 0.4467f, 0.8913f, 3.1400f, 1.0600f, 1.3500f, 0.3981f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0450f, { 0.0000f, 0.0000f, 0.0000f }, 0.1460f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPORT_FULLSTADIUM \ + { 1.0000f, 1.0000f, 0.3162f, 0.0708f, 0.7943f, 5.2500f, 0.1700f, 0.8000f, 0.1000f, 0.1880f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SPORT_STADIUMTANNOY \ + { 1.0000f, 0.7800f, 0.3162f, 0.5623f, 0.5012f, 2.5300f, 0.8800f, 0.6800f, 0.2818f, 0.2300f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +/* Prefab Presets */ + +#define EFX_REVERB_PRESET_PREFAB_WORKSHOP \ + { 0.4287f, 1.0000f, 0.3162f, 0.1413f, 0.3981f, 0.7600f, 1.0000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_PREFAB_SCHOOLROOM \ + { 0.4022f, 0.6900f, 0.3162f, 0.6310f, 0.5012f, 0.9800f, 0.4500f, 0.1800f, 1.4125f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PREFAB_PRACTISEROOM \ + { 0.4022f, 0.8700f, 0.3162f, 0.3981f, 0.5012f, 1.1200f, 0.5600f, 0.1800f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PREFAB_OUTHOUSE \ + { 1.0000f, 0.8200f, 0.3162f, 0.1122f, 0.1585f, 1.3800f, 0.3800f, 0.3500f, 0.8913f, 0.0240f, { 0.0000f, 0.0000f, -0.0000f }, 0.6310f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.1210f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_PREFAB_CARAVAN \ + { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.1259f, 0.4300f, 1.5000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +/* Dome and Pipe Presets */ + +#define EFX_REVERB_PRESET_DOME_TOMB \ + { 1.0000f, 0.7900f, 0.3162f, 0.3548f, 0.2239f, 4.1800f, 0.2100f, 0.1000f, 0.3868f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 1.6788f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_PIPE_SMALL \ + { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 5.0400f, 0.1000f, 0.1000f, 0.5012f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 2.5119f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_DOME_SAINTPAULS \ + { 1.0000f, 0.8700f, 0.3162f, 0.3548f, 0.2239f, 10.4800f, 0.1900f, 0.1000f, 0.1778f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0420f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PIPE_LONGTHIN \ + { 0.2560f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 9.2100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_PIPE_LARGE \ + { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 8.4500f, 0.1000f, 0.1000f, 0.3981f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_PIPE_RESONANT \ + { 0.1373f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 6.8100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } + +/* Outdoors Presets */ + +#define EFX_REVERB_PRESET_OUTDOORS_BACKYARD \ + { 1.0000f, 0.4500f, 0.3162f, 0.2512f, 0.5012f, 1.1200f, 0.3400f, 0.4600f, 0.4467f, 0.0690f, { 0.0000f, 0.0000f, -0.0000f }, 0.7079f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS \ + { 1.0000f, 0.0000f, 0.3162f, 0.0112f, 0.6310f, 2.1300f, 0.2100f, 0.4600f, 0.1778f, 0.3000f, { 0.0000f, 0.0000f, -0.0000f }, 0.4467f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON \ + { 1.0000f, 0.7400f, 0.3162f, 0.1778f, 0.6310f, 3.8900f, 0.2100f, 0.4600f, 0.3162f, 0.2230f, { 0.0000f, 0.0000f, -0.0000f }, 0.3548f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_OUTDOORS_CREEK \ + { 1.0000f, 0.3500f, 0.3162f, 0.1778f, 0.5012f, 2.1300f, 0.2100f, 0.4600f, 0.3981f, 0.1150f, { 0.0000f, 0.0000f, -0.0000f }, 0.1995f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_OUTDOORS_VALLEY \ + { 1.0000f, 0.2800f, 0.3162f, 0.0282f, 0.1585f, 2.8800f, 0.2600f, 0.3500f, 0.1413f, 0.2630f, { 0.0000f, 0.0000f, -0.0000f }, 0.3981f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } + +/* Mood Presets */ + +#define EFX_REVERB_PRESET_MOOD_HEAVEN \ + { 1.0000f, 0.9400f, 0.3162f, 0.7943f, 0.4467f, 5.0400f, 1.1200f, 0.5600f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0800f, 2.7420f, 0.0500f, 0.9977f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_MOOD_HELL \ + { 1.0000f, 0.5700f, 0.3162f, 0.3548f, 0.4467f, 3.5700f, 0.4900f, 2.0000f, 0.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1100f, 0.0400f, 2.1090f, 0.5200f, 0.9943f, 5000.0000f, 139.5000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_MOOD_MEMORY \ + { 1.0000f, 0.8500f, 0.3162f, 0.6310f, 0.3548f, 4.0600f, 0.8200f, 0.5600f, 0.0398f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.4740f, 0.4500f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +/* Driving Presets */ + +#define EFX_REVERB_PRESET_DRIVING_COMMENTATOR \ + { 1.0000f, 0.0000f, 0.3162f, 0.5623f, 0.5012f, 2.4200f, 0.8800f, 0.6800f, 0.1995f, 0.0930f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_DRIVING_PITGARAGE \ + { 0.4287f, 0.5900f, 0.3162f, 0.7079f, 0.5623f, 1.7200f, 0.9300f, 0.8700f, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_DRIVING_INCAR_RACER \ + { 0.0832f, 0.8000f, 0.3162f, 1.0000f, 0.7943f, 0.1700f, 2.0000f, 0.4100f, 1.7783f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS \ + { 0.0832f, 0.8000f, 0.3162f, 0.6310f, 1.0000f, 0.1700f, 0.7500f, 0.4100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY \ + { 0.2560f, 1.0000f, 0.3162f, 0.1000f, 0.5012f, 0.1300f, 0.4100f, 0.4600f, 0.7943f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND \ + { 1.0000f, 1.0000f, 0.3162f, 0.2818f, 0.6310f, 3.0100f, 1.3700f, 1.2800f, 0.3548f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.1778f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND \ + { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 0.7943f, 4.6200f, 1.7500f, 1.4000f, 0.2082f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_DRIVING_TUNNEL \ + { 1.0000f, 0.8100f, 0.3162f, 0.3981f, 0.8913f, 3.4200f, 0.9400f, 1.3100f, 0.7079f, 0.0510f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.0500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 155.3000f, 0.0000f, 0x1 } + +/* City Presets */ + +#define EFX_REVERB_PRESET_CITY_STREETS \ + { 1.0000f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.7900f, 1.1200f, 0.9100f, 0.2818f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 0.1995f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CITY_SUBWAY \ + { 1.0000f, 0.7400f, 0.3162f, 0.7079f, 0.8913f, 3.0100f, 1.2300f, 0.9100f, 0.7079f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CITY_MUSEUM \ + { 1.0000f, 0.8200f, 0.3162f, 0.1778f, 0.1778f, 3.2800f, 1.4000f, 0.5700f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_CITY_LIBRARY \ + { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.0891f, 2.7600f, 0.8900f, 0.4100f, 0.3548f, 0.0290f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } + +#define EFX_REVERB_PRESET_CITY_UNDERPASS \ + { 1.0000f, 0.8200f, 0.3162f, 0.4467f, 0.8913f, 3.5700f, 1.1200f, 0.9100f, 0.3981f, 0.0590f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1400f, 0.2500f, 0.0000f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CITY_ABANDONED \ + { 1.0000f, 0.6900f, 0.3162f, 0.7943f, 0.8913f, 3.2800f, 1.1700f, 0.9100f, 0.4467f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9966f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +/* Misc. Presets */ + +#define EFX_REVERB_PRESET_DUSTYROOM \ + { 0.3645f, 0.5600f, 0.3162f, 0.7943f, 0.7079f, 1.7900f, 0.3800f, 0.2100f, 0.5012f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0060f, { 0.0000f, 0.0000f, 0.0000f }, 0.2020f, 0.0500f, 0.2500f, 0.0000f, 0.9886f, 13046.0000f, 163.3000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_CHAPEL \ + { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 1.0000f, 4.6200f, 0.6400f, 1.2300f, 0.4467f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.1100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } + +#define EFX_REVERB_PRESET_SMALLWATERROOM \ + { 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } + +#endif /* EFX_PRESETS_H */ diff --git a/apps/openmw/mwsound/efx.h b/apps/openmw/mwsound/efx.h new file mode 100644 index 000000000..57766983f --- /dev/null +++ b/apps/openmw/mwsound/efx.h @@ -0,0 +1,761 @@ +#ifndef AL_EFX_H +#define AL_EFX_H + + +#include "alc.h" +#include "al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ALC_EXT_EFX_NAME "ALC_EXT_EFX" + +#define ALC_EFX_MAJOR_VERSION 0x20001 +#define ALC_EFX_MINOR_VERSION 0x20002 +#define ALC_MAX_AUXILIARY_SENDS 0x20003 + + +/* Listener properties. */ +#define AL_METERS_PER_UNIT 0x20004 + +/* Source properties. */ +#define AL_DIRECT_FILTER 0x20005 +#define AL_AUXILIARY_SEND_FILTER 0x20006 +#define AL_AIR_ABSORPTION_FACTOR 0x20007 +#define AL_ROOM_ROLLOFF_FACTOR 0x20008 +#define AL_CONE_OUTER_GAINHF 0x20009 +#define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A +#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B +#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C + + +/* Effect properties. */ + +/* Reverb effect parameters */ +#define AL_REVERB_DENSITY 0x0001 +#define AL_REVERB_DIFFUSION 0x0002 +#define AL_REVERB_GAIN 0x0003 +#define AL_REVERB_GAINHF 0x0004 +#define AL_REVERB_DECAY_TIME 0x0005 +#define AL_REVERB_DECAY_HFRATIO 0x0006 +#define AL_REVERB_REFLECTIONS_GAIN 0x0007 +#define AL_REVERB_REFLECTIONS_DELAY 0x0008 +#define AL_REVERB_LATE_REVERB_GAIN 0x0009 +#define AL_REVERB_LATE_REVERB_DELAY 0x000A +#define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B +#define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C +#define AL_REVERB_DECAY_HFLIMIT 0x000D + +/* EAX Reverb effect parameters */ +#define AL_EAXREVERB_DENSITY 0x0001 +#define AL_EAXREVERB_DIFFUSION 0x0002 +#define AL_EAXREVERB_GAIN 0x0003 +#define AL_EAXREVERB_GAINHF 0x0004 +#define AL_EAXREVERB_GAINLF 0x0005 +#define AL_EAXREVERB_DECAY_TIME 0x0006 +#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 +#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 +#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 +#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A +#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B +#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C +#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D +#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E +#define AL_EAXREVERB_ECHO_TIME 0x000F +#define AL_EAXREVERB_ECHO_DEPTH 0x0010 +#define AL_EAXREVERB_MODULATION_TIME 0x0011 +#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 +#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 +#define AL_EAXREVERB_HFREFERENCE 0x0014 +#define AL_EAXREVERB_LFREFERENCE 0x0015 +#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 +#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 + +/* Chorus effect parameters */ +#define AL_CHORUS_WAVEFORM 0x0001 +#define AL_CHORUS_PHASE 0x0002 +#define AL_CHORUS_RATE 0x0003 +#define AL_CHORUS_DEPTH 0x0004 +#define AL_CHORUS_FEEDBACK 0x0005 +#define AL_CHORUS_DELAY 0x0006 + +/* Distortion effect parameters */ +#define AL_DISTORTION_EDGE 0x0001 +#define AL_DISTORTION_GAIN 0x0002 +#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 +#define AL_DISTORTION_EQCENTER 0x0004 +#define AL_DISTORTION_EQBANDWIDTH 0x0005 + +/* Echo effect parameters */ +#define AL_ECHO_DELAY 0x0001 +#define AL_ECHO_LRDELAY 0x0002 +#define AL_ECHO_DAMPING 0x0003 +#define AL_ECHO_FEEDBACK 0x0004 +#define AL_ECHO_SPREAD 0x0005 + +/* Flanger effect parameters */ +#define AL_FLANGER_WAVEFORM 0x0001 +#define AL_FLANGER_PHASE 0x0002 +#define AL_FLANGER_RATE 0x0003 +#define AL_FLANGER_DEPTH 0x0004 +#define AL_FLANGER_FEEDBACK 0x0005 +#define AL_FLANGER_DELAY 0x0006 + +/* Frequency shifter effect parameters */ +#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 +#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 +#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 + +/* Vocal morpher effect parameters */ +#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 +#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 +#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 +#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 +#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 +#define AL_VOCAL_MORPHER_RATE 0x0006 + +/* Pitchshifter effect parameters */ +#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 +#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 + +/* Ringmodulator effect parameters */ +#define AL_RING_MODULATOR_FREQUENCY 0x0001 +#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 +#define AL_RING_MODULATOR_WAVEFORM 0x0003 + +/* Autowah effect parameters */ +#define AL_AUTOWAH_ATTACK_TIME 0x0001 +#define AL_AUTOWAH_RELEASE_TIME 0x0002 +#define AL_AUTOWAH_RESONANCE 0x0003 +#define AL_AUTOWAH_PEAK_GAIN 0x0004 + +/* Compressor effect parameters */ +#define AL_COMPRESSOR_ONOFF 0x0001 + +/* Equalizer effect parameters */ +#define AL_EQUALIZER_LOW_GAIN 0x0001 +#define AL_EQUALIZER_LOW_CUTOFF 0x0002 +#define AL_EQUALIZER_MID1_GAIN 0x0003 +#define AL_EQUALIZER_MID1_CENTER 0x0004 +#define AL_EQUALIZER_MID1_WIDTH 0x0005 +#define AL_EQUALIZER_MID2_GAIN 0x0006 +#define AL_EQUALIZER_MID2_CENTER 0x0007 +#define AL_EQUALIZER_MID2_WIDTH 0x0008 +#define AL_EQUALIZER_HIGH_GAIN 0x0009 +#define AL_EQUALIZER_HIGH_CUTOFF 0x000A + +/* Effect type */ +#define AL_EFFECT_FIRST_PARAMETER 0x0000 +#define AL_EFFECT_LAST_PARAMETER 0x8000 +#define AL_EFFECT_TYPE 0x8001 + +/* Effect types, used with the AL_EFFECT_TYPE property */ +#define AL_EFFECT_NULL 0x0000 +#define AL_EFFECT_REVERB 0x0001 +#define AL_EFFECT_CHORUS 0x0002 +#define AL_EFFECT_DISTORTION 0x0003 +#define AL_EFFECT_ECHO 0x0004 +#define AL_EFFECT_FLANGER 0x0005 +#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 +#define AL_EFFECT_VOCAL_MORPHER 0x0007 +#define AL_EFFECT_PITCH_SHIFTER 0x0008 +#define AL_EFFECT_RING_MODULATOR 0x0009 +#define AL_EFFECT_AUTOWAH 0x000A +#define AL_EFFECT_COMPRESSOR 0x000B +#define AL_EFFECT_EQUALIZER 0x000C +#define AL_EFFECT_EAXREVERB 0x8000 + +/* Auxiliary Effect Slot properties. */ +#define AL_EFFECTSLOT_EFFECT 0x0001 +#define AL_EFFECTSLOT_GAIN 0x0002 +#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 + +/* NULL Auxiliary Slot ID to disable a source send. */ +#define AL_EFFECTSLOT_NULL 0x0000 + + +/* Filter properties. */ + +/* Lowpass filter parameters */ +#define AL_LOWPASS_GAIN 0x0001 +#define AL_LOWPASS_GAINHF 0x0002 + +/* Highpass filter parameters */ +#define AL_HIGHPASS_GAIN 0x0001 +#define AL_HIGHPASS_GAINLF 0x0002 + +/* Bandpass filter parameters */ +#define AL_BANDPASS_GAIN 0x0001 +#define AL_BANDPASS_GAINLF 0x0002 +#define AL_BANDPASS_GAINHF 0x0003 + +/* Filter type */ +#define AL_FILTER_FIRST_PARAMETER 0x0000 +#define AL_FILTER_LAST_PARAMETER 0x8000 +#define AL_FILTER_TYPE 0x8001 + +/* Filter types, used with the AL_FILTER_TYPE property */ +#define AL_FILTER_NULL 0x0000 +#define AL_FILTER_LOWPASS 0x0001 +#define AL_FILTER_HIGHPASS 0x0002 +#define AL_FILTER_BANDPASS 0x0003 + + +/* Effect object function types. */ +typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); +typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*); +typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*); +typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); + +/* Filter object function types. */ +typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); +typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*); +typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*); +typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); + +/* Auxiliary Effect Slot object function types. */ +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); + +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); +AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); +AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); +AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); + +AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); +AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); +AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); +AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); + +AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); +AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +#endif + +/* Filter ranges and defaults. */ + +/* Lowpass filter */ +#define AL_LOWPASS_MIN_GAIN (0.0f) +#define AL_LOWPASS_MAX_GAIN (1.0f) +#define AL_LOWPASS_DEFAULT_GAIN (1.0f) + +#define AL_LOWPASS_MIN_GAINHF (0.0f) +#define AL_LOWPASS_MAX_GAINHF (1.0f) +#define AL_LOWPASS_DEFAULT_GAINHF (1.0f) + +/* Highpass filter */ +#define AL_HIGHPASS_MIN_GAIN (0.0f) +#define AL_HIGHPASS_MAX_GAIN (1.0f) +#define AL_HIGHPASS_DEFAULT_GAIN (1.0f) + +#define AL_HIGHPASS_MIN_GAINLF (0.0f) +#define AL_HIGHPASS_MAX_GAINLF (1.0f) +#define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) + +/* Bandpass filter */ +#define AL_BANDPASS_MIN_GAIN (0.0f) +#define AL_BANDPASS_MAX_GAIN (1.0f) +#define AL_BANDPASS_DEFAULT_GAIN (1.0f) + +#define AL_BANDPASS_MIN_GAINHF (0.0f) +#define AL_BANDPASS_MAX_GAINHF (1.0f) +#define AL_BANDPASS_DEFAULT_GAINHF (1.0f) + +#define AL_BANDPASS_MIN_GAINLF (0.0f) +#define AL_BANDPASS_MAX_GAINLF (1.0f) +#define AL_BANDPASS_DEFAULT_GAINLF (1.0f) + + +/* Effect parameter ranges and defaults. */ + +/* Standard reverb effect */ +#define AL_REVERB_MIN_DENSITY (0.0f) +#define AL_REVERB_MAX_DENSITY (1.0f) +#define AL_REVERB_DEFAULT_DENSITY (1.0f) + +#define AL_REVERB_MIN_DIFFUSION (0.0f) +#define AL_REVERB_MAX_DIFFUSION (1.0f) +#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) + +#define AL_REVERB_MIN_GAIN (0.0f) +#define AL_REVERB_MAX_GAIN (1.0f) +#define AL_REVERB_DEFAULT_GAIN (0.32f) + +#define AL_REVERB_MIN_GAINHF (0.0f) +#define AL_REVERB_MAX_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_GAINHF (0.89f) + +#define AL_REVERB_MIN_DECAY_TIME (0.1f) +#define AL_REVERB_MAX_DECAY_TIME (20.0f) +#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) + +#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) + +#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) + +#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) + +#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) + +#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) + +#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) + +#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE + +/* EAX reverb effect */ +#define AL_EAXREVERB_MIN_DENSITY (0.0f) +#define AL_EAXREVERB_MAX_DENSITY (1.0f) +#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) + +#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) +#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) +#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) + +#define AL_EAXREVERB_MIN_GAIN (0.0f) +#define AL_EAXREVERB_MAX_GAIN (1.0f) +#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) + +#define AL_EAXREVERB_MIN_GAINHF (0.0f) +#define AL_EAXREVERB_MAX_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) + +#define AL_EAXREVERB_MIN_GAINLF (0.0f) +#define AL_EAXREVERB_MAX_GAINLF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) + +#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) +#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) + +#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) + +#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) + +#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) + +#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) + +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) + +#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) + +#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) + +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) + +#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) +#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) +#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) + +#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) + +#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) +#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) + +#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) + +#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) + +#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) +#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) +#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) + +#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) +#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) +#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) + +#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE + +/* Chorus effect */ +#define AL_CHORUS_WAVEFORM_SINUSOID (0) +#define AL_CHORUS_WAVEFORM_TRIANGLE (1) + +#define AL_CHORUS_MIN_WAVEFORM (0) +#define AL_CHORUS_MAX_WAVEFORM (1) +#define AL_CHORUS_DEFAULT_WAVEFORM (1) + +#define AL_CHORUS_MIN_PHASE (-180) +#define AL_CHORUS_MAX_PHASE (180) +#define AL_CHORUS_DEFAULT_PHASE (90) + +#define AL_CHORUS_MIN_RATE (0.0f) +#define AL_CHORUS_MAX_RATE (10.0f) +#define AL_CHORUS_DEFAULT_RATE (1.1f) + +#define AL_CHORUS_MIN_DEPTH (0.0f) +#define AL_CHORUS_MAX_DEPTH (1.0f) +#define AL_CHORUS_DEFAULT_DEPTH (0.1f) + +#define AL_CHORUS_MIN_FEEDBACK (-1.0f) +#define AL_CHORUS_MAX_FEEDBACK (1.0f) +#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) + +#define AL_CHORUS_MIN_DELAY (0.0f) +#define AL_CHORUS_MAX_DELAY (0.016f) +#define AL_CHORUS_DEFAULT_DELAY (0.016f) + +/* Distortion effect */ +#define AL_DISTORTION_MIN_EDGE (0.0f) +#define AL_DISTORTION_MAX_EDGE (1.0f) +#define AL_DISTORTION_DEFAULT_EDGE (0.2f) + +#define AL_DISTORTION_MIN_GAIN (0.01f) +#define AL_DISTORTION_MAX_GAIN (1.0f) +#define AL_DISTORTION_DEFAULT_GAIN (0.05f) + +#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) +#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) +#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) + +#define AL_DISTORTION_MIN_EQCENTER (80.0f) +#define AL_DISTORTION_MAX_EQCENTER (24000.0f) +#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) + +#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) +#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) +#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) + +/* Echo effect */ +#define AL_ECHO_MIN_DELAY (0.0f) +#define AL_ECHO_MAX_DELAY (0.207f) +#define AL_ECHO_DEFAULT_DELAY (0.1f) + +#define AL_ECHO_MIN_LRDELAY (0.0f) +#define AL_ECHO_MAX_LRDELAY (0.404f) +#define AL_ECHO_DEFAULT_LRDELAY (0.1f) + +#define AL_ECHO_MIN_DAMPING (0.0f) +#define AL_ECHO_MAX_DAMPING (0.99f) +#define AL_ECHO_DEFAULT_DAMPING (0.5f) + +#define AL_ECHO_MIN_FEEDBACK (0.0f) +#define AL_ECHO_MAX_FEEDBACK (1.0f) +#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) + +#define AL_ECHO_MIN_SPREAD (-1.0f) +#define AL_ECHO_MAX_SPREAD (1.0f) +#define AL_ECHO_DEFAULT_SPREAD (-1.0f) + +/* Flanger effect */ +#define AL_FLANGER_WAVEFORM_SINUSOID (0) +#define AL_FLANGER_WAVEFORM_TRIANGLE (1) + +#define AL_FLANGER_MIN_WAVEFORM (0) +#define AL_FLANGER_MAX_WAVEFORM (1) +#define AL_FLANGER_DEFAULT_WAVEFORM (1) + +#define AL_FLANGER_MIN_PHASE (-180) +#define AL_FLANGER_MAX_PHASE (180) +#define AL_FLANGER_DEFAULT_PHASE (0) + +#define AL_FLANGER_MIN_RATE (0.0f) +#define AL_FLANGER_MAX_RATE (10.0f) +#define AL_FLANGER_DEFAULT_RATE (0.27f) + +#define AL_FLANGER_MIN_DEPTH (0.0f) +#define AL_FLANGER_MAX_DEPTH (1.0f) +#define AL_FLANGER_DEFAULT_DEPTH (1.0f) + +#define AL_FLANGER_MIN_FEEDBACK (-1.0f) +#define AL_FLANGER_MAX_FEEDBACK (1.0f) +#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) + +#define AL_FLANGER_MIN_DELAY (0.0f) +#define AL_FLANGER_MAX_DELAY (0.004f) +#define AL_FLANGER_DEFAULT_DELAY (0.002f) + +/* Frequency shifter effect */ +#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) +#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) +#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) + +#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) + +#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) +#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) +#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) + +#define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) + +/* Vocal morpher effect */ +#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) + +#define AL_VOCAL_MORPHER_PHONEME_A (0) +#define AL_VOCAL_MORPHER_PHONEME_E (1) +#define AL_VOCAL_MORPHER_PHONEME_I (2) +#define AL_VOCAL_MORPHER_PHONEME_O (3) +#define AL_VOCAL_MORPHER_PHONEME_U (4) +#define AL_VOCAL_MORPHER_PHONEME_AA (5) +#define AL_VOCAL_MORPHER_PHONEME_AE (6) +#define AL_VOCAL_MORPHER_PHONEME_AH (7) +#define AL_VOCAL_MORPHER_PHONEME_AO (8) +#define AL_VOCAL_MORPHER_PHONEME_EH (9) +#define AL_VOCAL_MORPHER_PHONEME_ER (10) +#define AL_VOCAL_MORPHER_PHONEME_IH (11) +#define AL_VOCAL_MORPHER_PHONEME_IY (12) +#define AL_VOCAL_MORPHER_PHONEME_UH (13) +#define AL_VOCAL_MORPHER_PHONEME_UW (14) +#define AL_VOCAL_MORPHER_PHONEME_B (15) +#define AL_VOCAL_MORPHER_PHONEME_D (16) +#define AL_VOCAL_MORPHER_PHONEME_F (17) +#define AL_VOCAL_MORPHER_PHONEME_G (18) +#define AL_VOCAL_MORPHER_PHONEME_J (19) +#define AL_VOCAL_MORPHER_PHONEME_K (20) +#define AL_VOCAL_MORPHER_PHONEME_L (21) +#define AL_VOCAL_MORPHER_PHONEME_M (22) +#define AL_VOCAL_MORPHER_PHONEME_N (23) +#define AL_VOCAL_MORPHER_PHONEME_P (24) +#define AL_VOCAL_MORPHER_PHONEME_R (25) +#define AL_VOCAL_MORPHER_PHONEME_S (26) +#define AL_VOCAL_MORPHER_PHONEME_T (27) +#define AL_VOCAL_MORPHER_PHONEME_V (28) +#define AL_VOCAL_MORPHER_PHONEME_Z (29) + +#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) +#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) +#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) + +#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) +#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) +#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) + +#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) +#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) +#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) + +/* Pitch shifter effect */ +#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) +#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) +#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) + +#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) +#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) +#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) + +/* Ring modulator effect */ +#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) +#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) +#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) + +#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) +#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) +#define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) + +#define AL_RING_MODULATOR_SINUSOID (0) +#define AL_RING_MODULATOR_SAWTOOTH (1) +#define AL_RING_MODULATOR_SQUARE (2) + +#define AL_RING_MODULATOR_MIN_WAVEFORM (0) +#define AL_RING_MODULATOR_MAX_WAVEFORM (2) +#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) + +/* Autowah effect */ +#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) +#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) + +#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) +#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) + +#define AL_AUTOWAH_MIN_RESONANCE (2.0f) +#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) +#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) + +#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) +#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) +#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) + +/* Compressor effect */ +#define AL_COMPRESSOR_MIN_ONOFF (0) +#define AL_COMPRESSOR_MAX_ONOFF (1) +#define AL_COMPRESSOR_DEFAULT_ONOFF (1) + +/* Equalizer effect */ +#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) +#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) +#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) +#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) + +#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) +#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) +#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) + +#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) + +#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) +#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) +#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) + +#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) + +#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) +#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) +#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) +#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) + + +/* Source parameter value ranges and defaults. */ +#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) +#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) +#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) + +#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_MIN_CONE_OUTER_GAINHF (0.0f) +#define AL_MAX_CONE_OUTER_GAINHF (1.0f) +#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) + +#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE + +#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE +#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE + +#define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE + + +/* Listener parameter value ranges and defaults. */ +#define AL_MIN_METERS_PER_UNIT FLT_MIN +#define AL_MAX_METERS_PER_UNIT FLT_MAX +#define AL_DEFAULT_METERS_PER_UNIT (1.0f) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AL_EFX_H */ diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 1f1834761..e2d54876f 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -11,11 +11,6 @@ namespace MWSound { -void FFmpeg_Decoder::fail(const std::string &msg) -{ - throw std::runtime_error("FFmpeg exception: "+msg); -} - int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { try @@ -33,7 +28,8 @@ int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) int FFmpeg_Decoder::writePacket(void *, uint8_t *, int) { - throw std::runtime_error("can't write to read-only stream"); + std::cerr<< "can't write to read-only stream" <get(fname); if((mFormatCtx=avformat_alloc_context()) == NULL) - fail("Failed to allocate context"); + throw std::runtime_error("Failed to allocate context"); - mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); - if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) + try { - // "Note that a user-supplied AVFormatContext will be freed on failure". - if (mFormatCtx) + mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); + if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { - if (mFormatCtx->pb != NULL) + // "Note that a user-supplied AVFormatContext will be freed on failure". + if (mFormatCtx) { - if (mFormatCtx->pb->buffer != NULL) + if (mFormatCtx->pb != NULL) { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; } - av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + avformat_free_context(mFormatCtx); } - avformat_free_context(mFormatCtx); + mFormatCtx = NULL; + throw std::runtime_error("Failed to allocate input stream"); } - mFormatCtx = NULL; - fail("Failed to allocate input stream"); - } - try - { if(avformat_find_stream_info(mFormatCtx, NULL) < 0) - fail("Failed to find stream info in "+fname); + throw std::runtime_error("Failed to find stream info in "+fname); for(size_t j = 0;j < mFormatCtx->nb_streams;j++) { @@ -224,32 +220,46 @@ void FFmpeg_Decoder::open(const std::string &fname) } } if(!mStream) - fail("No audio streams in "+fname); + throw std::runtime_error("No audio streams in "+fname); (*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt; AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); if(!codec) { - std::stringstream ss("No codec found for id "); - ss << (*mStream)->codec->codec_id; - fail(ss.str()); + std::string ss = "No codec found for id " + + std::to_string((*mStream)->codec->codec_id); + throw std::runtime_error(ss); } if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) - fail("Failed to open audio codec " + std::string(codec->long_name)); + throw std::runtime_error(std::string("Failed to open audio codec ") + + codec->long_name); mFrame = av_frame_alloc(); + + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT || + (*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + + mOutputChannelLayout = (*mStream)->codec->channel_layout; + if(mOutputChannelLayout == 0) + mOutputChannelLayout = av_get_default_channel_layout((*mStream)->codec->channels); } - catch(std::exception&) - { + catch(...) { if(mStream) avcodec_close((*mStream)->codec); mStream = NULL; if (mFormatCtx->pb->buffer != NULL) { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; } av_free(mFormatCtx->pb); mFormatCtx->pb = NULL; @@ -274,18 +284,18 @@ void FFmpeg_Decoder::close() { if (mFormatCtx->pb != NULL) { - // mFormatCtx->pb->buffer must be freed by hand, - // if not, valgrind will show memleak, see: - // - // https://trac.ffmpeg.org/ticket/1357 - // - if (mFormatCtx->pb->buffer != NULL) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + // mFormatCtx->pb->buffer must be freed by hand, + // if not, valgrind will show memleak, see: + // + // https://trac.ffmpeg.org/ticket/1357 + // + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; } avformat_close_input(&mFormatCtx); } @@ -301,16 +311,7 @@ std::string FFmpeg_Decoder::getName() void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(!mStream) - fail("No audio stream info"); - - if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT || (*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support - else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else - mOutputSampleFormat = AV_SAMPLE_FMT_S16; + throw std::runtime_error("No audio stream info"); if(mOutputSampleFormat == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; @@ -318,19 +319,11 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * *type = SampleType_Int16; else if(mOutputSampleFormat == AV_SAMPLE_FMT_FLT) *type = SampleType_Float32; - - int64_t ch_layout = (*mStream)->codec->channel_layout; - - if(ch_layout == 0) - ch_layout = av_get_default_channel_layout((*mStream)->codec->channels); - - mOutputChannelLayout = ch_layout; - if (ch_layout == AV_CH_LAYOUT_5POINT1 || ch_layout == AV_CH_LAYOUT_7POINT1 - || ch_layout == AV_CH_LAYOUT_QUAD) // FIXME: check for AL_EXT_MCFORMATS support - mOutputChannelLayout = AV_CH_LAYOUT_STEREO; - else if (ch_layout != AV_CH_LAYOUT_MONO - && ch_layout != AV_CH_LAYOUT_STEREO) - mOutputChannelLayout = AV_CH_LAYOUT_STEREO; + else + { + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + *type = SampleType_Int16; + } if(mOutputChannelLayout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; @@ -347,13 +340,27 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * char str[1024]; av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels, (*mStream)->codec->channel_layout); - fail(std::string("Unsupported channel layout: ")+str); + std::cerr<< "Unsupported channel layout: "<codec->channels == 1) + { + mOutputChannelLayout = AV_CH_LAYOUT_MONO; + *chans = ChannelConfig_Mono; + } + else + { + mOutputChannelLayout = AV_CH_LAYOUT_STEREO; + *chans = ChannelConfig_Stereo; + } } *samplerate = (*mStream)->codec->sample_rate; + int64_t ch_layout = (*mStream)->codec->channel_layout; + if(ch_layout == 0) + ch_layout = av_get_default_channel_layout((*mStream)->codec->channels); - if(mOutputSampleFormat != (*mStream)->codec->sample_fmt - || mOutputChannelLayout != ch_layout) + if(mOutputSampleFormat != (*mStream)->codec->sample_fmt || + mOutputChannelLayout != ch_layout) { mSwr = swr_alloc_set_opts(mSwr, // SwrContext mOutputChannelLayout, // output ch layout @@ -365,24 +372,29 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * 0, // logging level offset NULL); // log context if(!mSwr) - fail(std::string("Couldn't allocate SwrContext")); + throw std::runtime_error("Couldn't allocate SwrContext"); if(swr_init(mSwr) < 0) - fail(std::string("Couldn't initialize SwrContext")); - + throw std::runtime_error("Couldn't initialize SwrContext"); } } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { if(!mStream) - fail("No audio stream"); + { + std::cerr<< "No audio stream" < &output) { if(!mStream) - fail("No audio stream"); + { + std::cerr<< "No audio stream" < &output); - virtual size_t getSampleOffset(); - - void fail(const std::string &msg); + size_t read(char *buffer, size_t bytes) override; + void readAll(std::vector &output) override; + size_t getSampleOffset() override; FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 2d7f3a969..497562516 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -1,5 +1,7 @@ #include "movieaudiofactory.hpp" +#include + #include #include @@ -13,7 +15,7 @@ namespace MWSound { class MovieAudioDecoder; - class MWSoundDecoderBridge : public Sound_Decoder + class MWSoundDecoderBridge final : public Sound_Decoder { public: MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder) @@ -25,19 +27,19 @@ namespace MWSound private: MWSound::MovieAudioDecoder* mDecoder; - virtual void open(const std::string &fname); - virtual void close(); - virtual std::string getName(); - virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); - virtual size_t read(char *buffer, size_t bytes); - virtual size_t getSampleOffset(); + void open(const std::string &fname) override; + void close() override; + std::string getName() override; + void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override; + size_t read(char *buffer, size_t bytes) override; + size_t getSampleOffset() override; }; class MovieAudioDecoder : public Video::MovieAudioDecoder { public: MovieAudioDecoder(Video::VideoState *videoState) - : Video::MovieAudioDecoder(videoState) + : Video::MovieAudioDecoder(videoState), mAudioTrack(nullptr) { mDecoderBridge.reset(new MWSoundDecoderBridge(this)); } @@ -85,20 +87,20 @@ namespace MWSound public: ~MovieAudioDecoder() { - if(mAudioTrack.get()) + if(mAudioTrack) MWBase::Environment::get().getSoundManager()->stopTrack(mAudioTrack); - mAudioTrack.reset(); + mAudioTrack = nullptr; mDecoderBridge.reset(); } - MWBase::SoundStreamPtr mAudioTrack; + MWBase::SoundStream *mAudioTrack; std::shared_ptr mDecoderBridge; }; void MWSoundDecoderBridge::open(const std::string &fname) { - throw std::runtime_error("unimplemented"); + throw std::runtime_error("Method not implemented"); } void MWSoundDecoderBridge::close() {} @@ -123,11 +125,8 @@ namespace MWSound else if (outputChannelLayout == AV_CH_LAYOUT_QUAD) *chans = ChannelConfig_Quad; else - { - std::stringstream error; - error << "Unsupported channel layout: " << outputChannelLayout; - throw std::runtime_error(error.str()); - } + throw std::runtime_error("Unsupported channel layout: "+ + std::to_string(outputChannelLayout)); AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat(); if (outputSampleFormat == AV_SAMPLE_FMT_U8) @@ -140,7 +139,7 @@ namespace MWSound { char str[1024]; av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat); - throw std::runtime_error(std::string("Unsupported sample format: ") + str); + throw std::runtime_error(std::string("Unsupported sample format: ")+str); } } @@ -162,8 +161,8 @@ namespace MWSound decoder->setupFormat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - MWBase::SoundStreamPtr sound = sndMgr->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie); - if (!sound.get()) + MWBase::SoundStream *sound = sndMgr->playTrack(decoder->mDecoderBridge, MWSound::Type::Movie); + if (!sound) { decoder.reset(); return decoder; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 042795a8c..829c001e5 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1,9 +1,10 @@ #include #include #include +#include #include #include -#include +#include #include @@ -20,32 +21,12 @@ #include "soundmanagerimp.hpp" #include "loudness.hpp" +#include "efx-presets.h" + #ifndef ALC_ALL_DEVICES_SPECIFIER #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) @@ -53,8 +34,32 @@ ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCi namespace { +// The game uses 64 units per yard, or approximately 69.99125109 units per meter. +// Should this be defined publically somewhere? +const float UnitsPerMeter = 69.99125109f; + const int sLoudnessFPS = 20; // loudness values per second of audio +ALCenum checkALCError(ALCdevice *device, const char *func, int line) +{ + ALCenum err = alcGetError(device); + if(err != ALC_NO_ERROR) + std::cerr<< ">>>>>>>>> ALC error "<>>>>>>>> AL error "< void convertPointer(T& dest, R src) @@ -63,82 +68,152 @@ void convertPointer(T& dest, R src) } template -void getFunc(T& func, ALCdevice *device, const char *name) +void getALCFunc(T& func, ALCdevice *device, const char *name) { void* funcPtr = alcGetProcAddress(device, name); convertPointer(func, funcPtr); } -} - -namespace MWSound +template +void getALFunc(T& func, const char *name) { + void* funcPtr = alGetProcAddress(name); + convertPointer(func, funcPtr); +} -static void fail(const std::string &msg) -{ throw std::runtime_error("OpenAL exception: " + msg); } - -static void throwALCerror(ALCdevice *device) +// Effect objects +LPALGENEFFECTS alGenEffects; +LPALDELETEEFFECTS alDeleteEffects; +LPALISEFFECT alIsEffect; +LPALEFFECTI alEffecti; +LPALEFFECTIV alEffectiv; +LPALEFFECTF alEffectf; +LPALEFFECTFV alEffectfv; +LPALGETEFFECTI alGetEffecti; +LPALGETEFFECTIV alGetEffectiv; +LPALGETEFFECTF alGetEffectf; +LPALGETEFFECTFV alGetEffectfv; +// Filter objects +LPALGENFILTERS alGenFilters; +LPALDELETEFILTERS alDeleteFilters; +LPALISFILTER alIsFilter; +LPALFILTERI alFilteri; +LPALFILTERIV alFilteriv; +LPALFILTERF alFilterf; +LPALFILTERFV alFilterfv; +LPALGETFILTERI alGetFilteri; +LPALGETFILTERIV alGetFilteriv; +LPALGETFILTERF alGetFilterf; +LPALGETFILTERFV alGetFilterfv; +// Auxiliary slot objects +LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + + +void LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES &props) { - ALCenum err = alcGetError(device); - if(err != ALC_NO_ERROR) + ALint type = AL_NONE; + alGetEffecti(effect, AL_EFFECT_TYPE, &type); + if(type == AL_EFFECT_EAXREVERB) { - const ALCchar *errstring = alcGetString(device, err); - fail(errstring ? errstring : ""); + alEffectf(effect, AL_EAXREVERB_DIFFUSION, props.flDiffusion); + alEffectf(effect, AL_EAXREVERB_DENSITY, props.flDensity); + alEffectf(effect, AL_EAXREVERB_GAIN, props.flGain); + alEffectf(effect, AL_EAXREVERB_GAINHF, props.flGainHF); + alEffectf(effect, AL_EAXREVERB_GAINLF, props.flGainLF); + alEffectf(effect, AL_EAXREVERB_DECAY_TIME, props.flDecayTime); + alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, props.flDecayHFRatio); + alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, props.flDecayLFRatio); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, props.flReflectionsGain); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); + alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, props.flReflectionsPan); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, props.flLateReverbGain); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); + alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, props.flLateReverbPan); + alEffectf(effect, AL_EAXREVERB_ECHO_TIME, props.flEchoTime); + alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, props.flEchoDepth); + alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, props.flModulationTime); + alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, props.flModulationDepth); + alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); + alEffectf(effect, AL_EAXREVERB_HFREFERENCE, props.flHFReference); + alEffectf(effect, AL_EAXREVERB_LFREFERENCE, props.flLFReference); + alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); + alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); } -} - -static void throwALerror() -{ - ALenum err = alGetError(); - if(err != AL_NO_ERROR) + else if(type == AL_EFFECT_REVERB) { - const ALchar *errstring = alGetString(err); - fail(errstring ? errstring : ""); + alEffectf(effect, AL_REVERB_DIFFUSION, props.flDiffusion); + alEffectf(effect, AL_REVERB_DENSITY, props.flDensity); + alEffectf(effect, AL_REVERB_GAIN, props.flGain); + alEffectf(effect, AL_REVERB_GAINHF, props.flGainHF); + alEffectf(effect, AL_REVERB_DECAY_TIME, props.flDecayTime); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props.flDecayHFRatio); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, props.flReflectionsGain); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, props.flLateReverbGain); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); } + getALError(); +} + } +namespace MWSound +{ static ALenum getALFormat(ChannelConfig chans, SampleType type) { - static const struct { + struct FormatEntry { ALenum format; ChannelConfig chans; SampleType type; - } fmtlist[] = { + }; + struct FormatEntryExt { + const char name[32]; + ChannelConfig chans; + SampleType type; + }; + static const std::array fmtlist{{ { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 }, { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 }, { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 }, { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 }, - }; - static const size_t fmtlistsize = sizeof(fmtlist)/sizeof(fmtlist[0]); + }}; - for(size_t i = 0;i < fmtlistsize;i++) + for(auto &fmt : fmtlist) { - if(fmtlist[i].chans == chans && fmtlist[i].type == type) - return fmtlist[i].format; + if(fmt.chans == chans && fmt.type == type) + return fmt.format; } if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - static const struct { - char name[32]; - ChannelConfig chans; - SampleType type; - } mcfmtlist[] = { + static const std::array mcfmtlist{{ { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, - }; - static const size_t mcfmtlistsize = sizeof(mcfmtlist)/sizeof(mcfmtlist[0]); + }}; - for(size_t i = 0;i < mcfmtlistsize;i++) + for(auto &fmt : mcfmtlist) { - if(mcfmtlist[i].chans == chans && mcfmtlist[i].type == type) + if(fmt.chans == chans && fmt.type == type) { - ALenum format = alGetEnumValue(mcfmtlist[i].name); + ALenum format = alGetEnumValue(fmt.name); if(format != 0 && format != -1) return format; } @@ -146,43 +221,34 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) } if(alIsExtensionPresent("AL_EXT_FLOAT32")) { - static const struct { - char name[32]; - ChannelConfig chans; - SampleType type; - } fltfmtlist[] = { + static const std::array fltfmtlist{{ { "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 }, { "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 }, - }; - static const size_t fltfmtlistsize = sizeof(fltfmtlist)/sizeof(fltfmtlist[0]); + }}; - for(size_t i = 0;i < fltfmtlistsize;i++) + for(auto &fmt : fltfmtlist) { - if(fltfmtlist[i].chans == chans && fltfmtlist[i].type == type) + if(fmt.chans == chans && fmt.type == type) { - ALenum format = alGetEnumValue(fltfmtlist[i].name); + ALenum format = alGetEnumValue(fmt.name); if(format != 0 && format != -1) return format; } } + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - static const struct { - char name[32]; - ChannelConfig chans; - SampleType type; - } fltmcfmtlist[] = { + static const std::array fltmcfmtlist{{ { "AL_FORMAT_QUAD32", ChannelConfig_Quad, SampleType_Float32 }, { "AL_FORMAT_51CHN32", ChannelConfig_5point1, SampleType_Float32 }, { "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 }, - }; - static const size_t fltmcfmtlistsize = sizeof(fltmcfmtlist)/sizeof(fltmcfmtlist[0]); + }}; - for(size_t i = 0;i < fltmcfmtlistsize;i++) + for(auto &fmt : fltmcfmtlist) { - if(fltmcfmtlist[i].chans == chans && fltmcfmtlist[i].type == type) + if(fmt.chans == chans && fmt.type == type) { - ALenum format = alGetEnumValue(fltmcfmtlist[i].name); + ALenum format = alGetEnumValue(fmt.name); if(format != 0 && format != -1) return format; } @@ -190,7 +256,8 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) } } - fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")"); + std::cerr<< "Unsupported sound format ("< mBuffers; ALint mCurrentBufIdx; ALenum mFormat; @@ -229,9 +295,11 @@ private: friend class OpenAL_Output; public: - OpenAL_SoundStream(ALuint src, DecoderPtr decoder, bool getLoudnessData=false); + OpenAL_SoundStream(ALuint src, DecoderPtr decoder); ~OpenAL_SoundStream(); + bool init(bool getLoudnessData=false); + bool isPlaying(); double getStreamDelay() const; double getStreamOffset() const; @@ -316,49 +384,58 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder, bool getLoudnessData) - : mSource(src), mCurrentBufIdx(0), mFrameSize(0), mSilence(0), mDecoder(decoder), mIsFinished(false) +OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) + : mSource(src), mCurrentBufIdx(0), mFormat(AL_NONE), mSampleRate(0) + , mBufferSize(0), mFrameSize(0), mSilence(0), mDecoder(std::move(decoder)) + , mLoudnessAnalyzer(nullptr) { - alGenBuffers(sNumBuffers, mBuffers); - throwALerror(); - try - { - int srate; - ChannelConfig chans; - SampleType type; + mBuffers.fill(0); +} - mDecoder->getInfo(&srate, &chans, &type); - mFormat = getALFormat(chans, type); - mSampleRate = srate; +OpenAL_SoundStream::~OpenAL_SoundStream() +{ + if(mBuffers[0] && alIsBuffer(mBuffers[0])) + alDeleteBuffers(mBuffers.size(), mBuffers.data()); + alGetError(); - switch(type) - { - case SampleType_UInt8: mSilence = 0x80; break; - case SampleType_Int16: mSilence = 0x00; break; - case SampleType_Float32: mSilence = 0x00; break; - } + mDecoder->close(); +} + +bool OpenAL_SoundStream::init(bool getLoudnessData) +{ + alGenBuffers(mBuffers.size(), mBuffers.data()); + ALenum err = getALError(); + if(err != AL_NO_ERROR) + return false; - mFrameSize = framesToBytes(1, chans, type); - mBufferSize = static_cast(sBufferLength*srate); - mBufferSize *= mFrameSize; + ChannelConfig chans; + SampleType type; - if (getLoudnessData) - mLoudnessAnalyzer.reset(new Sound_Loudness(sLoudnessFPS, mSampleRate, chans, type)); + try { + mDecoder->getInfo(&mSampleRate, &chans, &type); + mFormat = getALFormat(chans, type); + } + catch(std::exception &e) { + std::cerr<< "Failed to get stream info: "<close(); + mFrameSize = framesToBytes(1, chans, type); + mBufferSize = static_cast(sBufferLength*mSampleRate); + mBufferSize *= mFrameSize; + + if (getLoudnessData) + mLoudnessAnalyzer.reset(new Sound_Loudness(sLoudnessFPS, mSampleRate, chans, type)); + + mIsFinished = false; + return true; } bool OpenAL_SoundStream::isPlaying() @@ -366,7 +443,7 @@ bool OpenAL_SoundStream::isPlaying() ALint state; alGetSourcei(mSource, AL_SOURCE_STATE, &state); - throwALerror(); + getALError(); if(state == AL_PLAYING || state == AL_PAUSED) return true; @@ -389,7 +466,7 @@ double OpenAL_SoundStream::getStreamDelay() const d = (double)inqueue / (double)mSampleRate; } - throwALerror(); + getALError(); return d; } @@ -415,7 +492,7 @@ double OpenAL_SoundStream::getStreamOffset() const t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; } - throwALerror(); + getALError(); return t; } @@ -464,16 +541,16 @@ ALint OpenAL_SoundStream::refillQueue() ALint queued; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - if(!mIsFinished && (ALuint)queued < sNumBuffers) + if(!mIsFinished && (ALuint)queued < mBuffers.size()) { std::vector data(mBufferSize); - for(;!mIsFinished && (ALuint)queued < sNumBuffers;++queued) + for(;!mIsFinished && (ALuint)queued < mBuffers.size();++queued) { - size_t got = mDecoder->read(&data[0], data.size()); + size_t got = mDecoder->read(data.data(), data.size()); if(got < data.size()) { mIsFinished = true; - std::memset(&data[got], mSilence, data.size()-got); + std::fill(data.begin()+got, data.end(), mSilence); } if(got > 0) { @@ -481,9 +558,9 @@ ALint OpenAL_SoundStream::refillQueue() mLoudnessAnalyzer->analyzeLoudness(data); ALuint bufid = mBuffers[mCurrentBufIdx]; - alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate); + alBufferData(bufid, mFormat, data.data(), data.size(), mSampleRate); alSourceQueueBuffers(mSource, 1, &bufid); - mCurrentBufIdx = (mCurrentBufIdx+1) % sNumBuffers; + mCurrentBufIdx = (mCurrentBufIdx+1) % mBuffers.size(); } } } @@ -512,76 +589,270 @@ std::vector OpenAL_Output::enumerate() return devlist; } -void OpenAL_Output::init(const std::string &devname) +bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) { deinit(); + std::cout<< "Initializing OpenAL..." < attrs; + attrs.reserve(15); + if(ALC.SOFT_HRTF) { - const ALCchar *name = NULL; - if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT")) - name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER); - if(alcGetError(mDevice) != AL_NO_ERROR || !name) - name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER); - std::cout << "Opened \""<(maxmono+maxstereo, 256); + maxtotal = std::min(maxmono+maxstereo, 256); if (maxtotal == 0) // workaround for broken implementations maxtotal = 256; - for(size_t i = 0;i < maxtotal;i++) - { - ALuint src = 0; - alGenSources(1, &src); - throwALerror(); - mFreeSources.push_back(src); - } } - catch(std::exception &e) + for(size_t i = 0;i < maxtotal;i++) { - std::cout <<"Error: "<removeAll(); - for(size_t i = 0;i < mFreeSources.size();i++) - alDeleteSources(1, &mFreeSources[i]); + for(ALuint source : mFreeSources) + alDeleteSources(1, &source); mFreeSources.clear(); + if(mEffectSlot) + alDeleteAuxiliaryEffectSlots(1, &mEffectSlot); + mEffectSlot = 0; + if(mDefaultEffect) + alDeleteEffects(1, &mDefaultEffect); + mDefaultEffect = 0; + if(mWaterEffect) + alDeleteEffects(1, &mWaterEffect); + mWaterEffect = 0; + if(mWaterFilter) + alDeleteFilters(1, &mWaterFilter); + mWaterFilter = 0; + alcMakeContextCurrent(0); if(mContext) alcDestroyContext(mContext); @@ -596,15 +867,13 @@ void OpenAL_Output::deinit() std::vector OpenAL_Output::enumerateHrtf() { - if(!mDevice) - fail("Device not initialized"); - std::vector ret; - if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + + if(!mDevice || !ALC.SOFT_HRTF) return ret; LPALCGETSTRINGISOFT alcGetStringiSOFT = 0; - getFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); + getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); ALCint num_hrtf; alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); @@ -618,24 +887,27 @@ std::vector OpenAL_Output::enumerateHrtf() return ret; } -void OpenAL_Output::enableHrtf(const std::string &hrtfname, bool auto_enable) +void OpenAL_Output::setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) { - if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + if(!mDevice || !ALC.SOFT_HRTF) { std::cerr<< "HRTF extension not present" < attrs; + attrs.reserve(15); + attrs.push_back(ALC_HRTF_SOFT); - attrs.push_back(auto_enable ? ALC_DONT_CARE_SOFT : ALC_TRUE); + attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE : + hrtfmode == HrtfMode::Enable ? ALC_TRUE : + /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); if(!hrtfname.empty()) { ALCint index = -1; @@ -660,12 +932,12 @@ void OpenAL_Output::enableHrtf(const std::string &hrtfname, bool auto_enable) } } attrs.push_back(0); - alcResetDeviceSOFT(mDevice, &attrs[0]); + alcResetDeviceSOFT(mDevice, attrs.data()); ALCint hrtf_state; alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); if(!hrtf_state) - std::cerr<< "Failed to enable HRTF" < 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" < OpenAL_Output::loadSound(const std::string &fname) { - throwALerror(); - - DecoderPtr decoder = mManager.getDecoder(); - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - 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); - } + getALError(); std::vector data; - ChannelConfig chans; - SampleType type; ALenum format; int srate; - decoder->getInfo(&srate, &chans, &type); - format = getALFormat(chans, type); + try { + DecoderPtr decoder = mManager.getDecoder(); + // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. + 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->readAll(data); - decoder->close(); + ChannelConfig chans; + SampleType type; + decoder->getInfo(&srate, &chans, &type); + format = getALFormat(chans, type); + if(format) decoder->readAll(data); + } + catch(std::exception &e) { + std::cerr<< "Failed to load audio from "< maxdist*maxdist) gain = 0.0f; - if(useenv && mListenerEnv == Env_Underwater) + if(useenv) { - gain *= 0.9f; - pitch *= 0.7f; + if(mWaterFilter) + alSourcei(source, AL_DIRECT_FILTER, + (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL + ); + else if(mListenerEnv == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + if(mEffectSlot) + alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); + } + else + { + if(mWaterFilter) + alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); + if(mEffectSlot) + alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); } alSourcef(source, AL_GAIN, gain); @@ -827,7 +1117,7 @@ void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat m if((pos - mListenerPos).length2() > maxdist*maxdist) gain = 0.0f; } - if(useenv && mListenerEnv == Env_Underwater) + if(useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { gain *= 0.9f; pitch *= 0.7f; @@ -841,68 +1131,66 @@ void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat m } -void OpenAL_Output::playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset) +bool OpenAL_Output::playSound(Sound *sound, Sound_Handle data, float offset) { ALuint source; if(mFreeSources.empty()) - fail("No free sources"); + { + std::cerr<< "No free sources!" <getPosition(), sound->getRealVolume(), sound->getPitch(), - sound->getIsLooping(), sound->getUseEnv()); - alSourcef(source, AL_SEC_OFFSET, offset); - throwALerror(); + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), + sound->getIsLooping(), sound->getUseEnv()); + alSourcef(source, AL_SEC_OFFSET, offset); + if(getALError() != AL_NO_ERROR) + return false; - alSourcei(source, AL_BUFFER, GET_PTRID(data)); - alSourcePlay(source); - throwALerror(); - - mActiveSounds.push_back(sound); - } - catch(std::exception&) { - mFreeSources.push_back(source); - throw; - } + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcePlay(source); + if(getALError() != AL_NO_ERROR) + return false; + mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); + mActiveSounds.push_back(sound); + + return true; } -void OpenAL_Output::playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset) +bool OpenAL_Output::playSound3D(Sound *sound, Sound_Handle data, float offset) { ALuint source; if(mFreeSources.empty()) - fail("No free sources"); + { + std::cerr<< "No free sources!" <getPosition(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), - sound->getUseEnv()); - alSourcef(source, AL_SEC_OFFSET, offset); - throwALerror(); + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), + sound->getUseEnv()); + alSourcef(source, AL_SEC_OFFSET, offset); + if(getALError() != AL_NO_ERROR) + return false; - alSourcei(source, AL_BUFFER, GET_PTRID(data)); - alSourcePlay(source); - throwALerror(); - - mActiveSounds.push_back(sound); - } - catch(std::exception&) { - mFreeSources.push_back(source); - throw; - } + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcePlay(source); + if(getALError() != AL_NO_ERROR) + return false; + mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); + mActiveSounds.push_back(sound); + + return true; } -void OpenAL_Output::finishSound(MWBase::SoundPtr sound) +void OpenAL_Output::finishSound(Sound *sound) { if(!sound->mHandle) return; ALuint source = GET_PTRID(sound->mHandle); @@ -913,96 +1201,96 @@ void OpenAL_Output::finishSound(MWBase::SoundPtr sound) // the initial queue already played when it hasn't. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); + getALError(); mFreeSources.push_back(source); mActiveSounds.erase(std::find(mActiveSounds.begin(), mActiveSounds.end(), sound)); } -bool OpenAL_Output::isSoundPlaying(MWBase::SoundPtr sound) +bool OpenAL_Output::isSoundPlaying(Sound *sound) { if(!sound->mHandle) return false; ALuint source = GET_PTRID(sound->mHandle); - ALint state; + ALint state = AL_STOPPED; alGetSourcei(source, AL_SOURCE_STATE, &state); - throwALerror(); + getALError(); return state == AL_PLAYING || state == AL_PAUSED; } -void OpenAL_Output::updateSound(MWBase::SoundPtr sound) +void OpenAL_Output::updateSound(Sound *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()); + getALError(); } -void OpenAL_Output::streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) +bool OpenAL_Output::streamSound(DecoderPtr decoder, Stream *sound) { - OpenAL_SoundStream *stream = 0; - ALuint source; - if(mFreeSources.empty()) - fail("No free sources"); - source = mFreeSources.front(); - mFreeSources.pop_front(); + { + std::cerr<< "No free sources!" <getIsLooping()) std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; - try { - initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), - false, sound->getUseEnv()); - throwALerror(); + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), + false, sound->getUseEnv()); + if(getALError() != AL_NO_ERROR) + return false; - stream = new OpenAL_SoundStream(source, decoder); - mStreamThread->add(stream); - mActiveStreams.push_back(sound); - } - catch(std::exception&) { - mStreamThread->remove(stream); + OpenAL_SoundStream *stream = new OpenAL_SoundStream(source, std::move(decoder)); + if(!stream->init()) + { delete stream; - mFreeSources.push_back(source); - throw; + return false; } + mStreamThread->add(stream); + mFreeSources.pop_front(); sound->mHandle = stream; + mActiveStreams.push_back(sound); + return true; } -void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData) +bool OpenAL_Output::streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) { - OpenAL_SoundStream *stream = 0; - ALuint source; - if(mFreeSources.empty()) - fail("No free sources"); - source = mFreeSources.front(); - mFreeSources.pop_front(); + { + std::cerr<< "No free sources!" <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(); + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); + if(getALError() != AL_NO_ERROR) + return false; - stream = new OpenAL_SoundStream(source, decoder, getLoudnessData); - mStreamThread->add(stream); - mActiveStreams.push_back(sound); - } - catch(std::exception&) { - mStreamThread->remove(stream); + OpenAL_SoundStream *stream = new OpenAL_SoundStream(source, std::move(decoder)); + if(!stream->init(getLoudnessData)) + { delete stream; - mFreeSources.push_back(source); - throw; + return false; } + mStreamThread->add(stream); + mFreeSources.pop_front(); sound->mHandle = stream; + mActiveStreams.push_back(sound); + return true; } -void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound) +void OpenAL_Output::finishStream(Stream *sound) { if(!sound->mHandle) return; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); @@ -1016,6 +1304,7 @@ void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound) // the initial queue already played when it hasn't. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); + getALError(); mFreeSources.push_back(source); mActiveStreams.erase(std::find(mActiveStreams.begin(), mActiveStreams.end(), sound)); @@ -1023,14 +1312,14 @@ void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound) delete stream; } -double OpenAL_Output::getStreamDelay(MWBase::SoundStreamPtr sound) +double OpenAL_Output::getStreamDelay(Stream *sound) { if(!sound->mHandle) return 0.0; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); return stream->getStreamDelay(); } -double OpenAL_Output::getStreamOffset(MWBase::SoundStreamPtr sound) +double OpenAL_Output::getStreamOffset(Stream *sound) { if(!sound->mHandle) return 0.0; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); @@ -1038,7 +1327,7 @@ double OpenAL_Output::getStreamOffset(MWBase::SoundStreamPtr sound) return stream->getStreamOffset(); } -float OpenAL_Output::getStreamLoudness(MWBase::SoundStreamPtr sound) +float OpenAL_Output::getStreamLoudness(Stream *sound) { if(!sound->mHandle) return 0.0; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); @@ -1046,7 +1335,7 @@ float OpenAL_Output::getStreamLoudness(MWBase::SoundStreamPtr sound) return stream->getCurrentLoudness(); } -bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound) +bool OpenAL_Output::isStreamPlaying(Stream *sound) { if(!sound->mHandle) return false; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); @@ -1054,7 +1343,7 @@ bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound) return stream->isPlaying(); } -void OpenAL_Output::updateStream(MWBase::SoundStreamPtr sound) +void OpenAL_Output::updateStream(Stream *sound) { if(!sound->mHandle) return; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); @@ -1062,6 +1351,7 @@ void OpenAL_Output::updateStream(MWBase::SoundStreamPtr sound) updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); + getALError(); } @@ -1086,7 +1376,37 @@ void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdi }; alListenerfv(AL_POSITION, pos.ptr()); alListenerfv(AL_ORIENTATION, orient); - throwALerror(); + + if(env != mListenerEnv) + { + // Speed of sound in water is 1484m/s, and in air is 343.3m/s (roughly) + alSpeedOfSound(((env == Env_Underwater) ? 1484.0f : 343.3f) * UnitsPerMeter); + + // Update active sources with the environment's direct filter + if(mWaterFilter) + { + ALuint filter = (env == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL; + for(Sound *sound : mActiveSounds) + { + if(sound->getUseEnv()) + alSourcei(GET_PTRID(sound->mHandle), AL_DIRECT_FILTER, filter); + } + for(Stream *sound : mActiveStreams) + { + if(sound->getUseEnv()) + alSourcei( + reinterpret_cast(sound->mHandle)->mSource, + AL_DIRECT_FILTER, filter + ); + } + } + // Update the environment effect + if(mEffectSlot) + alAuxiliaryEffectSloti(mEffectSlot, AL_EFFECTSLOT_EFFECT, + (env == Env_Underwater) ? mWaterEffect : mDefaultEffect + ); + } + getALError(); } mListenerPos = pos; @@ -1097,50 +1417,46 @@ void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdi void OpenAL_Output::pauseSounds(int types) { std::vector sources; - SoundVec::const_iterator sound = mActiveSounds.begin(); - for(;sound != mActiveSounds.end();++sound) + for(Sound *sound : mActiveSounds) { - if(*sound && (*sound)->mHandle && ((*sound)->getPlayType()&types)) - sources.push_back(GET_PTRID((*sound)->mHandle)); + if((types&sound->getPlayType())) + sources.push_back(GET_PTRID(sound->mHandle)); } - StreamVec::const_iterator stream = mActiveStreams.begin(); - for(;stream != mActiveStreams.end();++stream) + for(Stream *sound : mActiveStreams) { - if(*stream && (*stream)->mHandle && ((*stream)->getPlayType()&types)) + if((types&sound->getPlayType())) { - OpenAL_SoundStream *strm = reinterpret_cast((*stream)->mHandle); - sources.push_back(strm->mSource); + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + sources.push_back(stream->mSource); } } if(!sources.empty()) { - alSourcePausev(sources.size(), &sources[0]); - throwALerror(); + alSourcePausev(sources.size(), sources.data()); + getALError(); } } void OpenAL_Output::resumeSounds(int types) { std::vector sources; - SoundVec::const_iterator sound = mActiveSounds.begin(); - for(;sound != mActiveSounds.end();++sound) + for(Sound *sound : mActiveSounds) { - if(*sound && (*sound)->mHandle && ((*sound)->getPlayType()&types)) - sources.push_back(GET_PTRID((*sound)->mHandle)); + if((types&sound->getPlayType())) + sources.push_back(GET_PTRID(sound->mHandle)); } - StreamVec::const_iterator stream = mActiveStreams.begin(); - for(;stream != mActiveStreams.end();++stream) + for(Stream *sound : mActiveStreams) { - if(*stream && (*stream)->mHandle && ((*stream)->getPlayType()&types)) + if((types&sound->getPlayType())) { - OpenAL_SoundStream *strm = reinterpret_cast((*stream)->mHandle); - sources.push_back(strm->mSource); + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + sources.push_back(stream->mSource); } } if(!sources.empty()) { - alSourcePlayv(sources.size(), &sources[0]); - throwALerror(); + alSourcePlayv(sources.size(), sources.data()); + getALError(); } } @@ -1148,6 +1464,7 @@ void OpenAL_Output::resumeSounds(int types) OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0) , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) + , mWaterFilter(0), mWaterEffect(0), mDefaultEffect(0), mEffectSlot(0) , mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index ea3f393ff..afc869733 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -8,6 +8,7 @@ #include "alc.h" #include "al.h" +#include "alext.h" #include "sound_output.hpp" @@ -15,23 +16,37 @@ namespace MWSound { class SoundManager; class Sound; + class Stream; class OpenAL_Output : public Sound_Output { ALCdevice *mDevice; ALCcontext *mContext; + struct { + bool EXT_EFX : 1; + bool SOFT_HRTF : 1; + } ALC; + struct { + bool SOFT_source_spatialize : 1; + } AL; + typedef std::deque IDDq; IDDq mFreeSources; - typedef std::vector SoundVec; + typedef std::vector SoundVec; SoundVec mActiveSounds; - typedef std::vector StreamVec; + typedef std::vector StreamVec; StreamVec mActiveStreams; osg::Vec3f mListenerPos; Environment mListenerEnv; + ALuint mWaterFilter; + ALuint mWaterEffect; + ALuint mDefaultEffect; + ALuint mEffectSlot; + struct StreamThread; std::unique_ptr mStreamThread; @@ -45,31 +60,29 @@ namespace MWSound public: virtual std::vector enumerate(); - virtual void init(const std::string &devname=std::string()); + virtual bool init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode); virtual void deinit(); 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, bool getLoudnessData); - virtual void finishStream(MWBase::SoundStreamPtr sound); - virtual double getStreamDelay(MWBase::SoundStreamPtr sound); - virtual double getStreamOffset(MWBase::SoundStreamPtr sound); - virtual float getStreamLoudness(MWBase::SoundStreamPtr sound); - virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound); - virtual void updateStream(MWBase::SoundStreamPtr sound); + virtual void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode); + + virtual std::pair loadSound(const std::string &fname); + virtual size_t unloadSound(Sound_Handle data); + + virtual bool playSound(Sound *sound, Sound_Handle data, float offset); + virtual bool playSound3D(Sound *sound, Sound_Handle data, float offset); + virtual void finishSound(Sound *sound); + virtual bool isSoundPlaying(Sound *sound); + virtual void updateSound(Sound *sound); + + virtual bool streamSound(DecoderPtr decoder, Stream *sound); + virtual bool streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData); + virtual void finishStream(Stream *sound); + virtual double getStreamDelay(Stream *sound); + virtual double getStreamOffset(Stream *sound); + virtual float getStreamLoudness(Stream *sound); + virtual bool isStreamPlaying(Stream *sound); + virtual void updateStream(Stream *sound); virtual void startUpdate(); virtual void finishUpdate(); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 8c663fb1e..7324b6747 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,9 +7,14 @@ namespace MWSound { - class Sound { - Sound& operator=(const Sound &rhs); - Sound(const Sound &rhs); + // For testing individual PlayMode flags + inline int operator&(int a, PlayMode b) { return a & static_cast(b); } + inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } + + class SoundBase { + SoundBase& operator=(const SoundBase&) = delete; + SoundBase(const SoundBase&) = delete; + SoundBase(SoundBase&&) = delete; osg::Vec3f mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ @@ -47,37 +52,62 @@ namespace MWSound 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; } + MWSound::Type getPlayType() const + { return static_cast(mFlags&MWSound::Type::Mask); } + bool getUseEnv() const { return !(mFlags&MWSound::PlayMode::NoEnv); } + bool getIsLooping() const { return mFlags&MWSound::PlayMode::Loop; } + bool getDistanceCull() const { return mFlags&MWSound::PlayMode::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.0f), mHandle(0) - { } - 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) + void init(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.0f; + mHandle = nullptr; + } + + void init(float vol, float basevol, float pitch, int flags) + { + mPos = osg::Vec3f(0.0f, 0.0f, 0.0f); + mVolume = vol; + mBaseVolume = basevol; + mPitch = pitch; + mMinDistance = 1.0f; + mMaxDistance = 1000.0f; + mFlags = flags; + mFadeOutTime = 0.0f; + mHandle = nullptr; + } + + SoundBase() + : mPos(0.0f, 0.0f, 0.0f), mVolume(1.0f), mBaseVolume(1.0f), mPitch(1.0f) + , mMinDistance(1.0f), mMaxDistance(1000.0f), mFlags(0), mFadeOutTime(0.0f) + , mHandle(nullptr) { } }; - // 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); + class Sound : public SoundBase { + Sound& operator=(const Sound&) = delete; + Sound(const Sound&) = delete; + Sound(Sound&&) = delete; 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) - { } + Sound() { } + }; + + class Stream : public SoundBase { + Stream& operator=(const Stream&) = delete; + Stream(const Stream&) = delete; + Stream(Stream&&) = delete; + + public: + Stream() { } }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index e34d888ee..871562dc0 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -12,42 +12,47 @@ namespace MWSound class SoundManager; struct Sound_Decoder; class Sound; + class Stream; // 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; + enum class HrtfMode { + Disable, + Enable, + Auto + }; + class Sound_Output { SoundManager &mManager; virtual std::vector enumerate() = 0; - virtual void init(const std::string &devname=std::string()) = 0; + virtual bool init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) = 0; virtual void deinit() = 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, bool getLoudnessData) = 0; - virtual void finishStream(MWBase::SoundStreamPtr sound) = 0; - virtual double getStreamDelay(MWBase::SoundStreamPtr sound) = 0; - virtual double getStreamOffset(MWBase::SoundStreamPtr sound) = 0; - virtual float getStreamLoudness(MWBase::SoundStreamPtr sound) = 0; - virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound) = 0; - virtual void updateStream(MWBase::SoundStreamPtr sound) = 0; + virtual void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) = 0; + + virtual std::pair loadSound(const std::string &fname) = 0; + virtual size_t unloadSound(Sound_Handle data) = 0; + + virtual bool playSound(Sound *sound, Sound_Handle data, float offset) = 0; + virtual bool playSound3D(Sound *sound, Sound_Handle data, float offset) = 0; + virtual void finishSound(Sound *sound) = 0; + virtual bool isSoundPlaying(Sound *sound) = 0; + virtual void updateSound(Sound *sound) = 0; + + virtual bool streamSound(DecoderPtr decoder, Stream *sound) = 0; + virtual bool streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) = 0; + virtual void finishStream(Stream *sound) = 0; + virtual double getStreamDelay(Stream *sound) = 0; + virtual double getStreamOffset(Stream *sound) = 0; + virtual float getStreamLoudness(Stream *sound) = 0; + virtual bool isStreamPlaying(Stream *sound) = 0; + virtual void updateStream(Stream *sound) = 0; virtual void startUpdate() = 0; virtual void finishUpdate() = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 15b95b233..a798d350c 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -26,15 +26,14 @@ #include "sound.hpp" #include "openal_output.hpp" -#define SOUND_OUT "OpenAL" #include "ffmpeg_decoder.hpp" -#ifndef SOUND_IN -#define SOUND_IN "FFmpeg" -#endif namespace MWSound { + // For combining PlayMode and Type flags + inline int operator|(PlayMode a, Type b) { return static_cast(a) | static_cast(b); } + SoundManager::SoundManager(const VFS::Manager* vfs, const std::map& fallbackMap, bool useSound) : mVFS(vfs) , mFallback(fallbackMap) @@ -46,11 +45,16 @@ namespace MWSound , mFootstepsVolume(1.0f) , mSoundBuffers(new SoundBufferList::element_type()) , mBufferCacheSize(0) + , mSounds(new std::deque()) + , mStreams(new std::deque()) + , mMusic(nullptr) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) , mListenerUp(0,0,1) , mPausedSoundTypes(0) + , mUnderwaterSound(nullptr) + , mNearWaterSound(nullptr) { mMasterVolume = Settings::Manager::getFloat("master volume", "Sound"); mMasterVolume = std::min(std::max(mMasterVolume, 0.0f), 1.0f); @@ -67,8 +71,8 @@ namespace MWSound mNearWaterPoints = mFallback.getFallbackInt("Water_NearWaterPoints"); mNearWaterIndoorTolerance = mFallback.getFallbackFloat("Water_NearWaterIndoorTolerance"); mNearWaterOutdoorTolerance = mFallback.getFallbackFloat("Water_NearWaterOutdoorTolerance"); - mNearWaterIndoorID = mFallback.getFallbackString("Water_NearWaterIndoorID"); - mNearWaterOutdoorID = mFallback.getFallbackString("Water_NearWaterOutdoorID"); + mNearWaterIndoorID = Misc::StringUtils::lowerCase(mFallback.getFallbackString("Water_NearWaterIndoorID")); + mNearWaterOutdoorID = Misc::StringUtils::lowerCase(mFallback.getFallbackString("Water_NearWaterOutdoorID")); mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); @@ -76,59 +80,47 @@ namespace MWSound mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); if(!useSound) + { + std::cout<< "Sound disabled." < 0 ? HrtfMode::Enable : HrtfMode::Disable; - std::cout << "Sound output: " << SOUND_OUT << std::endl; - std::cout << "Sound decoder: " << SOUND_IN << std::endl; - - 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) { - if(devname.empty()) - throw; - std::cerr <<"Failed to open device \""<init(); - Settings::Manager::setString("device", "Sound", ""); - } + std::string devname = Settings::Manager::getString("device", "Sound"); + if(!mOutput->init(devname, hrtfname, hrtfmode)) + { + std::cerr<< "Failed to initialize audio output, sound disabled" <enumerateHrtf(); - if(!names.empty()) - { - std::cout <<"Enumerated HRTF names:"<< std::endl; - for(size_t i = 0;i < names.size();i++) - std::cout <<" "< names = mOutput->enumerate(); + std::cout <<"Enumerated output devices:\n"; + for(const std::string &name : names) + std::cout <<" "<disableHrtf(); - else if(!hrtfname.empty()) - mOutput->enableHrtf(hrtfname, hrtfstate<0); - } - catch(std::exception &e) { - std::cout <<"Sound init failed: "<enumerateHrtf(); + if(!names.empty()) + { + std::cout<< "Enumerated HRTF names:\n"; + for(const std::string &name : names) + std::cout <<" "<begin(); - for(;sfxiter != mSoundBuffers->end();++sfxiter) + for(Sound_Buffer &sfx : *mSoundBuffers) { - if(sfxiter->mHandle) - mOutput->unloadSound(sfxiter->mHandle); - sfxiter->mHandle = 0; + if(sfx.mHandle) + mOutput->unloadSound(sfx.mHandle); + sfx.mHandle = 0; } mUnusedBuffers.clear(); mOutput.reset(); @@ -181,30 +173,53 @@ namespace MWSound 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; + if(snd != mBufferNameMap.end()) + { + Sound_Buffer *sfx = snd->second; + if(sfx->mHandle) return sfx; + } + return nullptr; } // 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) { +#ifdef __GNUC__ +#define LIKELY(x) __builtin_expect((bool)(x), true) +#define UNLIKELY(x) __builtin_expect((bool)(x), false) +#else +#define LIKELY(x) (bool)(x) +#define UNLIKELY(x) (bool)(x) +#endif + if(UNLIKELY(mBufferNameMap.empty())) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + for(const ESM::Sound &sound : world->getStore().get()) + insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound); + } + Sound_Buffer *sfx; NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(snd != mBufferNameMap.end()) + if(LIKELY(snd != mBufferNameMap.end())) sfx = snd->second; else { MWBase::World *world = MWBase::Environment::get().getWorld(); - const ESM::Sound *sound = world->getStore().get().find(soundId); + const ESM::Sound *sound = world->getStore().get().search(soundId); + if(!sound) return nullptr; sfx = insertSound(soundId, sound); } +#undef LIKELY +#undef UNLIKELY if(!sfx->mHandle) { - sfx->mHandle = mOutput->loadSound(sfx->mResourceName); - mBufferCacheSize += mOutput->getSoundDataSize(sfx->mHandle); + size_t size; + std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName); + if(!sfx->mHandle) return nullptr; + mBufferCacheSize += size; if(mBufferCacheSize > mBufferCacheMax) { do { @@ -215,8 +230,8 @@ namespace MWSound } Sound_Buffer *unused = mUnusedBuffers.back(); - mBufferCacheSize -= mOutput->getSoundDataSize(unused->mHandle); - mOutput->unloadSound(unused->mHandle); + size = mOutput->unloadSound(unused->mHandle); + mBufferCacheSize -= size; unused->mHandle = 0; mUnusedBuffers.pop_back(); @@ -246,7 +261,39 @@ namespace MWSound return decoder; } - MWBase::SoundStreamPtr SoundManager::playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal) + Sound *SoundManager::getSoundRef() + { + Sound *ret; + if(!mUnusedSounds.empty()) + { + ret = mUnusedSounds.back(); + mUnusedSounds.pop_back(); + } + else + { + mSounds->emplace_back(); + ret = &mSounds->back(); + } + return ret; + } + + Stream *SoundManager::getStreamRef() + { + Stream *ret; + if(!mUnusedStreams.empty()) + { + ret = mUnusedStreams.back(); + mUnusedStreams.pop_back(); + } + else + { + mStreams->emplace_back(); + ret = &mStreams->back(); + } + return ret; + } + + Stream *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(); @@ -256,43 +303,48 @@ namespace MWSound 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); + bool played; + float basevol = volumeFromType(Type::Voice); + Stream *sound = getStreamRef(); if(playlocal) { - sound.reset(new Stream(1.0f, basevol, 1.0f, Play_NoEnv|Play_TypeVoice|Play_2D)); - mOutput->streamSound(decoder, sound); + sound->init(1.0f, basevol, 1.0f, PlayMode::NoEnv|Type::Voice|Play_2D); + played = 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, true); + sound->init(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance, + PlayMode::Normal|Type::Voice|Play_3D); + played = mOutput->streamSound3D(decoder, sound, true); + } + if(!played) + { + mUnusedStreams.push_back(sound); + return nullptr; } return sound; } // Gets the combined volume settings for the given sound type - float SoundManager::volumeFromType(PlayType type) const + float SoundManager::volumeFromType(Type type) const { float volume = mMasterVolume; switch(type) { - case Play_TypeSfx: + case Type::Sfx: volume *= mSFXVolume; break; - case Play_TypeVoice: + case Type::Voice: volume *= mVoiceVolume; break; - case Play_TypeFoot: + case Type::Foot: volume *= mFootstepsVolume; break; - case Play_TypeMusic: + case Type::Music: volume *= mMusicVolume; break; - case Play_TypeMask: - break; - default: + case Type::Movie: + case Type::Mask: break; } return volume; @@ -301,8 +353,11 @@ namespace MWSound void SoundManager::stopMusic() { if(mMusic) + { mOutput->finishStream(mMusic); - mMusic.reset(); + mUnusedStreams.push_back(mMusic); + mMusic = nullptr; + } } void SoundManager::streamMusicFull(const std::string& filename) @@ -311,20 +366,16 @@ namespace MWSound return; std::cout <<"Playing "<open(filename); + stopMusic(); - 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) { - std::cout << "Music Error: " << e.what() << "\n"; - mMusic.reset(); - } + DecoderPtr decoder = getDecoder(); + decoder->open(filename); + + mMusic = getStreamRef(); + mMusic->init(1.0f, volumeFromType(Type::Music), 1.0f, + PlayMode::NoEnv|Type::Music|Play_2D); + mOutput->streamSound(decoder, mMusic); } void SoundManager::advanceMusic(const std::string& filename) @@ -340,11 +391,6 @@ namespace MWSound mMusic->setFadeout(0.5f); } - void SoundManager::streamMusic(const std::string& filename) - { - advanceMusic("Music/"+filename); - } - void SoundManager::startRandomTitle() { std::vector filelist; @@ -395,6 +441,12 @@ namespace MWSound tracklist.pop_back(); } + + void SoundManager::streamMusic(const std::string& filename) + { + advanceMusic("Music/"+filename); + } + bool SoundManager::isMusicPlaying() { return mMusic && mOutput->isStreamPlaying(mMusic); @@ -411,31 +463,20 @@ namespace MWSound { if(!mOutput->isInitialized()) return; - try - { - std::string voicefile = "Sound/"+filename; - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + std::string voicefile = "Sound/"+filename; - MWBase::World *world = MWBase::Environment::get().getWorld(); - const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); + mVFS->normalizeFilename(voicefile); + DecoderPtr decoder = loadVoice(voicefile); - SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr); - if (oldIt != mActiveSaySounds.end()) - { - mOutput->finishStream(oldIt->second); - mActiveSaySounds.erase(oldIt); - } + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + stopSay(ptr); + Stream *sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + if(!sound) return; - mActiveSaySounds.insert(std::make_pair(ptr, sound)); - } - catch(std::exception &e) - { - std::cout <<"Sound Error: "<second; + Stream *sound = snditer->second; return mOutput->getStreamLoudness(sound); } @@ -454,27 +495,17 @@ namespace MWSound { if(!mOutput->isInitialized()) return; - try - { - std::string voicefile = "Sound/"+filename; - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + std::string voicefile = "Sound/"+filename; - SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr()); - if (oldIt != mActiveSaySounds.end()) - { - mOutput->finishStream(oldIt->second); - mActiveSaySounds.erase(oldIt); - } + mVFS->normalizeFilename(voicefile); + DecoderPtr decoder = loadVoice(voicefile); - mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(), - playVoice(decoder, osg::Vec3f(), true))); - } - catch(std::exception &e) - { - std::cout <<"Sound Error: "<finishStream(snditer->second); + mUnusedStreams.push_back(snditer->second); mActiveSaySounds.erase(snditer); } } - MWBase::SoundStreamPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) + Stream *SoundManager::playTrack(const DecoderPtr& decoder, Type type) { - MWBase::SoundStreamPtr track; if(!mOutput->isInitialized()) - return track; - try - { - track.reset(new Stream(1.0f, volumeFromType(type), 1.0f, Play_NoEnv|type|Play_2D)); - mOutput->streamSound(decoder, track); + return nullptr; - TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), track); - mActiveTracks.insert(iter, track); - } - catch(std::exception &e) + Stream *track = getStreamRef(); + track->init(1.0f, volumeFromType(type), 1.0f, PlayMode::NoEnv|type|Play_2D); + if(!mOutput->streamSound(decoder, track)) { - std::cout <<"Sound Error: "<finishStream(stream); TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), stream); if(iter != mActiveTracks.end() && *iter == stream) mActiveTracks.erase(iter); + mUnusedStreams.push_back(stream); } - double SoundManager::getTrackTimeDelay(MWBase::SoundStreamPtr stream) + double SoundManager::getTrackTimeDelay(Stream *stream) { return mOutput->getStreamDelay(stream); } - MWBase::SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, PlayType type, PlayMode mode, float offset) + Sound *SoundManager::playSound(const std::string& soundId, float volume, float pitch, Type type, PlayMode mode, float offset) { - MWBase::SoundPtr sound; if(!mOutput->isInitialized()) - return sound; - try - { - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - float basevol = volumeFromType(type); + return nullptr; - 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)); + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + if(!sfx) return nullptr; + + Sound *sound = getSoundRef(); + sound->init(volume * sfx->mVolume, volumeFromType(type), pitch, mode|type|Play_2D); + if(!mOutput->playSound(sound, sfx->mHandle, offset)) + { + mUnusedSounds.push_back(sound); + return nullptr; } - catch(std::exception&) + + if(sfx->mUses++ == 0) { - //std::cout <<"Sound Error: "<isInitialized()) - return sound; - try - { - // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - float basevol = volumeFromType(type); - const ESM::Position &pos = ptr.getRefData().getPosition(); - const osg::Vec3f objpos(pos.asVec3()); + return nullptr; - if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000) - return MWBase::SoundPtr(); + // Look up the sound in the ESM data + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + if(!sfx) return nullptr; - // Only one copy of given sound can be played at time on ptr, so stop previous copy - stopSound3D(ptr, soundId); + const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3()); + if((mode&PlayMode::RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000) + return nullptr; - 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 - { - 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)); + // Only one copy of given sound can be played at time on ptr, so stop previous copy + stopSound3D(ptr, soundId); + + bool played; + Sound *sound = getSoundRef(); + if(!(mode&PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer()) + { + sound->init(volume * sfx->mVolume, volumeFromType(type), pitch, mode|type|Play_2D); + played = mOutput->playSound(sound, sfx->mHandle, offset); + } + else + { + sound->init(objpos, volume * sfx->mVolume, volumeFromType(type), pitch, + sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D); + played = mOutput->playSound3D(sound, sfx->mHandle, offset); + } + if(!played) + { + mUnusedSounds.push_back(sound); + return nullptr; } - catch(std::exception&) + + if(sfx->mUses++ == 0) { - //std::cout <<"Sound Error: "<isInitialized()) - return sound; - try - { - // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - float basevol = volumeFromType(type); + return nullptr; - 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)); + // Look up the sound in the ESM data + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + if(!sfx) return nullptr; + + Sound *sound = getSoundRef(); + sound->init(initialPos, volume * sfx->mVolume, volumeFromType(type), pitch, + sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D); + if(!mOutput->playSound3D(sound, sfx->mHandle, offset)) + { + mUnusedSounds.push_back(sound); + return nullptr; } - catch(std::exception &) + + if(sfx->mUses++ == 0) { - //std::cout <<"Sound Error: "<finishSound(sound); } @@ -652,11 +681,10 @@ namespace MWSound if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) + for(SoundBufferRefPair &snd : snditer->second) { - if(sndidx->second == sfx) - mOutput->finishSound(sndidx->first); + if(snd.second == sfx) + mOutput->finishSound(snd.first); } } } @@ -666,37 +694,28 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) - mOutput->finishSound(sndidx->first); + for(SoundBufferRefPair &snd : snditer->second) + mOutput->finishSound(snd.first); } + SaySoundMap::iterator sayiter = mActiveSaySounds.find(ptr); + if(sayiter != mActiveSaySounds.end()) + mOutput->finishStream(sayiter->second); } void SoundManager::stopSound(const MWWorld::CellStore *cell) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + for(SoundMap::value_type &snd : mActiveSounds) { - if(snditer->first != MWWorld::ConstPtr() && - snditer->first != MWMechanics::getPlayer() && - snditer->first.getCell() == cell) + if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) { - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) - mOutput->finishSound(sndidx->first); + for(SoundBufferRefPair &sndbuf : snd.second) + mOutput->finishSound(sndbuf.first); } - ++snditer; } - SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); - while(sayiter != mActiveSaySounds.end()) + for(SaySoundMap::value_type &snd : mActiveSaySounds) { - if(sayiter->first != MWWorld::ConstPtr() && - sayiter->first != MWMechanics::getPlayer() && - sayiter->first.getCell() == cell) - { - mOutput->finishStream(sayiter->second); - } - ++sayiter; + if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) + mOutput->finishStream(snd.second); } } @@ -706,11 +725,10 @@ namespace MWSound if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) + for(SoundBufferRefPair &sndbuf : snditer->second) { - if(sndidx->second == sfx) - mOutput->finishSound(sndidx->first); + if(sndbuf.second == sfx) + mOutput->finishSound(sndbuf.first); } } } @@ -722,11 +740,10 @@ namespace MWSound if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) + for(SoundBufferRefPair &sndbuf : snditer->second) { - if(sndidx->second == sfx) - sndidx->first->setFadeout(duration); + if(sndbuf.second == sfx) + sndbuf.first->setFadeout(duration); } } } @@ -737,12 +754,10 @@ namespace MWSound 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 std::find_if(snditer->second.cbegin(), snditer->second.cend(), + [this,sfx](const SoundBufferRefPair &snd) -> bool + { return snd.second == sfx && mOutput->isSoundPlaying(snd.first); } + ) != snditer->second.cend(); } return false; } @@ -752,7 +767,7 @@ namespace MWSound { if(mOutput->isInitialized()) { - types &= Play_TypeMask; + types = types & Type::Mask; mOutput->pauseSounds(types); mPausedSoundTypes |= types; } @@ -762,7 +777,7 @@ namespace MWSound { if(mOutput->isInitialized()) { - types &= types&Play_TypeMask&mPausedSoundTypes; + types = types & Type::Mask & mPausedSoundTypes; mOutput->resumeSounds(types); mPausedSoundTypes &= ~types; } @@ -799,15 +814,10 @@ namespace MWSound if(regn == NULL) return; - std::vector::const_iterator soundIter; if(total == 0) { - soundIter = regn->mSoundList.begin(); - while(soundIter != regn->mSoundList.end()) - { - total += (int)soundIter->mChance; - ++soundIter; - } + for(const ESM::Region::SoundRef &sndref : regn->mSoundList) + total += (int)sndref.mChance; if(total == 0) return; } @@ -815,32 +825,31 @@ namespace MWSound int r = Misc::Rng::rollDice(total); int pos = 0; - soundIter = regn->mSoundList.begin(); - while(soundIter != regn->mSoundList.end()) + for(const ESM::Region::SoundRef &sndref : regn->mSoundList) { - if(r - pos < soundIter->mChance) + if(r - pos < sndref.mChance) { - playSound(soundIter->mSound.toString(), 1.0f, 1.0f); + playSound(sndref.mSound.toString(), 1.0f, 1.0f); break; } - pos += soundIter->mChance; - - ++soundIter; + pos += sndref.mChance; } } void SoundManager::updateWaterSound(float /*duration*/) { + static const ESM::Cell *LastCell; MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::ConstPtr player = world->getPlayerPtr(); osg::Vec3f pos = player.getRefData().getPosition().asVec3(); + const ESM::Cell *curcell = player.getCell()->getCell(); float volume = 0.0f; const std::string& soundId = player.getCell()->isExterior() ? mNearWaterOutdoorID : mNearWaterIndoorID; if (!mListenerUnderwater) { - if (player.getCell()->getCell()->hasWater()) + if (curcell->hasWater()) { float dist = std::abs(player.getCell()->getWaterLevel() - pos.z()); @@ -885,38 +894,43 @@ namespace MWSound if (volume == 0.0f) { mOutput->finishSound(mNearWaterSound); - mNearWaterSound.reset(); + mNearWaterSound = nullptr; } else { bool soundIdChanged = false; - Sound_Buffer* sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); - - for (SoundMap::const_iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer) + Sound_Buffer *sfx = lookupSound(soundId); + if(LastCell != curcell) { - for (SoundBufferRefPairList::const_iterator pairiter = snditer->second.begin(); pairiter != snditer->second.end(); ++pairiter) + LastCell = curcell; + SoundMap::const_iterator snditer = mActiveSounds.find(MWWorld::Ptr()); + if(snditer != mActiveSounds.end()) { - if (pairiter->first == mNearWaterSound) - { - if (pairiter->second != sfx) - soundIdChanged = true; - break; - } + SoundBufferRefPairList::const_iterator pairiter = std::find_if( + snditer->second.begin(), snditer->second.end(), + [this](const SoundBufferRefPairList::value_type &item) -> bool + { return mNearWaterSound == item.first; } + ); + if (pairiter != snditer->second.end() && pairiter->second != sfx) + soundIdChanged = true; } } - if (soundIdChanged) + if(soundIdChanged) { mOutput->finishSound(mNearWaterSound); - mNearWaterSound = playSound(soundId, volume, 1.0f, Play_TypeSfx, Play_Loop); + mNearWaterSound = playSound(soundId, volume, 1.0f, Type::Sfx, PlayMode::Loop); } else if (sfx) mNearWaterSound->setVolume(volume * sfx->mVolume); } } else if (volume > 0.0f) - mNearWaterSound = playSound(soundId, volume, 1.0f, Play_TypeSfx, Play_Loop); + { + LastCell = curcell; + mNearWaterSound = playSound(soundId, volume, 1.0f, Type::Sfx, PlayMode::Loop); + } } void SoundManager::updateSounds(float duration) @@ -939,7 +953,7 @@ namespace MWSound else if(mUnderwaterSound) { mOutput->finishSound(mUnderwaterSound); - mUnderwaterSound.reset(); + mUnderwaterSound = nullptr; } mOutput->startUpdate(); @@ -956,11 +970,14 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { + MWWorld::ConstPtr ptr = snditer->first; SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); while(sndidx != snditer->second.end()) { - MWWorld::ConstPtr ptr = snditer->first; - MWBase::SoundPtr sound = sndidx->first; + Sound *sound; + Sound_Buffer *sfx; + + std::tie(sound, sfx) = *sndidx; if(!ptr.isEmpty() && sound->getIs3D()) { const ESM::Position &pos = ptr.getRefData().getPosition(); @@ -977,7 +994,11 @@ namespace MWSound if(!mOutput->isSoundPlaying(sound)) { mOutput->finishSound(sound); - Sound_Buffer *sfx = sndidx->second; + mUnusedSounds.push_back(sound); + if(sound == mUnderwaterSound) + mUnderwaterSound = nullptr; + if(sound == mNearWaterSound) + mNearWaterSound = nullptr; if(sfx->mUses-- == 1) mUnusedBuffers.push_front(sfx); sndidx = snditer->second.erase(sndidx); @@ -991,7 +1012,7 @@ namespace MWSound } } if(snditer->second.empty()) - mActiveSounds.erase(snditer++); + snditer = mActiveSounds.erase(snditer); else ++snditer; } @@ -1000,7 +1021,7 @@ namespace MWSound while(sayiter != mActiveSaySounds.end()) { MWWorld::ConstPtr ptr = sayiter->first; - MWBase::SoundStreamPtr sound = sayiter->second; + Stream *sound = sayiter->second; if(!ptr.isEmpty() && sound->getIs3D()) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -1017,6 +1038,7 @@ namespace MWSound if(!mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); + mUnusedStreams.push_back(sound); mActiveSaySounds.erase(sayiter++); } else @@ -1031,7 +1053,7 @@ namespace MWSound TrackList::iterator trkiter = mActiveTracks.begin(); for(;trkiter != mActiveTracks.end();++trkiter) { - MWBase::SoundStreamPtr sound = *trkiter; + Stream *sound = *trkiter; if(!mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); @@ -1049,8 +1071,8 @@ namespace MWSound if(mListenerUnderwater) { // Play underwater sound (after updating sounds) - if(!(mUnderwaterSound && mOutput->isSoundPlaying(mUnderwaterSound))) - mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv); + if(!mUnderwaterSound) + mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Type::Sfx, PlayMode::LoopNoEnv); } mOutput->finishUpdate(); } @@ -1099,28 +1121,23 @@ namespace MWSound if(!mOutput->isInitialized()) return; mOutput->startUpdate(); - SoundMap::iterator snditer = mActiveSounds.begin(); - for(;snditer != mActiveSounds.end();++snditer) + for(SoundMap::value_type &snd : mActiveSounds) { - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) + for(SoundBufferRefPair &sndbuf : snd.second) { - MWBase::SoundPtr sound = sndidx->first; + Sound *sound = sndbuf.first; sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateSound(sound); } } - SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); - for(;sayiter != mActiveSaySounds.end();++sayiter) + for(SaySoundMap::value_type &snd : mActiveSaySounds) { - MWBase::SoundStreamPtr sound = sayiter->second; + Stream *sound = snd.second; sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } - TrackList::iterator trkiter = mActiveTracks.begin(); - for(;trkiter != mActiveTracks.end();++trkiter) + for(Stream *sound : mActiveTracks) { - MWBase::SoundStreamPtr sound = *trkiter; sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } @@ -1146,16 +1163,16 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.find(old); if(snditer != mActiveSounds.end()) { - SoundBufferRefPairList sndlist = snditer->second; + SoundBufferRefPairList sndlist = std::move(snditer->second); mActiveSounds.erase(snditer); - mActiveSounds[updated] = sndlist; + mActiveSounds.emplace(updated, std::move(sndlist)); } SaySoundMap::iterator sayiter = mActiveSaySounds.find(old); if(sayiter != mActiveSaySounds.end()) { - MWBase::SoundStreamPtr stream = sayiter->second; + Stream *stream = sayiter->second; mActiveSaySounds.erase(sayiter); - mActiveSaySounds[updated] = stream; + mActiveSaySounds.emplace(updated, stream); } } @@ -1226,29 +1243,35 @@ namespace MWSound void SoundManager::clear() { - SoundMap::iterator snditer = mActiveSounds.begin(); - for(;snditer != mActiveSounds.end();++snditer) + stopMusic(); + + for(SoundMap::value_type &snd : mActiveSounds) { - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - for(;sndidx != snditer->second.end();++sndidx) + for(SoundBufferRefPair &sndbuf : snd.second) { - mOutput->finishSound(sndidx->first); - Sound_Buffer *sfx = sndidx->second; + mOutput->finishSound(sndbuf.first); + mUnusedSounds.push_back(sndbuf.first); + Sound_Buffer *sfx = sndbuf.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); + mUnderwaterSound = nullptr; + mNearWaterSound = nullptr; + + for(SaySoundMap::value_type &snd : mActiveSaySounds) + { + mOutput->finishStream(snd.second); + mUnusedStreams.push_back(snd.second); + } mActiveSaySounds.clear(); - TrackList::iterator trkiter = mActiveTracks.begin(); - for(;trkiter != mActiveTracks.end();++trkiter) - mOutput->finishStream(*trkiter); + + for(Stream *sound : mActiveTracks) + { + mOutput->finishStream(sound); + mUnusedStreams.push_back(sound); + } mActiveTracks.clear(); - mUnderwaterSound.reset(); - mNearWaterSound.reset(); - stopMusic(); } } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 691e52932..e31a0e575 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -29,6 +29,7 @@ namespace MWSound class Sound_Output; struct Sound_Decoder; class Sound; + class Stream; class Sound_Buffer; enum Environment { @@ -49,7 +50,7 @@ namespace MWSound std::unique_ptr mOutput; // Caches available music tracks by - std::map > mMusicFiles; + std::unordered_map> mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played @@ -74,25 +75,31 @@ namespace MWSound size_t mBufferCacheMax; size_t mBufferCacheSize; - typedef std::map NameBufferMap; + typedef std::unordered_map NameBufferMap; NameBufferMap mBufferNameMap; // NOTE: unused buffers are stored in front-newest order. typedef std::deque SoundList; SoundList mUnusedBuffers; - typedef std::pair SoundBufferRefPair; + std::unique_ptr> mSounds; + std::vector mUnusedSounds; + + std::unique_ptr> mStreams; + std::vector mUnusedStreams; + + typedef std::pair SoundBufferRefPair; typedef std::vector SoundBufferRefPairList; typedef std::map SoundMap; SoundMap mActiveSounds; - typedef std::map SaySoundMap; + typedef std::map SaySoundMap; SaySoundMap mActiveSaySounds; - typedef std::vector TrackList; + typedef std::vector TrackList; TrackList mActiveTracks; - MWBase::SoundStreamPtr mMusic; + Stream *mMusic; std::string mCurrentPlaylist; bool mListenerUnderwater; @@ -102,8 +109,8 @@ namespace MWSound int mPausedSoundTypes; - MWBase::SoundPtr mUnderwaterSound; - MWBase::SoundPtr mNearWaterSound; + Sound *mUnderwaterSound; + Sound *mNearWaterSound; Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); @@ -113,10 +120,14 @@ namespace MWSound // returns a decoder to start streaming DecoderPtr loadVoice(const std::string &voicefile); - MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); + Sound *getSoundRef(); + Stream *getStreamRef(); + + Stream *playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); void streamMusicFull(const std::string& filename); void advanceMusic(const std::string& filename); + void startRandomTitle(); void updateSounds(float duration); void updateRegionSound(float duration); @@ -125,7 +136,7 @@ namespace MWSound std::string mNextMusic; - float volumeFromType(PlayType type) const; + float volumeFromType(Type type) const; SoundManager(const SoundManager &rhs); SoundManager& operator=(const SoundManager &rhs); @@ -147,9 +158,6 @@ namespace MWSound ///< Play a soundifle /// \param filename name of a sound file in "Music/" in the data directory. - virtual void startRandomTitle(); - ///< Starts a random track from the current playlist - virtual bool isMusicPlaying(); ///< Returns true if music is playing @@ -176,33 +184,33 @@ namespace MWSound /// 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::SoundStreamPtr playTrack(const DecoderPtr& decoder, PlayType type); + virtual Stream *playTrack(const DecoderPtr& decoder, Type type); ///< Play a 2D audio track, using a custom decoder - virtual void stopTrack(MWBase::SoundStreamPtr stream); + virtual void stopTrack(Stream *stream); ///< Stop the given audio track from playing - virtual double getTrackTimeDelay(MWBase::SoundStreamPtr stream); + virtual double getTrackTimeDelay(Stream *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); + virtual Sound *playSound(const std::string& soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0); ///< Play a sound, independently of 3D-position ///< @param offset Number of seconds into the sound to start playback. - 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); + virtual Sound *playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, + float volume, float pitch, Type type=Type::Sfx, + PlayMode mode=PlayMode::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 Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, - float volume, float pitch, PlayType type, PlayMode mode, float offset=0); + virtual Sound *playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, + float volume, float pitch, Type 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. - virtual void stopSound(MWBase::SoundPtr sound); + virtual void stopSound(Sound *sound); ///< Stop the given sound from playing /// @note no-op if \a sound is null @@ -227,10 +235,10 @@ namespace MWSound 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); + virtual void pauseSounds(int types); ///< Pauses all currently playing sounds, including music. - virtual void resumeSounds(int types=Play_TypeMask); + virtual void resumeSounds(int types); ///< Resumes all previously paused sounds. virtual void update(float duration); diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 468207e81..32a3c8f96 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -27,18 +27,18 @@ void MWWorld::Action::execute (const Ptr& actor, bool noSound) { if(!mSoundId.empty() && !noSound) { - MWBase::SoundManager::PlayMode envType = MWBase::SoundManager::Play_Normal; + MWSound::PlayMode envType = MWSound::PlayMode::Normal; // Action sounds should not have a distortion in GUI mode // example: take an item or drink a potion underwater if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWindowManager()->isGuiMode()) { - envType = MWBase::SoundManager::Play_NoEnv; + envType = MWSound::PlayMode::NoEnv; } if(mKeepSound && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0, - MWBase::SoundManager::Play_TypeSfx, envType, mSoundOffset + MWSound::Type::Sfx, envType, mSoundOffset ); else { @@ -46,13 +46,11 @@ void MWWorld::Action::execute (const Ptr& actor, bool noSound) if(mKeepSound) MWBase::Environment::get().getSoundManager()->playSound3D( (local ? actor : mTarget).getRefData().getPosition().asVec3(), - mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, - envType, mSoundOffset + mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset ); else MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget, - mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, - envType, mSoundOffset + mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset ); } } diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index 0df451b18..7ff6dd068 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -10,9 +10,8 @@ namespace MWWorld { - ActionOpen::ActionOpen (const MWWorld::Ptr& container, bool loot) + ActionOpen::ActionOpen (const MWWorld::Ptr& container) : Action (false, container) - , mLoot(loot) { } @@ -23,6 +22,6 @@ namespace MWWorld MWMechanics::diseaseContact(actor, getTarget()); - MWBase::Environment::get().getWindowManager()->openContainer(getTarget(), mLoot); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, getTarget()); } } diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 454cc09f1..3bce25c6b 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -12,12 +12,9 @@ namespace MWWorld virtual void executeImp (const MWWorld::Ptr& actor); public: - ActionOpen (const Ptr& container, bool loot=false); + ActionOpen (const Ptr& container); ///< \param container The Container the Player has activated. - /// \param loot If true, display the "dispose of corpse" button - private: - bool mLoot; }; } diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index afdcfdda2..8e45f14e8 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -43,14 +43,12 @@ namespace MWWorld return; } - bool showTakeButton = (getTarget().getContainerStore() != &actor.getClass().getContainerStore(actor)); - LiveCellRef *ref = getTarget().get(); if (ref->mBase->mData.mIsScroll) - MWBase::Environment::get().getWindowManager()->showScroll(getTarget(), showTakeButton); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Scroll, getTarget()); else - MWBase::Environment::get().getWindowManager()->showBook(getTarget(), showTakeButton); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Book, getTarget()); MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp index 00a4756dd..c23bf2900 100644 --- a/apps/openmw/mwworld/actionread.hpp +++ b/apps/openmw/mwworld/actionread.hpp @@ -16,4 +16,4 @@ namespace MWWorld }; } -#endif // ACTIONOPEN_H +#endif // ACTIONREAD_H diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index 8e19927b8..191cf2063 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -23,6 +23,6 @@ namespace MWWorld return; } - MWBase::Environment::get().getWindowManager()->startRepairItem(getTarget()); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Repair, getTarget()); } } diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 051380ff5..c7bb6a26e 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,7 +1,7 @@ #include "actiontalk.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/windowmanager.hpp" namespace MWWorld { @@ -9,6 +9,6 @@ namespace MWWorld void ActionTalk::executeImp (const Ptr& actor) { - MWBase::Environment::get().getDialogueManager()->startDialogue (getTarget()); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, getTarget()); } } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index fa221154a..9b8c8c6a4 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -633,10 +633,6 @@ namespace MWWorld loadRefs (); mState = State_Loaded; - - // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. - // In a simple test, loading the graph for all cells in MW + expansions took 200 ms - mPathgridGraph.load(this); } } @@ -1125,16 +1121,6 @@ namespace MWWorld return !(left==right); } - bool CellStore::isPointConnected(const int start, const int end) const - { - return mPathgridGraph.isPointConnected(start, end); - } - - std::list CellStore::aStarSearch(const int start, const int end) const - { - return mPathgridGraph.aStarSearch(start, end); - } - void CellStore::setFog(ESM::FogState *fog) { mFogState.reset(fog); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 20fba595a..33e629ea0 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -32,13 +32,12 @@ #include #include -#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld - #include "timestamp.hpp" #include "ptr.hpp" namespace ESM { + struct Cell; struct CellState; struct FogState; struct CellId; @@ -436,10 +435,6 @@ namespace MWWorld void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - bool isPointConnected(const int start, const int end) const; - - std::list aStarSearch(const int start, const int end) const; - private: /// Run through references and store IDs @@ -451,8 +446,6 @@ namespace MWWorld ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. - - MWMechanics::PathgridGraph mPathgridGraph; }; template<> diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index eef10b905..981f63e34 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -408,13 +408,13 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Cons return it; } -int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement) +int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor) { int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) - toRemove -= remove(*iter, toRemove, actor, equipReplacement); + toRemove -= remove(*iter, toRemove, actor); flagAsModified(); @@ -422,7 +422,7 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const return count - toRemove; } -int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement) +int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) { assert(this == item.getContainerStore()); @@ -806,7 +806,7 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) case ESM::REC_WEAP: readEquipmentState (getState (weapons, state), thisIndex, inventory); break; case ESM::REC_LIGH: readEquipmentState (getState (lights, state), thisIndex, inventory); break; case 0: - std::cerr << "Warning: Dropping reference to '" << state.mRef.mRefID << "' (object no longer exists)" << std::endl; + std::cerr << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)" << std::endl; break; default: std::cerr << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID << std::endl; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index f27ff1db9..dbb82cbda 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -142,12 +142,12 @@ namespace MWWorld ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) - int remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement = true); + int remove(const std::string& itemId, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a itemId from this container. /// /// @return the number of items actually removed - virtual int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = true); + virtual int remove(const Ptr& item, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a item from this inventory. /// /// @return the number of items actually removed diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index f672aca71..cb9d6d350 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -289,7 +289,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) // Autoequip clothing, armor and weapons. // Equipping lights is handled in Actors::updateEquippedLight based on environment light. - for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter) { Ptr test = *iter; @@ -314,9 +313,12 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) std::pair, bool> itemsSlots = iter->getClass().getEquipmentSlots (*iter); + // checking if current item poited by iter can be equipped for (std::vector::const_iterator iter2 (itemsSlots.first.begin()); iter2!=itemsSlots.first.end(); ++iter2) { + // if true then it means slot is equipped already + // check if slot may require swapping if current item is more valueable if (slots_.at (*iter2)!=end()) { Ptr old = *slots_.at (*iter2); @@ -339,10 +341,28 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) } else if (iter.getType() == ContainerStore::Type_Clothing) { + // if left ring is equipped + if (*iter2 == Slot_LeftRing) + { + // if there is a place for right ring dont swap it + if (slots_.at(Slot_RightRing) == end()) + { + continue; + } + else // if right ring is equipped too + { + Ptr rightRing = *slots_.at(Slot_RightRing); + + // we want to swap cheaper ring only if both are equipped + if (old.getClass().getValue (old) >= rightRing.getClass().getValue (rightRing)) + continue; + } + } + if (old.getTypeName() == typeid(ESM::Clothing).name()) { // check value - if (old.getClass().getValue (old) > test.getClass().getValue (test)) + if (old.getClass().getValue (old) >= test.getClass().getValue (test)) // old clothing was more valuable continue; } @@ -361,6 +381,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) } } + // if we are here it means item can be equipped or swapped slots_[*iter2] = iter; break; } @@ -678,6 +699,30 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem( return mSelectedEnchantItem; } +int MWWorld::InventoryStore::remove(const std::string& itemId, int count, const Ptr& actor) +{ + return remove(itemId, count, actor, false); +} + +int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor) +{ + return remove(item, count, actor, false); +} + +int MWWorld::InventoryStore::remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement) +{ + int toRemove = count; + + for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) + toRemove -= remove(*iter, toRemove, actor, equipReplacement); + + flagAsModified(); + + // number of removed items + return count - toRemove; +} + int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement) { int retCount = ContainerStore::remove(item, count, actor); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 90f7f7788..49991c164 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -177,7 +177,11 @@ namespace MWWorld virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; ///< @return true if the two specified objects can stack with each other - virtual int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = true); + virtual int remove(const std::string& itemId, int count, const Ptr& actor); + virtual int remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement); + + virtual int remove(const Ptr& item, int count, const Ptr& actor); + virtual int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement); ///< Remove \a count item(s) designated by \a item from this inventory. /// /// @return the number of items actually removed diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index d233fba6f..19bf7f55e 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -276,6 +276,29 @@ namespace MWWorld mAutoMove = false; mForwardBackward = 0; mTeleported = false; + mAttackingOrSpell = false; + mCurrentCrimeId = -1; + mPaidCrimeId = -1; + mLastKnownExteriorPosition = osg::Vec3f(0,0,0); + + for (int i=0; i +#include #include +#include #include #include @@ -41,13 +43,29 @@ namespace { - ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::vector& sounds, float& speed, std::string& texture, const ESM::EffectList& effects) + ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::vector& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id) { + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + const ESM::EffectList* effects; + if (const ESM::Spell* spell = esmStore.get().search(id)) // check if it's a spell + { + sourceName = spell->mName; + effects = &spell->mEffects; + } + else // check if it's an enchanted item + { + MWWorld::ManualRef ref(esmStore, id); + MWWorld::Ptr ptr = ref.getPtr(); + const ESM::Enchantment* ench = esmStore.get().find(ptr.getClass().getEnchantment(ptr)); + sourceName = ptr.getClass().getName(ptr); + effects = &ench->mEffects; + } + int count = 0; speed = 0.0f; ESM::EffectList projectileEffects; - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) + for (std::vector::const_iterator iter (effects->mList.begin()); + iter!=effects->mList.end(); ++iter) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); @@ -82,14 +100,14 @@ namespace if (projectileEffects.mList.size() == 1) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - effects.mList.begin()->mEffectID); + effects->mList.begin()->mEffectID); texture = magicEffect->mParticle; } if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects { std::ostringstream ID; - ID << "VFX_Multiple" << effects.mList.size(); + ID << "VFX_Multiple" << effects->mList.size(); std::vector::iterator it; it = projectileIDs.begin(); it = projectileIDs.insert(it, ID.str()); @@ -132,6 +150,7 @@ namespace MWWorld , mResourceSystem(resourceSystem) , mRendering(rendering) , mPhysics(physics) + , mCleanupTimer(0.0f) { } @@ -184,6 +203,12 @@ namespace MWWorld osg::ref_ptr projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); + osg::ref_ptr boundVisitor = new osg::ComputeBoundsVisitor(); + projectile->accept(*boundVisitor.get()); + osg::BoundingBox bb = boundVisitor->getBoundingBox(); + + state.mNode->setPivotPoint(bb.center()); + if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { @@ -235,8 +260,7 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::string &spellId, bool stack, const ESM::EffectList &effects, const Ptr &caster, - const std::string &sourceName, const osg::Vec3f& fallbackDirection) + void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) @@ -257,18 +281,16 @@ namespace MWWorld orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection)); MagicBoltState state; - state.mSourceName = sourceName; state.mSpellId = spellId; state.mCasterHandle = caster; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else state.mActorId = -1; - state.mStack = stack; std::string texture = ""; - state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, effects); + state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); // Non-projectile should have been removed by getMagicBoltData if (state.mEffects.mList.empty()) @@ -277,13 +299,14 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); - osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(effects); + osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (size_t it = 0; it != state.mSoundIds.size(); it++) { - MWBase::SoundPtr sound = sndMgr->playSound3D(pos, state.mSoundIds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + MWBase::Sound *sound = sndMgr->playSound3D(pos, state.mSoundIds.at(it), 1.0f, 1.0f, + MWSound::Type::Sfx, MWSound::PlayMode::Loop); if (sound) state.mSounds.push_back(sound); } @@ -300,6 +323,7 @@ namespace MWWorld state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; + state.mThrown = projectile.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); @@ -311,10 +335,49 @@ namespace MWWorld void ProjectileManager::update(float dt) { + periodicCleanup(dt); moveProjectiles(dt); moveMagicBolts(dt); } + void ProjectileManager::periodicCleanup(float dt) + { + mCleanupTimer -= dt; + if (mCleanupTimer <= 0.0f) + { + mCleanupTimer = 2.0f; + + auto isCleanable = [](const ProjectileManager::State& state) -> bool + { + const float farawayThreshold = 72000.0f; + osg::Vec3 playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); + return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; + }; + + for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + { + if (isCleanable(*it)) + { + cleanupProjectile(*it); + it = mProjectiles.erase(it); + } + else + ++it; + } + + for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + { + if (isCleanable(*it)) + { + cleanupMagicBolt(*it); + it = mMagicBolts.erase(it); + } + else + ++it; + } + } + } + void ProjectileManager::moveMagicBolts(float duration) { for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) @@ -323,7 +386,6 @@ namespace MWWorld static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->getFloat(); float speed = fTargetSpellMaxSpeed * it->mSpeed; - osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); osg::Vec3f pos(it->mNode->getPosition()); @@ -363,7 +425,7 @@ namespace MWWorld cast.mHitPosition = pos; cast.mId = it->mSpellId; cast.mSourceName = it->mSourceName; - cast.mStack = it->mStack; + cast.mStack = false; cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); } } @@ -377,10 +439,9 @@ namespace MWWorld MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, ESM::RT_Target, it->mSpellId, it->mSourceName); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) - { - MWBase::Environment::get().getSoundManager()->stopSound(it->mSounds.at(soundIter)); - } + sndMgr->stopSound(it->mSounds.at(soundIter)); mParent->removeChild(it->mNode); @@ -404,7 +465,22 @@ namespace MWWorld osg::Vec3f newPos = pos + it->mVelocity * duration; osg::Quat orient; - orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); + + if (it->mThrown) + orient.set( + osg::Matrixd::rotate(it->mEffectAnimationTime->getTime() * -10.0,osg::Vec3f(0,0,1)) * + osg::Matrixd::rotate(osg::PI / 2.0,osg::Vec3f(0,1,0)) * + osg::Matrixd::rotate(-1 * osg::PI / 2.0,osg::Vec3f(1,0,0)) * + osg::Matrixd::inverse( + osg::Matrixd::lookAt( + osg::Vec3f(0,0,0), + it->mVelocity, + osg::Vec3f(0,0,1)) + ) + ); + else + orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); + it->mNode->setAttitude(orient); it->mNode->setPosition(newPos); @@ -454,20 +530,30 @@ namespace MWWorld } } + void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) + { + mParent->removeChild(state.mNode); + } + + void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) + { + mParent->removeChild(state.mNode); + for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) + { + MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); + } + } + void ProjectileManager::clear() { for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) { - mParent->removeChild(it->mNode); + cleanupProjectile(*it); } mProjectiles.clear(); for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) { - mParent->removeChild(it->mNode); - for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) - { - MWBase::Environment::get().getSoundManager()->stopSound(it->mSounds.at(soundIter)); - } + cleanupMagicBolt(*it); } mMagicBolts.clear(); } @@ -504,11 +590,7 @@ namespace MWWorld state.mActorId = it->mActorId; state.mSpellId = it->mSpellId; - state.mEffects = it->mEffects; - state.mSound = it->mSoundIds.at(0); - state.mSourceName = it->mSourceName; state.mSpeed = it->mSpeed; - state.mStack = it->mStack; state.save(writer); @@ -536,6 +618,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); + state.mThrown = ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; } catch(...) { @@ -553,13 +636,21 @@ namespace MWWorld esm.load(reader); MagicBoltState state; - state.mSourceName = esm.mSourceName; state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; - state.mStack = esm.mStack; std::string texture = ""; - state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, esm.mEffects); + + try + { + state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); + } + catch(...) + { + std::cerr << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)" << std::endl; + return true; + } + state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as // projectile effects, so we can't calculate it from the save // file's effect list, which is already trimmed of non-projectile @@ -577,15 +668,14 @@ namespace MWWorld return true; } - osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(esm.mEffects); + osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - for (size_t soundIter = 0; soundIter != state.mSoundIds.size(); soundIter++) { - MWBase::SoundPtr sound = sndMgr->playSound3D(esm.mPosition, state.mSoundIds.at(soundIter), 1.0f, 1.0f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + MWBase::Sound *sound = sndMgr->playSound3D(esm.mPosition, state.mSoundIds.at(soundIter), 1.0f, 1.0f, + MWSound::Type::Sfx, MWSound::PlayMode::Loop); if (sound) state.mSounds.push_back(sound); } diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index bfa4980e9..ba2fe7a74 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,8 +49,7 @@ namespace MWWorld 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 &spellId, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); + void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection); void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); @@ -69,6 +68,7 @@ namespace MWWorld Resource::ResourceSystem* mResourceSystem; MWRender::RenderingManager* mRendering; MWPhysics::PhysicsSystem* mPhysics; + float mCleanupTimer; struct State { @@ -101,9 +101,7 @@ namespace MWWorld float mSpeed; - bool mStack; - - std::vector mSounds; + std::vector mSounds; std::vector mSoundIds; }; @@ -114,11 +112,16 @@ namespace MWWorld osg::Vec3f mVelocity; float mAttackStrength; + bool mThrown; }; std::vector mMagicBolts; std::vector mProjectiles; + void cleanupProjectile(ProjectileState& state); + void cleanupMagicBolt(MagicBoltState& state); + void periodicCleanup(float dt); + void moveProjectiles(float dt); void moveMagicBolts(float dt); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5d04a7af0..ae152646f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -645,10 +645,16 @@ namespace MWWorld MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); + if (changeEvent) + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); + changeCellGrid(x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); + + if (changeEvent) + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); } CellStore* Scene::getCurrentCell () diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index eaf88d45a..2f0a2f8cf 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -531,7 +531,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mQueuedWeather(0) , mRegions() , mResult() - , mAmbientSound() + , mAmbientSound(nullptr) , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; @@ -731,19 +731,21 @@ void WeatherManager::update(float duration, bool paused) { stopSounds(); if (!mResult.mAmbientLoopSoundID.empty()) - mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - + mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound( + mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, + MWSound::Type::Sfx, MWSound::PlayMode::Loop + ); mPlayingSoundID = mResult.mAmbientLoopSoundID; } - if (mAmbientSound.get()) + else if (mAmbientSound) mAmbientSound->setVolume(mResult.mAmbientSoundVolume); } void WeatherManager::stopSounds() { - if (mAmbientSound.get()) + if (mAmbientSound) MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound.reset(); + mAmbientSound = nullptr; mPlayingSoundID.clear(); } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 044200757..84a6c5105 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -288,7 +288,7 @@ namespace MWWorld std::map mRegions; MWRender::WeatherResult mResult; - MWBase::SoundPtr mAmbientSound; + MWBase::Sound *mAmbientSound; std::string mPlayingSoundID; void addWeather(const std::string& name, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a086c5619..44924b2c3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2218,11 +2218,16 @@ namespace MWWorld pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z(); - return isUnderwater(object.getCell(), pos); + const CellStore *currCell = object.isInCell() ? object.getCell() : NULL; // currCell == NULL should only happen for player, during initial startup + + return isUnderwater(currCell, pos); } bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const { + if (!cell) + return false; + if (!(cell->getCell()->hasWater())) { return false; } @@ -2602,6 +2607,10 @@ namespace MWWorld if (ptr.getRefData().isDeleted()) return true; + // vanilla Morrowind does not allow to sell items from containers with zero capacity + if (ptr.getClass().getCapacity(ptr) <= 0.f) + return true; + if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId())) mOut.push_back(ptr); @@ -2989,10 +2998,9 @@ namespace MWWorld mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } - void World::launchMagicBolt (const std::string &spellId, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) + void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) { - mProjectileManager->launchMagicBolt(spellId, stack, effects, caster, sourceName, fallbackDirection); + mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); } const std::vector& World::getContentFiles() const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8fbcb88c2..f3728a29d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -69,7 +69,7 @@ namespace MWWorld /// \brief The game world and its visual representation - class World : public MWBase::World + class World final: public MWBase::World { Resource::ResourceSystem* mResourceSystem; @@ -137,7 +137,7 @@ namespace MWWorld MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true); public: // FIXME - void removeContainerScripts(const Ptr& reference); + void removeContainerScripts(const Ptr& reference) override; private: void addContainerScripts(const Ptr& reference, CellStore* cell); void PCDropped (const Ptr& item); @@ -191,210 +191,210 @@ namespace MWWorld virtual ~World(); - virtual void startNewGame (bool bypass); + void startNewGame (bool bypass) override; ///< \param bypass Bypass regular game start. - virtual void clear(); + void clear() override; - virtual int countSavedGameRecords() const; - virtual int countSavedGameCells() const; + int countSavedGameRecords() const override; + int countSavedGameCells() const override; - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const override; - virtual void readRecord (ESM::ESMReader& reader, uint32_t type, - const std::map& contentFileMap); + void readRecord (ESM::ESMReader& reader, uint32_t type, + const std::map& contentFileMap) override; - virtual CellStore *getExterior (int x, int y); + CellStore *getExterior (int x, int y) override; - virtual CellStore *getInterior (const std::string& name); + CellStore *getInterior (const std::string& name) override; - virtual CellStore *getCell (const ESM::CellId& id); + CellStore *getCell (const ESM::CellId& id) override; //switch to POV before showing player's death animation - virtual void useDeathCamera(); + void useDeathCamera() override; - virtual void setWaterHeight(const float height); + void setWaterHeight(const float height) override; - virtual bool toggleWater(); - virtual bool toggleWorld(); + bool toggleWater() override; + bool toggleWorld() override; - virtual void adjustSky(); + void adjustSky() override; - virtual const Fallback::Map *getFallback() const; + const Fallback::Map *getFallback() const override; - virtual Player& getPlayer(); - virtual MWWorld::Ptr getPlayerPtr(); + Player& getPlayer() override; + MWWorld::Ptr getPlayerPtr() override; - virtual const MWWorld::ESMStore& getStore() const; + const MWWorld::ESMStore& getStore() const override; - virtual std::vector& getEsmReader(); + std::vector& getEsmReader() override; - virtual LocalScripts& getLocalScripts(); + LocalScripts& getLocalScripts() override; - virtual bool hasCellChanged() const; + bool hasCellChanged() const override; ///< Has the set of active cells changed, since the last frame? - virtual bool isCellExterior() const; + bool isCellExterior() const override; - virtual bool isCellQuasiExterior() const; + bool isCellQuasiExterior() const override; - virtual osg::Vec2f getNorthVector (const CellStore* cell); + osg::Vec2f getNorthVector (const CellStore* cell) override; ///< get north vector for given interior cell - virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); + void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) override; ///< get a list of teleport door markers for a given cell, to be displayed on the local map - virtual void setGlobalInt (const std::string& name, int value); + void setGlobalInt (const std::string& name, int value) override; ///< Set value independently from real type. - virtual void setGlobalFloat (const std::string& name, float value); + void setGlobalFloat (const std::string& name, float value) override; ///< Set value independently from real type. - virtual int getGlobalInt (const std::string& name) const; + int getGlobalInt (const std::string& name) const override; ///< Get value independently from real type. - virtual float getGlobalFloat (const std::string& name) const; + float getGlobalFloat (const std::string& name) const override; ///< Get value independently from real type. - virtual char getGlobalVariableType (const std::string& name) const; + char getGlobalVariableType (const std::string& name) const override; ///< Return ' ', if there is no global variable with this name. - virtual std::string getCellName (const MWWorld::CellStore *cell = 0) const; + std::string getCellName (const MWWorld::CellStore *cell = 0) const override; ///< Return name of the cell. /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. - virtual void removeRefScript (MWWorld::RefData *ref); + void removeRefScript (MWWorld::RefData *ref) override; //< Remove the script attached to ref from mLocalScripts - virtual Ptr getPtr (const std::string& name, bool activeOnly); + Ptr getPtr (const std::string& name, bool activeOnly) override; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - virtual Ptr searchPtr (const std::string& name, bool activeOnly); + Ptr searchPtr (const std::string& name, bool activeOnly) override; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - virtual Ptr searchPtrViaActorId (int actorId); + Ptr searchPtrViaActorId (int actorId) override; ///< Search is limited to the active cells. - virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr); + MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr) override; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. - virtual void adjustPosition (const Ptr& ptr, bool force); + void adjustPosition (const Ptr& ptr, bool force) override; ///< Adjust position after load to be on ground. Must be called after model load. /// @param force do this even if the ptr is flying - virtual void fixPosition (const Ptr& actor); + void fixPosition (const Ptr& actor) override; ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. - virtual void enable (const Ptr& ptr); + void enable (const Ptr& ptr) override; - virtual void disable (const Ptr& ptr); + void disable (const Ptr& ptr) override; - virtual void advanceTime (double hours, bool incremental = false); + void advanceTime (double hours, bool incremental = false) override; ///< Advance in-game time. - virtual void setHour (double hour); + void setHour (double hour) override; ///< Set in-game time hour. - virtual void setMonth (int month); + void setMonth (int month) override; ///< Set in-game time month. - virtual void setDay (int day); + void setDay (int day) override; ///< Set in-game time day. - virtual int getDay() const; - virtual int getMonth() const; - virtual int getYear() const; + int getDay() const override; + int getMonth() const override; + int getYear() const override; - virtual std::string getMonthName (int month = -1) const; + std::string getMonthName (int month = -1) const override; ///< Return name of month (-1: current month) - virtual TimeStamp getTimeStamp() const; + TimeStamp getTimeStamp() const override; ///< Return current in-game time stamp. - virtual bool toggleSky(); + bool toggleSky() override; ///< \return Resulting mode - virtual void changeWeather (const std::string& region, const unsigned int id); + void changeWeather (const std::string& region, const unsigned int id) override; - virtual int getCurrentWeather() const; + int getCurrentWeather() const override; - virtual int getMasserPhase() const; + int getMasserPhase() const override; - virtual int getSecundaPhase() const; + int getSecundaPhase() const override; - virtual void setMoonColour (bool red); + void setMoonColour (bool red) override; - virtual void modRegion(const std::string ®ionid, const std::vector &chances); + void modRegion(const std::string ®ionid, const std::vector &chances) override; - virtual float getTimeScaleFactor() const; + float getTimeScaleFactor() const override; - virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true); + void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) override; ///< Move to interior cell. ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true); + void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) override; ///< Move to exterior cell. ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); + void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) override; ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual const ESM::Cell *getExterior (const std::string& cellName) const; + const ESM::Cell *getExterior (const std::string& cellName) const override; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. - virtual void markCellAsUnchanged(); + void markCellAsUnchanged() override; - virtual MWWorld::Ptr getFacedObject(); + MWWorld::Ptr getFacedObject() override; ///< Return pointer to the object the player is looking at, if it is within activation range - virtual float getDistanceToFacedObject(); + float getDistanceToFacedObject() override; /// 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::ConstPtr &ptr, float distance, std::vector &targets); + std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) override; /// @note No-op for items in containers. Use ContainerStore::removeItem instead. - virtual void deleteObject (const Ptr& ptr); + void deleteObject (const Ptr& ptr) override; - virtual void undeleteObject (const Ptr& ptr); + void undeleteObject (const Ptr& ptr) override; - virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); + MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z) override; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true); + MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr - virtual void scaleObject (const Ptr& ptr, float scale); + void scaleObject (const Ptr& ptr, float scale) override; /// 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); + void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false) override; - virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); + MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) override; ///< Place an object. Makes a copy of the Ptr. - virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance); + MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) override; ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). - virtual float getMaxActivationDistance(); + float getMaxActivationDistance() override; - virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) - const; + void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) + const override; ///< Convert cell numbers to position. - virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const; + void positionToIndex (float x, float y, int &cellX, int &cellY) const override; ///< Convert position to cell numbers - virtual void queueMovement(const Ptr &ptr, const osg::Vec3f &velocity); + void queueMovement(const Ptr &ptr, const osg::Vec3f &velocity) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. @@ -408,139 +408,139 @@ namespace MWWorld End of tes3mp addition */ - virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2); + bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; ///< cast a Ray and return true if there is an object in the ray path. - virtual bool toggleCollisionMode(); + bool toggleCollisionMode() override; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. ///< \return Resulting mode - virtual bool toggleRenderMode (MWRender::RenderMode mode); + bool toggleRenderMode (MWRender::RenderMode mode) override; ///< Toggle a render mode. ///< \return Resulting mode - virtual const ESM::Potion *createRecord (const ESM::Potion& record); + const ESM::Potion *createRecord (const ESM::Potion& record) override; ///< Create a new record (of type potion) in the ESM store. /// \return pointer to created record - virtual const ESM::Spell *createRecord (const ESM::Spell& record); + const ESM::Spell *createRecord (const ESM::Spell& record) override; ///< Create a new record (of type spell) in the ESM store. /// \return pointer to created record - virtual const ESM::Class *createRecord (const ESM::Class& record); + const ESM::Class *createRecord (const ESM::Class& record) override; ///< Create a new record (of type class) in the ESM store. /// \return pointer to created record - virtual const ESM::Cell *createRecord (const ESM::Cell& record); + const ESM::Cell *createRecord (const ESM::Cell& record) override; ///< Create a new record (of type cell) in the ESM store. /// \return pointer to created record - virtual const ESM::NPC *createRecord(const ESM::NPC &record); + const ESM::NPC *createRecord(const ESM::NPC &record) override; ///< Create a new record (of type npc) in the ESM store. /// \return pointer to created record - virtual const ESM::Creature *createRecord(const ESM::Creature &record); + const ESM::Creature *createRecord(const ESM::Creature &record) override; ///< Create a new record (of type creature) in the ESM store. /// \return pointer to created record - virtual const ESM::Armor *createRecord (const ESM::Armor& record); + const ESM::Armor *createRecord (const ESM::Armor& record) override; ///< Create a new record (of type armor) in the ESM store. /// \return pointer to created record - virtual const ESM::Weapon *createRecord (const ESM::Weapon& record); + const ESM::Weapon *createRecord (const ESM::Weapon& record) override; ///< Create a new record (of type weapon) in the ESM store. /// \return pointer to created record - virtual const ESM::Clothing *createRecord (const ESM::Clothing& record); + const ESM::Clothing *createRecord (const ESM::Clothing& record) override; ///< Create a new record (of type clothing) in the ESM store. /// \return pointer to created record - virtual const ESM::Enchantment *createRecord (const ESM::Enchantment& record); + const ESM::Enchantment *createRecord (const ESM::Enchantment& record) override; ///< Create a new record (of type enchantment) in the ESM store. /// \return pointer to created record - virtual const ESM::Book *createRecord (const ESM::Book& record); + const ESM::Book *createRecord (const ESM::Book& record) override; ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record - virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record); + const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record - virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record); + const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record - virtual void update (float duration, bool paused); + void update (float duration, bool paused) override; - virtual void updateWindowManager (); + void updateWindowManager () override; - virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount); + MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) override; ///< 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::ConstPtr& object, int amount); + MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) override; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object /// @param number of objects to place - virtual bool canPlaceObject(float cursorX, float cursorY); + bool canPlaceObject(float cursorX, float cursorY) override; ///< @return true if it is possible to place on object at specified cursor location - virtual void processChangedSettings(const Settings::CategorySettingVector& settings); + void processChangedSettings(const Settings::CategorySettingVector& settings) override; - virtual bool isFlying(const MWWorld::Ptr &ptr) const; - virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const; + bool isFlying(const MWWorld::Ptr &ptr) const override; + bool isSlowFalling(const MWWorld::Ptr &ptr) const override; ///Is the head of the creature underwater? - 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 isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const; - virtual bool isWading(const MWWorld::ConstPtr &object) const; - virtual bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const; - virtual bool isOnGround(const MWWorld::Ptr &ptr) const; + bool isSubmerged(const MWWorld::ConstPtr &object) const override; + bool isSwimming(const MWWorld::ConstPtr &object) const override; + bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const override; + bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const override; + bool isWading(const MWWorld::ConstPtr &object) const override; + bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const override; + bool isOnGround(const MWWorld::Ptr &ptr) const override; - virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const; + osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const override; - virtual void togglePOV(); + void togglePOV() override; - virtual bool isFirstPerson() const; + bool isFirstPerson() const override; - virtual void togglePreviewMode(bool enable); + void togglePreviewMode(bool enable) override; - virtual bool toggleVanityMode(bool enable); + bool toggleVanityMode(bool enable) override; - virtual void allowVanityMode(bool allow); + void allowVanityMode(bool allow) override; - virtual void togglePlayerLooking(bool enable); + void togglePlayerLooking(bool enable) override; - virtual void changeVanityModeScale(float factor); + void changeVanityModeScale(float factor) override; - virtual bool vanityRotateCamera(float * rot); - virtual void setCameraDistance(float dist, bool adjust = false, bool override = true); + bool vanityRotateCamera(float * rot) override; + void setCameraDistance(float dist, bool adjust = false, bool override = true) override; - virtual void setupPlayer(); - virtual void renderPlayer(); + void setupPlayer() override; + void renderPlayer() override; /// open or close a non-teleport door (depending on current state) - virtual void activateDoor(const MWWorld::Ptr& door); + void activateDoor(const MWWorld::Ptr& door) override; /// update movement state of a non-teleport door as specified /// @param state see MWClass::setDoorState /// @note throws an exception when invoked on a teleport door - virtual void activateDoor(const MWWorld::Ptr& door, int state); + void activateDoor(const MWWorld::Ptr& door, int state) override; /* Start of tes3mp addition Useful self-contained method for saving door states */ - virtual void saveDoorState(const MWWorld::Ptr& door, int state); + void saveDoorState(const MWWorld::Ptr& door, int state) override; /* End of tes3mp addition */ @@ -550,37 +550,37 @@ namespace MWWorld Make it possible to check whether a cell is active */ - virtual bool isCellActive(MWWorld::CellStore* cell); + bool isCellActive(MWWorld::CellStore* cell) override; /* End of tes3mp addition */ - 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); + bool getPlayerStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if the player is standing on \a object + bool getActorStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if any actor is standing on \a object + bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) override; ///< @return true if the player is colliding with \a object + bool getActorCollidingWith (const MWWorld::ConstPtr& object) override; ///< @return true if any actor is colliding with \a object + void hurtStandingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) override; ///< 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::ConstPtr& object, float dmgPerSecond); + void hurtCollidingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) override; ///< 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(); + float getWindSpeed() override; - virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out); + void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) override; ///< get all containers in active cells owned by this Npc - virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out); + void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) override; ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor); + bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) override; ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false); + float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) override; - virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); + void enableActorCollision(const MWWorld::Ptr& actor, bool enable) override; - virtual int canRest(); + int canRest() override; ///< check if the player is allowed to rest \n /// 0 - yes \n /// 1 - only waiting \n @@ -588,133 +588,132 @@ namespace MWWorld /// 3 - enemies are nearby (not implemented) /// \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(); + MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override; + const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const override; + void reattachPlayerCamera() override; /// \todo this does not belong here - virtual void screenshot (osg::Image* image, int w, int h); + void screenshot (osg::Image* image, int w, int h) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise - virtual bool findExteriorPosition(const std::string &name, ESM::Position &pos); + bool findExteriorPosition(const std::string &name, ESM::Position &pos) override; /// Find position in interior cell near door entrance /// \return false if interior with given name not exists, true otherwise - virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos); + bool findInteriorPosition(const std::string &name, ESM::Position &pos) override; /// Enables or disables use of teleport spell effects (recall, intervention, etc). - virtual void enableTeleporting(bool enable); + void enableTeleporting(bool enable) override; /// Returns true if teleport spell effects are allowed. - virtual bool isTeleportingEnabled() const; + bool isTeleportingEnabled() const override; /// Enables or disables use of levitation spell effect. - virtual void enableLevitation(bool enable); + void enableLevitation(bool enable) override; /// Returns true if levitation spell effect is allowed. - virtual bool isLevitationEnabled() const; + bool isLevitationEnabled() const override; - virtual bool getGodModeState(); + bool getGodModeState() override; - virtual bool toggleGodMode(); + bool toggleGodMode() override; - virtual bool toggleScripts(); - virtual bool getScriptsEnabled() const; + bool toggleScripts() override; + bool getScriptsEnabled() const override; /** * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. * @param actor * @return true if the spell can be casted (i.e. the animation should start) */ - virtual bool startSpellCast (const MWWorld::Ptr& actor); + bool startSpellCast (const MWWorld::Ptr& actor) override; /** * @brief Cast the actual spell, should be called mid-animation * @param actor */ - virtual void castSpell (const MWWorld::Ptr& actor); + void castSpell (const MWWorld::Ptr& actor) override; - virtual void launchMagicBolt (const std::string& spellId, 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::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); + void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; + void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; - virtual const std::vector& getContentFiles() const; + const std::vector& getContentFiles() const override; - virtual void breakInvisibility (const MWWorld::Ptr& actor); + void breakInvisibility (const MWWorld::Ptr& actor) override; // Are we in an exterior or pseudo-exterior cell and it's night? - virtual bool isDark() const; + bool isDark() const override; - virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result); + bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case - virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, - const std::string& id); + void teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id) override; /// List all references (filtered by \a type) detected by \a ptr. The range /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. /// @note This also works for references in containers. - virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, - DetectionType type); + void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, + DetectionType type) override; /// Update the value of some globals according to the world state, which may be used by dialogue entries. /// This should be called when initiating a dialogue. - virtual void updateDialogueGlobals(); + void updateDialogueGlobals() override; /// Moves all stolen items from \a ptr to the closest evidence chest. - virtual void confiscateStolenItems(const MWWorld::Ptr& ptr); + void confiscateStolenItems(const MWWorld::Ptr& ptr) override; - virtual void goToJail (); + void goToJail () override; /// Spawn a random creature from a levelled list next to the player - virtual void spawnRandomCreature(const std::string& creatureList); + void spawnRandomCreature(const std::string& creatureList) override; /// Spawn a blood effect for \a ptr at \a worldPosition - virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition); + void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos); + void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override; - virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, + void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, - const bool fromProjectile=false); + const bool fromProjectile=false) override; - virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); + void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; /// @see MWWorld::WeatherManager::isInStorm - virtual bool isInStorm() const; + bool isInStorm() const override; /// @see MWWorld::WeatherManager::getStormDirection - virtual osg::Vec3f getStormDirection() const; + osg::Vec3f getStormDirection() const override; /// Resets all actors in the current active cells to their original location within that cell. - virtual void resetActors(); + void resetActors() override; - virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor) const; + bool isWalkingOnWater (const MWWorld::ConstPtr& actor) const override; /// 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::ConstPtr& actor, const MWWorld::ConstPtr& target); + osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; /// Return the distance between actor's weapon and target's collision box. - virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); + float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; - virtual bool isPlayerInJail() const; + bool isPlayerInJail() const override; /// Return terrain height at \a worldPos position. - virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const; + float getTerrainHeightAt(const osg::Vec3f& worldPos) const override; /// Return physical or rendering half extents of the given actor. - virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const; + osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const override; /// Export scene graph to a file and return the filename. /// \param ptr object to export scene graph for (if empty, export entire scene graph) - virtual std::string exportSceneGraph(const MWWorld::Ptr& ptr); + std::string exportSceneGraph(const MWWorld::Ptr& ptr) override; /// Preload VFX associated with this effect list - virtual void preloadEffects(const ESM::EffectList* effectList); + void preloadEffects(const ESM::EffectList* effectList) override; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index ea4b1209c..9b09bc41f 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -17,7 +17,7 @@ if (GTEST_FOUND) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) + openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index d2b9ab0f6..5f7338e52 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -96,7 +96,7 @@ if (OPENMW_USE_UNSHIELD) include_directories(${LIBUNSHIELD_INCLUDE_DIRS}) endif() -add_executable(openmw-wizard +openmw_add_executable(openmw-wizard ${GUI_TYPE} ${WIZARD} ${WIZARD_HEADER} diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake index 25105f1e2..4f2be8c42 100644 --- a/cmake/FindSDL2.cmake +++ b/cmake/FindSDL2.cmake @@ -70,10 +70,10 @@ endif() libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h HINTS $ENV{SDL2DIR} - PATH_SUFFIXES include SDL2 + PATH_SUFFIXES include SDL2 include/SDL2 FIND_LIBRARY SDL2 HINTS $ENV{SDL2DIR} - PATH_SUFFIXES ${_sdl_lib_suffix} + PATH_SUFFIXES lib ${_sdl_lib_suffix} ) libfind_version_n_header(SDL2 NAMES SDL_version.h DEFINES SDL_MAJOR_VERSION SDL_MINOR_VERSION SDL_PATCHLEVEL) @@ -85,7 +85,7 @@ IF(NOT SDL2_BUILDING_LIBRARY AND NOT APPLE) libfind_pkg_detect(SDL2MAIN sdl2 FIND_LIBRARY SDL2main HINTS $ENV{SDL2DIR} - PATH_SUFFIXES ${_sdl_lib_suffix} + PATH_SUFFIXES lib ${_sdl_lib_suffix} ) set(SDL2MAIN_FIND_QUIETLY TRUE) libfind_process(SDL2MAIN) diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index 32abbbfbf..ff719d72a 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -26,4 +26,6 @@ else () message(WARNING "Failed to get valid version information from Git") endif () -configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) +include(${MACROSFILE}) + +configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 04530f313..5dd9901fe 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -91,13 +91,6 @@ endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_qt_dir) -macro (copy_all_files source_dir destination_dir files) -foreach (f ${files}) -get_filename_component(filename ${f} NAME) -configure_file(${source_dir}/${f} ${destination_dir}/${filename} COPYONLY) -endforeach (f) -endmacro (copy_all_files) - macro (add_file project type file) list (APPEND ${project}${type} ${file}) endmacro (add_file) @@ -145,3 +138,73 @@ foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_hdrs_noqt) + +include(CMakeParseArguments) + +macro (openmw_add_executable target) + set(OMW_ADD_EXE_OPTIONS WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL) + set(OMW_ADD_EXE_VALUES) + set(OMW_ADD_EXE_MULTI_VALUES) + cmake_parse_arguments(OMW_ADD_EXE "${OMW_ADD_EXE_OPTIONS}" "${OMW_ADD_EXE_VALUES}" "${OMW_ADD_EXE_MULTI_VALUES}" ${ARGN}) + + if (OMW_ADD_EXE_WIN32) + set(OMW_ADD_EXE_WIN32_VALUE WIN32) + endif (OMW_ADD_EXE_WIN32) + + if (OMW_ADD_EXE_MACOSX_BUNDLE) + set(OMW_ADD_EXE_MACOSX_BUNDLE_VALUE MACOSX_BUNDLE) + endif (OMW_ADD_EXE_MACOSX_BUNDLE) + + if (OMW_ADD_EXE_EXCLUDE_FROM_ALL) + set(OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE EXCLUDE_FROM_ALL) + endif (OMW_ADD_EXE_EXCLUDE_FROM_ALL) + + add_executable(${target} ${OMW_ADD_EXE_WIN32_VALUE} ${OMW_ADD_EXE_MACOSX_BUNDLE_VALUE} ${OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE} ${OMW_ADD_EXE_UNPARSED_ARGUMENTS}) + + if (MSVC) + if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) + set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$(TargetDir)") + endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) + endif (MSVC) +endmacro (openmw_add_executable) + +macro (get_generator_is_multi_config VALUE) + if (DEFINED generator_is_multi_config_var) + set(${VALUE} ${generator_is_multi_config_var}) + else (DEFINED generator_is_multi_config_var) + if (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) + get_cmake_property(${VALUE} GENERATOR_IS_MULTI_CONFIG) + else (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) + list(LENGTH "${CMAKE_CONFIGURATION_TYPES}" ${VALUE}) + endif (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) + endif (DEFINED generator_is_multi_config_var) +endmacro (get_generator_is_multi_config) + +macro (copy_resource_file source_path destination_dir_base dest_path_relative) + get_generator_is_multi_config(multi_config) + if (multi_config) + foreach(cfgtype ${CMAKE_CONFIGURATION_TYPES}) + configure_file(${source_path} "${destination_dir_base}/${cfgtype}/${dest_path_relative}" COPYONLY) + endforeach(cfgtype) + else (multi_config) + configure_file(${source_path} "${destination_dir_base}/${dest_path_relative}" COPYONLY) + endif (multi_config) +endmacro (copy_resource_file) + +macro (configure_resource_file source_path destination_dir_base dest_path_relative) + get_generator_is_multi_config(multi_config) + if (multi_config) + foreach(cfgtype ${CMAKE_CONFIGURATION_TYPES}) + configure_file(${source_path} "${destination_dir_base}/${cfgtype}/${dest_path_relative}") + endforeach(cfgtype) + else (multi_config) + configure_file(${source_path} "${destination_dir_base}/${dest_path_relative}") + endif (multi_config) +endmacro (configure_resource_file) + +macro (copy_all_resource_files source_dir destination_dir_base destination_dir_relative files) + foreach (f ${files}) + get_filename_component(filename ${f} NAME) + copy_resource_file("${source_dir}/${f}" "${destination_dir_base}" "${destination_dir_relative}/${filename}") + endforeach (f) +endmacro (copy_all_resource_files) \ No newline at end of file diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f57f1353d..2efc1c412 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -2,22 +2,28 @@ project (Components) # Version file set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in") -set (VERSION_FILE "${OpenMW_BINARY_DIR}/resources/version") +set (VERSION_FILE_PATH_BASE "${OpenMW_BINARY_DIR}") +set (VERSION_FILE_PATH_RELATIVE resources/version) if (GIT_CHECKOUT) - add_custom_target (git-version + get_generator_is_multi_config(multi_config) + add_custom_target (git-version COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DVERSION_IN_FILE=${VERSION_IN_FILE} - -DVERSION_FILE=${VERSION_FILE} + -DVERSION_FILE_PATH_BASE=${VERSION_FILE_PATH_BASE} + -DVERSION_FILE_PATH_RELATIVE=${VERSION_FILE_PATH_RELATIVE} -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} -DOPENMW_VERSION=${OPENMW_VERSION} + -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake + "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" + -Dgenerator_is_multi_config_var=${multi_config} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake VERBATIM) else (GIT_CHECKOUT) - configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) + configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) endif (GIT_CHECKOUT) if(BUILD_OPENMW OR BUILD_OPENCS) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index eb3e1e4ee..cd5bf7ef7 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -240,7 +240,7 @@ namespace Compiler void registerExtensions (Extensions& extensions) { extensions.registerFunction ("xbox", 'l', "", opcodeXBox); - extensions.registerFunction ("onactivate", 'l', "", opcodeOnActivate); + extensions.registerFunction ("onactivate", 'l', "", opcodeOnActivate, opcodeOnActivateExplicit); extensions.registerInstruction ("activate", "x", opcodeActivate, opcodeActivateExplicit); extensions.registerInstruction ("lock", "/l", opcodeLock, opcodeLockExplicit); extensions.registerInstruction ("unlock", "", opcodeUnlock, opcodeUnlockExplicit); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index b2333032b..6a6552467 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -200,6 +200,7 @@ namespace Compiler { const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; + const int opcodeOnActivateExplicit = 0x2000306; const int opcodeActivate = 0x2000075; const int opcodeActivateExplicit = 0x2000244; const int opcodeLock = 0x20004; diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index a897806c2..b35612ee4 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -55,7 +55,6 @@ void Config::GameSettings::validatePaths() for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { QString path = QString::fromUtf8(it->string().c_str()); - path.remove(QChar('\"')); QDir dir(path); if (dir.exists()) @@ -64,6 +63,11 @@ void Config::GameSettings::validatePaths() // Do the same for data-local QString local = mSettings.value(QString("data-local")); + if (local.length() && local.at(0) == QChar('\"')) + { + local.remove(0, 1); + local.chop(1); + } if (local.isEmpty()) return; @@ -76,7 +80,6 @@ void Config::GameSettings::validatePaths() if (!dataDirs.empty()) { QString path = QString::fromUtf8(dataDirs.front().string().c_str()); - path.remove(QChar('\"')); QDir dir(path); if (dir.exists()) @@ -120,6 +123,28 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMap // Don't remove existing data entries if (key != QLatin1String("data")) settings.remove(key); + else + { + // 'data=...' line, so needs processing to deal with ampersands and quotes + // The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not work as there are too may QStrings involved + QChar delim = '\"'; + QChar escape = '&'; + + if (value.at(0) == delim) + { + QString valueOriginal = value; + value = ""; + + for (QString::const_iterator it = valueOriginal.begin() + 1; it != valueOriginal.end(); ++it) + { + if (*it == escape) + ++it; + else if (*it == delim) + break; + value += *it; + } + } + } QStringList values = cache.values(key); values.append(settings.values(key)); @@ -152,9 +177,31 @@ bool Config::GameSettings::writeFile(QTextStream &stream) while (i.hasPrevious()) { i.previous(); + // 'data=...' lines need quotes and ampersands escaping to match how boost::filesystem::path uses boost::io::quoted + if (i.key() == QLatin1String("data")) + { + stream << i.key() << "="; + + // The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not work as there are too may QStrings involved + QChar delim = '\"'; + QChar escape = '&'; + QString string = i.value(); + + stream << delim; + for (QString::const_iterator it = string.begin(); it != string.end(); ++it) + { + if (*it == delim || *it == escape) + stream << escape; + stream << *it; + } + stream << delim; + + stream << '\n'; + continue; + } + // Quote paths with spaces - if (i.key() == QLatin1String("data") - || i.key() == QLatin1String("data-local") + if (i.key() == QLatin1String("data-local") || i.key() == QLatin1String("resources")) { if (i.value().contains(QChar(' '))) @@ -358,9 +405,26 @@ bool Config::GameSettings::writeFileWithComments(QFile &file) { it.previous(); + if (it.key() == QLatin1String("data")) + { + settingLine = it.key() + "="; + + // The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not work as there are too may QStrings involved + QChar delim = '\"'; + QChar escape = '&'; + QString string = it.value(); + + settingLine += delim; + for (QString::const_iterator iter = string.begin(); iter != string.end(); ++iter) + { + if (*iter == delim || *iter == escape) + settingLine += escape; + settingLine += *iter; + } + settingLine += delim; + } // Quote paths with spaces - if ((it.key() == QLatin1String("data") - || it.key() == QLatin1String("data-local") + else if ((it.key() == QLatin1String("data-local") || it.key() == QLatin1String("resources")) && it.value().contains(QChar(' '))) { QString stripped = it.value(); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 72c3eb98d..a91dfe3d3 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -175,6 +175,45 @@ namespace ESM } + void Land::blank() + { + mPlugin = 0; + + for (int i = 0; i < LAND_GLOBAL_MAP_LOD_SIZE; ++i) + mWnam[0] = 0; + + if (!mLandData) + mLandData = new LandData; + + mLandData->mHeightOffset = 0; + for (int i = 0; i < LAND_NUM_VERTS; ++i) + mLandData->mHeights[i] = 0; + mLandData->mMinHeight = 0; + mLandData->mMaxHeight = 0; + for (int i = 0; i < LAND_NUM_VERTS; ++i) + { + mLandData->mNormals[i*3+0] = 0; + mLandData->mNormals[i*3+1] = 0; + mLandData->mNormals[i*3+2] = 127; + } + for (int i = 0; i < LAND_NUM_TEXTURES; ++i) + mLandData->mTextures[i] = 0; + for (int i = 0; i < LAND_NUM_VERTS; ++i) + { + mLandData->mColours[i*3+0] = -1; + mLandData->mColours[i*3+1] = -1; + mLandData->mColours[i*3+2] = -1; + } + mLandData->mUnk1 = 0; + mLandData->mUnk2 = 0; + mLandData->mDataLoaded = Land::DATA_VNML | Land::DATA_VHGT | Land::DATA_WNAM | + Land::DATA_VCLR | Land::DATA_VTEX; + mDataTypes = mLandData->mDataLoaded; + + // No file associated with the land now + mContext.filename.clear(); + } + void Land::loadData(int flags, LandData* target) const { // Create storage if nothing is loaded @@ -193,6 +232,16 @@ namespace ESM return; } + // Copy data to target if no file + if (mContext.filename.empty()) + { + // Make sure there is data, and that it doesn't point to the same object. + if (mLandData && mLandData != target) + *target = *mLandData; + + return; + } + ESM::ESMReader reader; reader.restoreContext(mContext); @@ -266,7 +315,7 @@ namespace ESM bool Land::isDataLoaded(int flags) const { - return mLandData && (mLandData->mDataLoaded & flags) == (flags & mDataTypes); + return mLandData && (mLandData->mDataLoaded & flags) == flags; } Land::Land (const Land& land) diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 9f33c37da..2163c30fc 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -31,6 +31,8 @@ struct Land // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. + // In the editor, there may not be a file associated with the Land, + // in which case the filename will be empty. ESM_Context mContext; int mDataTypes; @@ -64,6 +66,8 @@ struct Land //total number of textures per land static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE; + static const int LAND_GLOBAL_MAP_LOD_SIZE = 81; + #pragma pack(push,1) struct VHGT { @@ -109,12 +113,12 @@ struct Land }; // low-LOD heightmap (used for rendering the global map) - signed char mWnam[81]; + signed char mWnam[LAND_GLOBAL_MAP_LOD_SIZE]; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; - void blank() {} + void blank(); /** * Actually loads data into target @@ -128,9 +132,11 @@ struct Land void unloadData() const; /// Check if given data type is loaded - /// @note We only check data types that *can* be loaded (present in mDataTypes) bool isDataLoaded(int flags) const; + /// Sets the flags and creates a LandData if needed + void setDataLoaded(int flags); + Land (const Land& land); Land& operator= (Land land); diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index 613b706e3..3e150f9c0 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -59,7 +59,7 @@ namespace ESM void LandTexture::blank() { + mId.clear(); mTexture.clear(); - mIndex = -1; } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 2cb5abf0c..1604e9281 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -31,14 +31,15 @@ struct LandTexture /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "LandTexture"; } + // mId is merely a user friendly name for the texture in the editor. std::string mId, mTexture; int mIndex; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; + /// Sets the record to the default state. Does not touch the index. Does touch mID. void blank(); - ///< Set record to default state (does not touch the ID). }; } #endif diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 3a918cf19..9f8ad94e1 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -380,6 +380,7 @@ static std::map genNameMap() names[131] ="sEffectBoundGloves"; names[128] ="sEffectBoundHelm"; names[125] ="sEffectBoundLongbow"; + names[126] ="sEffectExtraSpell"; names[121] ="sEffectBoundLongsword"; names[122] ="sEffectBoundMace"; names[130] ="sEffectBoundShield"; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 5b89f4055..d7f30e079 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -43,10 +43,12 @@ struct NPC Misc = 0x00400, Potions = 0x02000, + AllItems = Weapon|Armor|Clothing|Books|Ingredients|Picks|Probes|Lights|Apparatus|RepairItem|Misc|Potions, + // Other services Spells = 0x00800, MagicItems = 0x01000, - Training = 0x04000, // What skills? + Training = 0x04000, Spellmaking = 0x08000, Enchanting = 0x10000, Repair = 0x20000 diff --git a/components/esm/projectilestate.cpp b/components/esm/projectilestate.cpp index 70ae4c5ee..8ade9d5b2 100644 --- a/components/esm/projectilestate.cpp +++ b/components/esm/projectilestate.cpp @@ -27,11 +27,7 @@ namespace ESM BaseProjectileState::save(esm); esm.writeHNString ("SPEL", mSpellId); - esm.writeHNString ("SRCN", mSourceName); - mEffects.save(esm); esm.writeHNT ("SPED", mSpeed); - esm.writeHNT ("STCK", mStack); - esm.writeHNString ("SOUN", mSound); } void MagicBoltState::load(ESMReader &esm) @@ -39,11 +35,14 @@ namespace ESM BaseProjectileState::load(esm); mSpellId = esm.getHNString("SPEL"); - mSourceName = esm.getHNString ("SRCN"); - mEffects.load(esm); + if (esm.isNextSub("SRCN")) // for backwards compatibility + esm.skipHSub(); + ESM::EffectList().load(esm); // for backwards compatibility esm.getHNT (mSpeed, "SPED"); - esm.getHNT (mStack, "STCK"); - mSound = esm.getHNString ("SOUN"); + if (esm.isNextSub("STCK")) // for backwards compatibility + esm.skipHSub(); + if (esm.isNextSub("SOUN")) // for backwards compatibility + esm.skipHSub(); } void ProjectileState::save(ESMWriter &esm) const diff --git a/components/esm/projectilestate.hpp b/components/esm/projectilestate.hpp index 3471fbfc7..67ec89bb6 100644 --- a/components/esm/projectilestate.hpp +++ b/components/esm/projectilestate.hpp @@ -31,11 +31,7 @@ namespace ESM struct MagicBoltState : public BaseProjectileState { std::string mSpellId; - std::string mSourceName; - ESM::EffectList mEffects; float mSpeed; - bool mStack; - std::string mSound; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 5316255ad..7c3956a29 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -75,8 +75,6 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { path = it->string(); - boost::erase_all(path, "\""); - *it = boost::filesystem::path(path); // Check if path contains a token if (!path.empty() && *path.begin() == '?') diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index 4f76139d9..b239ec6a1 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -8,7 +8,7 @@ namespace { // somewhat arbitrary though 64KB buffers didn't seem to improve performance any -const size_t sBufferSize = 4096; +const size_t sBufferSize = 8192; } namespace Files diff --git a/components/files/escape.cpp b/components/files/escape.cpp index f28870c70..93ae9b885 100644 --- a/components/files/escape.cpp +++ b/components/files/escape.cpp @@ -1,6 +1,6 @@ #include "escape.hpp" -#include +#include namespace Files { @@ -8,7 +8,7 @@ namespace Files const int escape_hash_filter::sEscapeIdentifier = 'a'; const int escape_hash_filter::sHashIdentifier = 'h'; - escape_hash_filter::escape_hash_filter() : mNext(), mPrevious(), mSeenNonWhitespace(false), mFinishLine(false) + escape_hash_filter::escape_hash_filter() : mSeenNonWhitespace(false), mFinishLine(false) { } @@ -26,8 +26,14 @@ namespace Files std::string EscapeHashString::processString(const std::string & str) { - std::string temp = boost::replace_all_copy(str, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sHashIdentifier, "#"); - boost::replace_all(temp, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sEscapeIdentifier, std::string((char)escape_hash_filter::sEscape, 1)); + std::string temp = str; + + static const char hash[] = { escape_hash_filter::sEscape, escape_hash_filter::sHashIdentifier }; + Misc::StringUtils::replaceAll(temp, hash, "#", 2, 1); + + static const char escape[] = { escape_hash_filter::sEscape, escape_hash_filter::sEscapeIdentifier }; + Misc::StringUtils::replaceAll(temp, escape, "@", 2, 1); + return temp; } diff --git a/components/files/escape.hpp b/components/files/escape.hpp index 2017c2ed2..d01bd8d98 100644 --- a/components/files/escape.hpp +++ b/components/files/escape.hpp @@ -30,7 +30,6 @@ namespace Files private: std::queue mNext; - int mPrevious; bool mSeenNonWhitespace; bool mFinishLine; @@ -42,11 +41,9 @@ namespace Files if (mNext.empty()) { int character = boost::iostreams::get(src); - bool record = true; if (character == boost::iostreams::WOULD_BLOCK) { mNext.push(character); - record = false; } else if (character == EOF) { @@ -78,7 +75,7 @@ namespace Files mFinishLine = true; } } - else if (mPrevious == sEscape) + else if (character == sEscape) { mNext.push(sEscape); mNext.push(sEscapeIdentifier); @@ -89,8 +86,6 @@ namespace Files } if (!mSeenNonWhitespace && !isspace(character)) mSeenNonWhitespace = true; - if (record) - mPrevious = character; } int retval = mNext.front(); mNext.pop(); diff --git a/components/misc/messageformatparser.cpp b/components/misc/messageformatparser.cpp index bfd8dd562..3a35c83ea 100644 --- a/components/misc/messageformatparser.cpp +++ b/components/misc/messageformatparser.cpp @@ -34,21 +34,19 @@ namespace Misc if (i < m.size()) { - int precision = 0; - bool precisionSet = false; + int precision = -1; if (m[i] == '.') { + precision = 0; while (++i < m.size() && m[i] >= '0' && m[i] <= '9') { precision = precision * 10 + (m[i] - '0'); - precisionSet = true; } } if (i < m.size()) { width = (widthSet) ? width : -1; - precision = (precisionSet) ? precision : -1; if (m[i] == 'S' || m[i] == 's') visitedPlaceholder(StringPlaceholder, pad, width, precision); diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 9acd81710..9f4931d72 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -2,6 +2,7 @@ #define MISC_STRINGOPS_H #include +#include #include #include @@ -138,6 +139,35 @@ public: return notFound; } + + /** @brief Replaces all occurrences of a string in another string. + * + * @param str The string to operate on. + * @param what The string to replace. + * @param with The replacement string. + * @param whatLen The length of the string to replace. + * @param withLen The length of the replacement string. + * + * @return A reference to the string passed in @p str. + */ + static std::string &replaceAll(std::string &str, const char *what, const char *with, + std::size_t whatLen=std::string::npos, std::size_t withLen=std::string::npos) + { + if (whatLen == std::string::npos) + whatLen = strlen(what); + + if (withLen == std::string::npos) + withLen = strlen(with); + + std::size_t found; + std::size_t offset = 0; + while((found = str.find(what, offset, whatLen)) != std::string::npos) + { + str.replace(found, whatLen, with, withLen); + offset = found + withLen; + } + return str; + } }; } diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 760015902..368374a64 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -18,6 +18,11 @@ public: { } + Utf8Stream (const char * str) : + cur ((unsigned char*) str), nxt ((unsigned char*) str), end ((unsigned char*) str + strlen(str)), val(Utf8Stream::sBadChar()) + { + } + Utf8Stream (std::pair range) : cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar()) { diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index d0fc9bab0..08a7466fa 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -6,138 +6,8 @@ namespace Nif { //Private functions -uint8_t NIFStream::read_byte() -{ - uint8_t byte; - inp->read((char*)&byte, 1); - return byte; -} -uint16_t NIFStream::read_le16() -{ - uint8_t buffer[2]; - inp->read((char*)buffer, 2); - return buffer[0] | (buffer[1]<<8); -} -uint32_t NIFStream::read_le32() -{ - uint8_t buffer[4]; - inp->read((char*)buffer, 4); - return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); -} -float NIFStream::read_le32f() -{ - union { - uint32_t i; - float f; - } u = { read_le32() }; - return u.f; -} -//Public functions -osg::Vec2f NIFStream::getVector2() -{ - osg::Vec2f vec; - for(size_t i = 0;i < 2;i++) - vec._v[i] = getFloat(); - return vec; -} -osg::Vec3f NIFStream::getVector3() -{ - osg::Vec3f vec; - for(size_t i = 0;i < 3;i++) - vec._v[i] = getFloat(); - return vec; -} -osg::Vec4f NIFStream::getVector4() -{ - osg::Vec4f vec; - for(size_t i = 0;i < 4;i++) - vec._v[i] = getFloat(); - return vec; -} -Matrix3 NIFStream::getMatrix3() -{ - Matrix3 mat; - for(size_t i = 0;i < 3;i++) - { - for(size_t j = 0;j < 3;j++) - mat.mValues[i][j] = getFloat(); - } - return mat; -} -osg::Quat NIFStream::getQuaternion() -{ - osg::Quat quat; - quat.w() = getFloat(); - quat.x() = getFloat(); - quat.y() = getFloat(); - quat.z() = getFloat(); - return quat; -} -Transformation NIFStream::getTrafo() -{ - Transformation t; - t.pos = getVector3(); - t.rotation = getMatrix3(); - t.scale = getFloat(); - return t; -} -std::string NIFStream::getString(size_t length) -{ - std::vector str (length+1, 0); - - inp->read(&str[0], length); - - return &str[0]; -} -std::string NIFStream::getString() -{ - size_t size = read_le32(); - return getString(size); -} -std::string NIFStream::getVersionString() -{ - std::string result; - std::getline(*inp, result); - return result; -} - -void NIFStream::getUShorts(std::vector &vec, size_t size) -{ - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getUShort(); -} -void NIFStream::getFloats(std::vector &vec, size_t size) -{ - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getFloat(); -} -void NIFStream::getVector2s(std::vector &vec, size_t size) -{ - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector2(); -} -void NIFStream::getVector3s(std::vector &vec, size_t size) -{ - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector3(); -} -void NIFStream::getVector4s(std::vector &vec, size_t size) -{ - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector4(); -} -void NIFStream::getQuaternions(std::vector &quat, size_t size) -{ - quat.resize(size); - for(size_t i = 0;i < quat.size();i++) - quat[i] = getQuaternion(); -} +//Public functions } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 860c62e64..d00069be7 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -21,16 +21,70 @@ namespace Nif class NIFFile; +/* + readLittleEndianBufferOfType: This template should only be used with non POD data types +*/ +template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) +{ +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) + pIStream->read((char*)dest, numInstances * sizeof(T)); +#else + uint8_t* destByteBuffer = (uint8_t*)dest; + pIStream->read((char*)dest, numInstances * sizeof(T)); + /* + Due to the loop iterations being known at compile time, + this nested loop will most likely be unrolled + For example, for 2 instances of a 4 byte data type, you should get the below result + */ + union { + IntegerT i; + T t; + } u; + for (uint32_t i = 0; i < numInstances; i++) + { + u = { 0 }; + for (uint32_t byte = 0; byte < sizeof(T); byte++) + u.i |= (((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8)); + dest[i] = u.t; + } +#endif +} + +/* + readLittleEndianDynamicBufferOfType: This template should only be used with non POD data types +*/ +template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) +{ +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) + pIStream->read((char*)dest, numInstances * sizeof(T)); +#else + uint8_t* destByteBuffer = (uint8_t*)dest; + pIStream->read((char*)dest, numInstances * sizeof(T)); + union { + IntegerT i; + T t; + } u; + for (uint32_t i = 0; i < numInstances; i++) + { + u.i = 0; + for (uint32_t byte = 0; byte < sizeof(T); byte++) + u.i |= ((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8); + dest[i] = u.t; + } +#endif +} +template type inline readLittleEndianType(Files::IStreamPtr &pIStream) +{ + type val; + readLittleEndianBufferOfType<1,type,IntegerT>(pIStream, (type*)&val); + return val; +} + class NIFStream { /// Input stream Files::IStreamPtr inp; - uint8_t read_byte(); - uint16_t read_le16(); - uint32_t read_le32(); - float read_le32f(); - public: NIFFile * const file; @@ -39,33 +93,117 @@ public: void skip(size_t size) { inp->ignore(size); } - char getChar() { return read_byte(); } - short getShort() { return read_le16(); } - unsigned short getUShort() { return read_le16(); } - int getInt() { return read_le32(); } - unsigned int getUInt() { return read_le32(); } - float getFloat() { return read_le32f(); } - - osg::Vec2f getVector2(); - osg::Vec3f getVector3(); - osg::Vec4f getVector4(); - Matrix3 getMatrix3(); - osg::Quat getQuaternion(); - Transformation getTrafo(); + char getChar() + { + return readLittleEndianType(inp); + } + short getShort() + { + return readLittleEndianType(inp); + } + unsigned short getUShort() + { + return readLittleEndianType(inp); + } + int getInt() + { + return readLittleEndianType(inp); + } + unsigned int getUInt() + { + return readLittleEndianType(inp); + } + float getFloat() + { + return readLittleEndianType(inp); + } + + osg::Vec2f getVector2() { + osg::Vec2f vec; + readLittleEndianBufferOfType<2,float,uint32_t>(inp, (float*)&vec._v[0]); + return vec; + } + osg::Vec3f getVector3() { + osg::Vec3f vec; + readLittleEndianBufferOfType<3, float,uint32_t>(inp, (float*)&vec._v[0]); + return vec; + } + osg::Vec4f getVector4() { + osg::Vec4f vec; + readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&vec._v[0]); + return vec; + } + Matrix3 getMatrix3() { + Matrix3 mat; + readLittleEndianBufferOfType<9, float,uint32_t>(inp, (float*)&mat.mValues); + return mat; + } + osg::Quat getQuaternion() { + float f[4]; + readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f); + osg::Quat quat; + quat.w() = f[0]; + quat.x() = f[1]; + quat.y() = f[2]; + quat.z() = f[3]; + return quat; + } + Transformation getTrafo() { + Transformation t; + t.pos = getVector3(); + t.rotation = getMatrix3(); + t.scale = getFloat(); + return t; + } ///Read in a string of the given length - std::string getString(size_t length); + std::string getString(size_t length) { + std::vector str(length + 1, 0); + + inp->read(&str[0], length); + + return &str[0]; + } ///Read in a string of the length specified in the file - std::string getString(); + std::string getString() { + size_t size = readLittleEndianType(inp); + return getString(size); + } ///This is special since the version string doesn't start with a number, and ends with "\n" - std::string getVersionString(); - - void getUShorts(std::vector &vec, size_t size); - void getFloats(std::vector &vec, size_t size); - void getVector2s(std::vector &vec, size_t size); - void getVector3s(std::vector &vec, size_t size); - void getVector4s(std::vector &vec, size_t size); - void getQuaternions(std::vector &quat, size_t size); + std::string getVersionString() { + std::string result; + std::getline(*inp, result); + return result; + } + + void getUShorts(std::vector &vec, size_t size) { + vec.resize(size); + readLittleEndianDynamicBufferOfType(inp, &vec.front(), size); + } + void getFloats(std::vector &vec, size_t size) { + vec.resize(size); + readLittleEndianDynamicBufferOfType(inp, &vec.front(), size); + } + void getVector2s(std::vector &vec, size_t size) { + vec.resize(size); + /* The packed storage of each Vec2f is 2 floats exactly */ + readLittleEndianDynamicBufferOfType(inp,(float*) &vec.front(), size*2); + } + void getVector3s(std::vector &vec, size_t size) { + vec.resize(size); + /* The packed storage of each Vec3f is 3 floats exactly */ + readLittleEndianDynamicBufferOfType(inp, (float*) &vec.front(), size*3); + } + void getVector4s(std::vector &vec, size_t size) { + vec.resize(size); + /* The packed storage of each Vec4f is 4 floats exactly */ + readLittleEndianDynamicBufferOfType(inp, (float*) &vec.front(), size*4); + } + void getQuaternions(std::vector &quat, size_t size) { + quat.resize(size); + for (size_t i = 0;i < quat.size();i++) + quat[i] = getQuaternion(); + } }; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 3865b17a5..9e9fe3759 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -49,7 +49,7 @@ BulletNifLoader::~BulletNifLoader() { } -osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr nif) +osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& nif) { mShape = new Resource::BulletShape; diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index a30bf8fdf..fff51933f 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -50,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); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8810f171a..a3b81338e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1191,8 +1191,8 @@ namespace NifOsg if (pixelData->mipmaps.empty()) return NULL; - unsigned int width = 0; - unsigned int height = 0; + int width = 0; + int height = 0; std::vector mipmapVector; for (unsigned int i=0; imipmaps.size()-3; ++i) diff --git a/components/openmw-mp/Utils.cpp b/components/openmw-mp/Utils.cpp index 14a194268..a4a4b6e7b 100644 --- a/components/openmw-mp/Utils.cpp +++ b/components/openmw-mp/Utils.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,41 @@ unsigned int ::Utils::crc32Checksum(const std::string &file) return crc32.checksum(); } +void Utils::printVersion(std::string appName, std::string version, std::string commitHash, int protocol) +{ + cout << appName << " " << version; + cout << " ("; +#if defined(_WIN32) + cout << "Windows"; +#elif defined(__linux) + cout << "Linux"; +#elif defined(__APPLE__) + cout << "OS X"; +#else + cout << "Unknown OS"; +#endif + cout << " "; +#if defined(__x86_64__) || defined(_M_X64) + cout << "64-bit"; +#elif defined(__i386__) || defined(_M_I86) + cout << "32-bit"; +#elif defined(__ARM_ARCH) + cout << "ARMv" << __ARM_ARCH << " "; +#ifdef __aarch64__ + cout << "64-bit"; +#else + cout << "32-bit"; +#endif +#else + cout << "Unknown architecture"; +#endif + cout << ")" << endl; + cout << "Protocol version: " << protocol << endl; + cout << "Commit hash: " << commitHash.substr(0, 10) << endl; + + cout << "------------------------------------------------------------" << endl; +} + void Utils::printWithWidth(ostringstream &sstr, string str, size_t width) { sstr << left << setw(width) << setfill(' ') << str; diff --git a/components/openmw-mp/Utils.hpp b/components/openmw-mp/Utils.hpp index 00cbd8cab..448cb13d7 100644 --- a/components/openmw-mp/Utils.hpp +++ b/components/openmw-mp/Utils.hpp @@ -36,6 +36,7 @@ namespace Utils unsigned int crc32Checksum(const std::string &file); + void printVersion(std::string appName, std::string version, std::string commitHash, int protocol); void printWithWidth(std::ostringstream &sstr, std::string str, size_t width); std::string intToHexStr(unsigned val); diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index c1a7eb8f3..a3d09311a 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -63,7 +63,7 @@ class NodeToShapeVisitor : public osg::NodeVisitor public: NodeToShapeVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mTriangleMesh(NULL) + , mTriangleMesh(nullptr) { } @@ -71,11 +71,11 @@ public: virtual void apply(osg::Drawable &drawable) { if (!mTriangleMesh) - mTriangleMesh = new btTriangleMesh; + mTriangleMesh.reset(new btTriangleMesh); osg::Matrixf worldMat = osg::computeLocalToWorld(getNodePath()); osg::TriangleFunctor functor; - functor.setTriMesh(mTriangleMesh); + functor.setTriMesh(mTriangleMesh.get()); functor.setMatrix(worldMat); drawable.accept(functor); } @@ -86,14 +86,12 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - TriangleMeshShape* meshShape = new TriangleMeshShape(mTriangleMesh, true); - shape->mCollisionShape = meshShape; - mTriangleMesh = NULL; + shape->mCollisionShape = new TriangleMeshShape(mTriangleMesh.release(), true); return shape; } private: - btTriangleMesh* mTriangleMesh; + std::unique_ptr mTriangleMesh; }; BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) @@ -141,10 +139,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & node->accept(visitor); shape = visitor.getShape(); if (!shape) - { - mCache->addEntryToObjectCache(normalized, NULL); return osg::ref_ptr(); - } } mCache->addEntryToObjectCache(normalized, shape); @@ -158,7 +153,8 @@ osg::ref_ptr BulletShapeManager::cacheInstance(const std::s mVFS->normalizeFilename(normalized); osg::ref_ptr instance = createInstance(normalized); - mInstanceCache->addEntryToObjectCache(normalized, instance.get()); + if (instance) + mInstanceCache->addEntryToObjectCache(normalized, instance.get()); return instance; } diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 441fd8212..000a833cf 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -69,7 +69,6 @@ namespace Resource // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) { - std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; return false; } break; @@ -123,12 +122,31 @@ namespace Resource return mWarningImage; } - osg::Image* image = result.getImage(); + osg::ref_ptr image = result.getImage(); + image->setFileName(normalized); if (!checkSupported(image, filename)) { - mCache->addEntryToObjectCache(normalized, mWarningImage); - return mWarningImage; + static bool uncompress = (getenv("OPENMW_DECOMPRESS_TEXTURES") != 0); + if (!uncompress) + { + std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); + return mWarningImage; + } + else + { + // decompress texture in software if not supported by GPU + // requires update to getColor() to be released with OSG 3.6 + osg::ref_ptr newImage = new osg::Image; + newImage->setFileName(image->getFileName()); + newImage->allocateImage(image->s(), image->t(), image->r(), image->isImageTranslucent() ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE); + for (int s=0; ss(); ++s) + for (int t=0; tt(); ++t) + for (int r=0; rr(); ++r) + newImage->setColor(image->getColor(s,t,r), s,t,r); + image = newImage; + } } mCache->addEntryToObjectCache(normalized, image); diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index 266139f3c..d6639d3ae 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -51,6 +51,11 @@ namespace Resource void MultiObjectCache::addEntryToObjectCache(const std::string &filename, osg::Object *object) { + if (!object) + { + OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl; + return; + } OpenThreads::ScopedLock lock(_objectCacheMutex); _objectCache.insert(std::make_pair(filename, object)); } diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index de0fa7a40..e8c082f91 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -34,6 +34,11 @@ ObjectCache::~ObjectCache() void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp) { + if (!object) + { + OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl; + return; + } OpenThreads::ScopedLock lock(_objectCacheMutex); _objectCache[filename]=ObjectTimeStampPair(object,timestamp); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ab801ab82..03b850fac 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -244,12 +244,9 @@ namespace Resource void SceneManager::recreateShaders(osg::ref_ptr node) { - Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); - shaderVisitor.setForceShaders(mForceShaders); - shaderVisitor.setClampLighting(mClampLighting); - shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); - shaderVisitor.setAllowedToModifyStateSets(false); - node->accept(shaderVisitor); + osg::ref_ptr shaderVisitor(createShaderVisitor()); + shaderVisitor->setAllowedToModifyStateSets(false); + node->accept(*shaderVisitor); } void SceneManager::setClampLighting(bool clamp) @@ -435,13 +432,18 @@ namespace Resource bool canOptimize(const std::string& filename) { - // xmesh.nif can not be optimized because there are keyframes added in post size_t slashpos = filename.find_last_of("\\/"); if (slashpos != std::string::npos && slashpos+1 < filename.size()) { std::string basename = filename.substr(slashpos+1); + // xmesh.nif can not be optimized because there are keyframes added in post if (!basename.empty() && basename[0] == 'x') return false; + + // NPC skeleton files can not be optimized because of keyframes added in post + // (most of them are usually named like 'xbase_anim.nif' anyway, but not all of them :( ) + if (basename.compare(0, 9, "base_anim") == 0 || basename.compare(0, 4, "skin") == 0) + return false; } // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly cautious - instead, decide on filename @@ -516,16 +518,8 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); - shaderVisitor.setForceShaders(mForceShaders); - shaderVisitor.setClampLighting(mClampLighting); - shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); - shaderVisitor.setAutoUseNormalMaps(mAutoUseNormalMaps); - shaderVisitor.setNormalMapPattern(mNormalMapPattern); - shaderVisitor.setNormalHeightMapPattern(mNormalHeightMapPattern); - shaderVisitor.setAutoUseSpecularMaps(mAutoUseSpecularMaps); - shaderVisitor.setSpecularMapPattern(mSpecularMapPattern); - loaded->accept(shaderVisitor); + osg::ref_ptr shaderVisitor (createShaderVisitor()); + loaded->accept(*shaderVisitor); // share state // do this before optimizing so the optimizer will be able to combine nodes more aggressively @@ -748,4 +742,18 @@ namespace Resource stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } + Shader::ShaderVisitor *SceneManager::createShaderVisitor() + { + Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor->setForceShaders(mForceShaders); + shaderVisitor->setClampLighting(mClampLighting); + shaderVisitor->setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); + shaderVisitor->setNormalMapPattern(mNormalMapPattern); + shaderVisitor->setNormalHeightMapPattern(mNormalHeightMapPattern); + shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); + shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); + return shaderVisitor; + } + } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 65524f76e..4f1523ece 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -31,6 +31,7 @@ namespace osgDB namespace Shader { class ShaderManager; + class ShaderVisitor; } namespace Resource @@ -141,14 +142,16 @@ namespace Resource void setUnRefImageDataAfterApply(bool unref); /// @see ResourceManager::updateCache - virtual void updateCache(double referenceTime); + void updateCache(double referenceTime) override; - virtual void clearCache(); + void clearCache() override; - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: + Shader::ShaderVisitor* createShaderVisitor(); + std::unique_ptr mShaderManager; bool mForceShaders; bool mClampLighting; diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 2ffbace3b..1b7e4ca93 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -62,10 +62,7 @@ void MorphGeometry::dirty() { mDirty = true; if (!mMorphedBoundingBox) - { - _boundingBoxComputed = false; dirtyBound(); - } } osg::ref_ptr MorphGeometry::getSourceGeometry() const diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 3add3bb23..eec302965 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -42,13 +42,4 @@ osg::Vec4f colourFromRGB(unsigned int clr) return colour; } -osg::Vec4f colourFromRGBA(unsigned int clr) -{ - osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f, - ((clr >> 8) & 0xFF) / 255.0f, - ((clr >> 16) & 0xFF) / 255.0f, - ((clr >> 24) & 0xFF) / 255.0f); - return colour; -} - } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index d8fefdb29..109099740 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -15,8 +15,6 @@ namespace SceneUtil osg::Vec4f colourFromRGB (unsigned int clr); - osg::Vec4f colourFromRGBA (unsigned int clr); - } #endif diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 74b9be63d..2f6123e34 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -19,6 +19,14 @@ namespace SceneUtil return false; } + void FindByClassVisitor::apply(osg::Node &node) + { + if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) + mFoundNodes.push_back(&node); + + traverse(node); + } + void FindByNameVisitor::apply(osg::Group &group) { if (!checkGroup(group)) diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 209f2d9bd..265fd6d02 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -20,7 +20,6 @@ namespace SceneUtil } virtual void apply(osg::Group& group); - virtual void apply(osg::MatrixTransform& node); virtual void apply(osg::Geometry& node); @@ -30,6 +29,21 @@ namespace SceneUtil osg::Group* mFoundNode; }; + class FindByClassVisitor : public osg::NodeVisitor + { + public: + FindByClassVisitor(const std::string& nameToFind) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mNameToFind(nameToFind) + { + } + + virtual void apply(osg::Node &node); + + std::string mNameToFind; + std::vector mFoundNodes; + }; + // Disable freezeOnCull for all visited particlesystems class DisableFreezeOnCullVisitor : public osg::NodeVisitor { diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index cc40506f0..2cd1ec806 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -119,6 +119,7 @@ unsigned int WorkQueue::getNumActiveThreads() const WorkThread::WorkThread(WorkQueue *workQueue) : mWorkQueue(workQueue) + , mActive(false) { } diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index a76de00d1..634c4884f 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -85,14 +85,11 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v mMouseListener->mouseReleased(evt.button, evt.button.button); break; case SDL_KEYDOWN: - if (!evt.key.repeat) - { - mKeyboardListener->keyPressed(evt.key); + mKeyboardListener->keyPressed(evt.key); - if (!isModifierHeld(KMOD_ALT) && evt.key.keysym.sym >= SDLK_F1 && evt.key.keysym.sym <= SDLK_F12) - { - mViewer->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_F1 + (evt.key.keysym.sym - SDLK_F1)); - } + if (!isModifierHeld(KMOD_ALT) && evt.key.keysym.sym >= SDLK_F1 && evt.key.keysym.sym <= SDLK_F12) + { + mViewer->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_F1 + (evt.key.keysym.sym - SDLK_F1)); } break; diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index e93642ee2..2e7b5a8ae 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -292,7 +292,7 @@ public: 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; + ostream << "# http://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << std::endl; } // We still have one more thing to do before we're completely done writing the file. diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 7cb49c6cb..5efd1b86e 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -131,7 +131,11 @@ namespace Shader { std::string shaderSource = templateIt->second; if (!parseDefines(shaderSource, defines)) + { + // Add to the cache anyway to avoid logging the same error over and over. + mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr)); return NULL; + } osg::ref_ptr shader (new osg::Shader(shaderType)); shader->setShaderSource(shaderSource); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 46531f23e..d8c4fd084 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -32,9 +32,9 @@ namespace Terrain osg::ref_ptr getChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags); - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; - virtual void clearCache(); + void clearCache() override; void releaseGLObjects(osg::State* state) override; diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 8aec54835..640f2932b 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -116,7 +116,10 @@ namespace Terrain osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) - throw std::runtime_error("Unable to create shader"); + { + // Try again without shader. Error already logged by above + return createPasses(false, forcePerPixelLighting, clampLighting, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); + } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); } diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index b7cc0ae01..335bb496b 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -75,4 +75,9 @@ void World::updateTextureFiltering() mTextureManager->updateTextureFiltering(); } +void World::clearAssociatedCaches() +{ + mChunkManager->clearCache(); +} + } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index d0576fbd3..e1c3828fc 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -63,6 +63,10 @@ namespace Terrain float getHeightAt (const osg::Vec3f& worldPos); + /// Clears the cached land and landtexture data. + /// @note Thread safe. + virtual void clearAssociatedCaches(); + /// Load a terrain cell at maximum LOD and store it in the View for later use. /// @note Thread safe. virtual void cacheCell(View* view, int x, int y) {} diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 0ce8ce951..bf18cef5b 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -66,6 +66,13 @@ namespace Gui notifySizeChange (this); } + void AutoSizedEditBox::initialiseOverride() + { + Base::initialiseOverride(); + setNeedKeyFocus(false); + setEditStatic(true); + } + void AutoSizedEditBox::setPropertyOverride(const std::string& _key, const std::string& _value) { if (_key == "ExpandDirection") @@ -171,9 +178,11 @@ namespace Gui total_width += mSpacing; } - if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height)) + if (mAutoResize && (total_width+mPadding*2 != getClientCoord().width || total_height+mPadding*2 != getClientCoord().height)) { - setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2)); + int xmargin = getSize().width - getClientCoord().width; + int ymargin = getSize().height - getClientCoord().height; + setSize(MyGUI::IntSize(total_width+mPadding*2 + xmargin, total_height+mPadding*2 + ymargin)); return; } @@ -191,19 +200,19 @@ namespace Gui continue; bool vstretch = w->getUserString ("VStretch") == "true"; - int max_height = getSize().height - mPadding*2; + int max_height = getClientCoord().height - mPadding*2; int height = vstretch ? max_height : sizes[i].first.height; MyGUI::IntCoord widgetCoord; widgetCoord.left = curX; - widgetCoord.top = mPadding + (getSize().height-mPadding*2 - height) / 2; + widgetCoord.top = mPadding + (getClientCoord().height-mPadding*2 - height) / 2; int width = 0; if (sizes[i].second) { if (h_stretched_count == 0) throw std::logic_error("unexpected"); - width = sizes[i].first.width + (getSize().width-mPadding*2 - total_width)/h_stretched_count; + width = sizes[i].first.width + (getClientCoord().width-mPadding*2 - total_width)/h_stretched_count; } else width = sizes[i].first.width; @@ -236,6 +245,14 @@ namespace Gui align(); } + void HBox::initialiseOverride() + { + Base::initialiseOverride(); + MyGUI::Widget* client = 0; + assignWidget(client, "Client"); + setWidgetClient(client); + } + void HBox::onWidgetCreated(MyGUI::Widget* _widget) { align(); @@ -317,13 +334,14 @@ namespace Gui total_height += mSpacing; } - if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height)) + if (mAutoResize && (total_width+mPadding*2 != getClientCoord().width || total_height+mPadding*2 != getClientCoord().height)) { - setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2)); + int xmargin = getSize().width - getClientCoord().width; + int ymargin = getSize().height - getClientCoord().height; + setSize(MyGUI::IntSize(total_width+mPadding*2 + xmargin, total_height+mPadding*2 + ymargin)); return; } - int curY = 0; for (unsigned int i = 0; i < count; ++i) { @@ -337,19 +355,19 @@ namespace Gui continue; bool hstretch = w->getUserString ("HStretch") == "true"; - int maxWidth = getSize().width - mPadding*2; + int maxWidth = getClientCoord().width - mPadding*2; int width = hstretch ? maxWidth : sizes[i].first.width; MyGUI::IntCoord widgetCoord; widgetCoord.top = curY; - widgetCoord.left = mPadding + (getSize().width-mPadding*2 - width) / 2; + widgetCoord.left = mPadding + (getClientCoord().width-mPadding*2 - width) / 2; int height = 0; if (sizes[i].second) { if (v_stretched_count == 0) throw std::logic_error("unexpected"); - height = sizes[i].first.height + (getSize().height-mPadding*2 - total_height)/v_stretched_count; + height = sizes[i].first.height + (getClientCoord().height-mPadding*2 - total_height)/v_stretched_count; } else height = sizes[i].first.height; @@ -382,6 +400,14 @@ namespace Gui align(); } + void VBox::initialiseOverride() + { + Base::initialiseOverride(); + MyGUI::Widget* client = 0; + assignWidget(client, "Client"); + setWidgetClient(client); + } + MyGUI::IntSize VBox::getRequestedSize () { MyGUI::IntSize size(0,0); diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index 70f73ce42..66be00719 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -39,9 +39,12 @@ namespace Gui MYGUI_RTTI_DERIVED( AutoSizedEditBox ) public: + virtual MyGUI::IntSize getRequestedSize(); virtual void setCaption(const MyGUI::UString& _value); + virtual void initialiseOverride(); + protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); }; @@ -99,6 +102,8 @@ namespace Gui virtual void setCoord (const MyGUI::IntCoord &_value); protected: + virtual void initialiseOverride(); + virtual void align(); virtual MyGUI::IntSize getRequestedSize(); @@ -116,6 +121,8 @@ namespace Gui virtual void setCoord (const MyGUI::IntCoord &_value); protected: + virtual void initialiseOverride(); + virtual void align(); virtual MyGUI::IntSize getRequestedSize(); diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index 8e3f8ed69..ab0739c2c 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -5,6 +5,15 @@ namespace Gui { + ImageButton::ImageButton() + : Base() + , mMouseFocus(false) + , mMousePress(false) + , mKeyFocus(false) + { + setNeedKeyFocus(true); + } + void ImageButton::setPropertyOverride(const std::string &_key, const std::string &_value) { if (_key == "ImageHighlighted") @@ -24,22 +33,36 @@ namespace Gui } void ImageButton::onMouseSetFocus(Widget* _old) { - setImageTexture(mImageHighlighted); - ImageBox::onMouseSetFocus(_old); + mMouseFocus = true; + updateImage(); + Base::onMouseSetFocus(_old); } void ImageButton::onMouseLostFocus(Widget* _new) { - setImageTexture(mImageNormal); - ImageBox::onMouseLostFocus(_new); + mMouseFocus = false; + updateImage(); + Base::onMouseLostFocus(_new); } void ImageButton::onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) { if (_id == MyGUI::MouseButton::Left) - setImageTexture(mImagePushed); + { + mMousePress = true; + updateImage(); + } + Base::onMouseButtonPressed(_left, _top, _id); + } - ImageBox::onMouseButtonPressed(_left, _top, _id); + void ImageButton::updateImage() + { + if (mMousePress) + setImageTexture(mImagePushed); + else if (mMouseFocus || mKeyFocus) + setImageTexture(mImageHighlighted); + else + setImageTexture(mImageNormal); } MyGUI::IntSize ImageButton::getRequestedSize() @@ -70,8 +93,23 @@ namespace Gui void ImageButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) { if (_id == MyGUI::MouseButton::Left) - setImageTexture(mImageHighlighted); + { + mMousePress = false; + updateImage(); + } + + Base::onMouseButtonReleased(_left, _top, _id); + } - ImageBox::onMouseButtonReleased(_left, _top, _id); + void ImageButton::onKeySetFocus(MyGUI::Widget *_old) + { + mKeyFocus = true; + updateImage(); + } + + void ImageButton::onKeyLostFocus(MyGUI::Widget *_new) + { + mKeyFocus = false; + updateImage(); } } diff --git a/components/widgets/imagebutton.hpp b/components/widgets/imagebutton.hpp index a539f15c9..509b1c8c2 100644 --- a/components/widgets/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -16,19 +16,30 @@ namespace Gui public: MyGUI::IntSize getRequestedSize(); + ImageButton(); + /// 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); + private: + void updateImage(); + protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); virtual void onMouseLostFocus(MyGUI::Widget* _new); virtual void onMouseSetFocus(MyGUI::Widget* _old); virtual void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id); virtual void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id); + virtual void onKeySetFocus(MyGUI::Widget* _old); + virtual void onKeyLostFocus(MyGUI::Widget* _new); std::string mImageHighlighted; std::string mImageNormal; std::string mImagePushed; + + bool mMouseFocus; + bool mMousePress; + bool mKeyFocus; }; } diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index c5a459f22..9318e32ed 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -72,6 +72,7 @@ namespace Gui button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheelMoved); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); + button->setNeedKeyFocus(true); int height = button->getTextSize().height; button->setSize(MyGUI::IntSize(button->getSize().width, height)); diff --git a/components/widgets/numericeditbox.cpp b/components/widgets/numericeditbox.cpp index 828a84589..ee2c30a7b 100644 --- a/components/widgets/numericeditbox.cpp +++ b/components/widgets/numericeditbox.cpp @@ -75,4 +75,20 @@ namespace Gui setCaption(MyGUI::utility::toString(mValue)); } + void NumericEditBox::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char character) + { + if (key == MyGUI::KeyCode::ArrowUp) + { + setValue(std::min(mValue+1, mMaxValue)); + eventValueChanged(mValue); + } + else if (key == MyGUI::KeyCode::ArrowDown) + { + setValue(std::max(mValue-1, mMinValue)); + eventValueChanged(mValue); + } + else if (character == 0 || (character >= '0' && character <= '9')) + Base::onKeyButtonPressed(key, character); + } + } diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index 20000b3d3..ca7674f22 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -33,6 +33,7 @@ namespace Gui private: void onEditTextChange(MyGUI::EditBox* sender); void onKeyLostFocus(MyGUI::Widget* _new); + void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char character); int mValue; diff --git a/components/widgets/windowcaption.hpp b/components/widgets/windowcaption.hpp index bdd4c0a2e..b45da2d1c 100644 --- a/components/widgets/windowcaption.hpp +++ b/components/widgets/windowcaption.hpp @@ -23,6 +23,7 @@ namespace Gui private: MyGUI::Widget* mLeft; MyGUI::Widget* mRight; + MyGUI::Widget* mClient; void align(); }; diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index bb1097e0c..38915c95b 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -218,6 +218,128 @@ actually modify the contents of the game. Subsection to come... ===================== +Adding to an npc +**************** + +The simplest way is probably to add it to the inventory of a shopkeeper. +An obvious candidate is Arrille in Seyda Neen - he's quick to find in a new game +and he's easy to find in the CS as his name comes early alphabetically. + +.. figure:: _static/images/chapter-1/Ring_to_Arrille.png + :alt: Putting the ring into Arrille's inventory + +Open the CS and open the *Objects* table (*World* → *Objects*). +Scroll down to Arrille, or use a filter like !string("ID","arrille"). + +Open another pane to edit him - either right click and select edit or use the +shortcut (default is shift double-click). Scroll down to the inventory section +and right click to add a new row. Type in the id of the ring (or find it in the +object pane, and drag and drop). Set the number of rings for him to stock - with +a negative number indicating that he will restock again to maintain that level. + +However, it's an attractive item, so he will probably wear it rather than sell it. +So set his stock level too high for him to wear them all (3 works, 2 might do). + +Another possibilty, again in Seyda Neen making it easy to access, would be for +Fargoth to give it to the player in exchange for his healing ring. + +.. figure:: _static/images/chapter-1/Ring_to_Fargoth_1.png + :alt: Editing Fargoth to give ring to player + +Open the *Topicinfo* Table (*Characters* → *Topic Infos*). Use a filter !string(Topic,ring) +and select the row with a response starting with "You found it!". Edit the record, +firstly by adding a bit more to the response, then by adding a line to the script +to give the ring to the player - the same as used earlier in the console +.. code:: + + player->AddItem "ring_night_vision" 1 + +.. figure:: _static/images/chapter-1/Ring_to_Fargoth_2.png + :alt: Editing Fargoth to give ring to player + +Navigation in the CS +==================== +This is probably a suitable place to start talking about how navigation differs from TESCS +in vanilla Morrowind. + +There is advice in Scripting for Dummies, the definitive manual for Morrowind Scripting: +"If you give your scripts a common tag, that will make it easier to jump between the +different scripts of your project, e.g. start every script name with AA_Scriptname +this will put them right at the beginning of the list and keep them neatly together." + +This is valid for the rather poorer navigation facilities there, but it's not sensible for +the OpenMW CS. Some modders took it further, and started script names and object id with numbers, +typically "1", to bring the items even earlier in the default alphabetical sorts. In fact +the CS won't allow names/ids to start with numbers or to include ".". + +There are better options available: + +Filtering, which isn't available at all in TESCS - put in a filter like + +!string("ID",".*ring.*") + +to find all IDs which contain the string "ring" + +Sorting, which is available in some parts of TESCS, but not for scripts (other than script names being +sorted in ascending order)- hence the recommendation +Typically the "Modified" column is useful here - most items will have "Base" status, unchanged from +the base game. + +"Added" status" will cover those items added in this addon. + +"Modified" status will cover items from the base game which have been modified in this addon. + +Click on the top of the column to toggle between ascending and descending order - thus between "Added" +and "Modified" at the top. Or put your desired modified status into a filter then sort alpabetically +on a different column. + + + +Checking your new addon +======================= + +Launch OpenMW and in the launcher under *Data Files* check your addon, if it's not +already checked. Load a game and make your way to Seyda Neen - or start a new game. + +Check whether Arrille has one (or more) for sale, and whether Fargoth give you one +when you return his healing ring. + +Placing in a chest +****************** + +For this example we will use the small chest intended for lockpick practice, +located in the Census and Excise Office in Seyda Neen. + +First we need the ID of the chest - this can be obtained either by clicking on it in the console +in the game, or by applying a similar process in the CS - + +World/Cells + +Select "Seyda Neen, Census and Excise Office" + +Right-click and select "View" + +Use mouse wheel to zoom in/out, and mouse plus WASD keys to navigate + +Click on the small chest + +Either way, you should find the ID, which is "chest_small_02_lockprac". + +Open the Objects table (World/Objects) and scroll down to find this item. + +Alternatively use the Edit/Search facility, selecting ID rather than text, +enter "lockprac" (without the quotes) into the search box, press "Search", +which should return two rows, then select the "Container" one rather than the "Instance" + +Right-click and "Edit Record". + +Right-click the "Content" section and select "Add a row" + +Set the Item ID of the new row to be your new ring - simplest way is probably to open the Objects +table if it's not already open, sort on the "Modified" column which should bring the ring, +with its status of "Added" to the top, then drag and drop to the chest row. +Increase the Count to 1. +Save the addon, then test to ensure it works - e.g. start a new game and lockpick the chest. diff --git a/docs/source/reference/modding/differences.rst b/docs/source/reference/modding/differences.rst index e7cac15d0..d492dc542 100644 --- a/docs/source/reference/modding/differences.rst +++ b/docs/source/reference/modding/differences.rst @@ -12,14 +12,14 @@ OpenMW is designed to be able to use all the normal Morrowind mod files such as Multiple Data Folders --------------------- -The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. This has many advantages, especially when it comes to unistalling mods and preventing unintentional overwrites of files. +The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. This has many advantages, especially when it comes to uninstalling mods and preventing unintentional overwrites of files. .. warning:: Most mods can still be installed into the root OpenMW data folder, but this is not recommended. To install mods via this new feature: -#. Open ``openmw.cfg`` with your preffered text editor. It is located as described in :doc:`paths` and *not* in your OpenMW root directory. +#. Open ``openmw.cfg`` with your preferred text editor. It is located as described in :doc:`paths` and *not* in your OpenMW root directory. #. Find or search for ``data=``. This is located very near the bottom of the file. #. Add a new line below this line and make a new entry of the format ``data="path/to/your/mod"`` #. Make as many of these entries as you need for each mod folder you want to include. diff --git a/docs/source/reference/modding/mod-install.rst b/docs/source/reference/modding/mod-install.rst index a72678173..e62c27fc1 100644 --- a/docs/source/reference/modding/mod-install.rst +++ b/docs/source/reference/modding/mod-install.rst @@ -9,7 +9,7 @@ Install #. Your mod probably comes in some kind of archive, such as ``.zip``, ``.rar``, ``.7z``, or something along those lines. Unpack this archive into its own folder. #. Ensure the structure of this folder is correct. - #. Locate the plugin files, ``.esp`` or ``.omwaddon``. The folder containing the plugin files we will call your *data folder* + #. Locate the plugin files, ``.esp`` or ``.omwaddon``, or possibly ``.esm``. The folder containing the plugin files we will call your *data folder* #. Check that all resource folders (``Meshes``, ``Textures``, etc.) containing additional resource files (the actual meshes, textures, etc.) are in the *data folder*. .. note:: @@ -18,9 +18,10 @@ Install #. Open your ``openmw.cfg`` file in your preferred plain text editor. It is located as described in :doc:`paths` and *not* in your OpenMW root directory. #. Find or search for ``data=``. This is located very near the bottom of the file. If you are using Morrowind, this first entry should already point to your Morrowind data directory, ``Data Files``; otherwise it will point to your game file, ``.omwgame``. #. Create a new line underneath and type: ``data="path/to/your/data folder"`` Remember, the *data folder* is where your mod's plugin files are. The double quotes around this path name are *required*. +#. If your mod contains resources in a ``.bsa`` file, go to near the top of the file, locate the entries like ''fallback-archive=Morrowind.bsa'' and create a new line underneath and type: ``fallback-archive=.bsa''``. .. note:: - Some text editors, such as TextEdit on Mac, will autocorrect your double quotes to typographical "curly" quotes instead of leaving them as the propper neutral vertical quotes ``""``. + Some text editors, such as TextEdit on Mac, will autocorrect your double quotes to typographical "curly" quotes instead of leaving them as the proper neutral vertical quotes ``""``. #. Save your ``openmw.cfg`` file. diff --git a/extern/osg-ffmpeg-videoplayer/License.txt b/extern/osg-ffmpeg-videoplayer/License.txt index 49f534065..bb70d7d37 100644 --- a/extern/osg-ffmpeg-videoplayer/License.txt +++ b/extern/osg-ffmpeg-videoplayer/License.txt @@ -1,4 +1,4 @@ -Copyright (c) 2014 Jannik Heller , Chris Robinson +Copyright (c) 2014 scrawl, Chris Robinson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 0f5e7b5e5..3ff8a1b2b 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -4,7 +4,7 @@ endif() # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) -set(DDIR ${OPENMW_MYGUI_FILES_ROOT}/resources/mygui) +set(DDIRRELATIVE resources/mygui) set(MYGUI_FILES core.skin @@ -105,4 +105,4 @@ set(MYGUI_FILES ) -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${MYGUI_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_MYGUI_FILES_ROOT} ${DDIRRELATIVE} "${MYGUI_FILES}") diff --git a/docs/license/DejaVu Font License.txt b/files/mygui/DejaVu Font License.txt similarity index 100% rename from docs/license/DejaVu Font License.txt rename to files/mygui/DejaVu Font License.txt diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout index 8a1bf1347..a8e5431fb 100644 --- a/files/mygui/openmw_alchemy_window.layout +++ b/files/mygui/openmw_alchemy_window.layout @@ -76,9 +76,7 @@ - - - + diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index 7c158af8d..c83c4982b 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -5,49 +5,46 @@ - - + + + + + + + + - - - - + - - - - - - - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - + + + diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml index 5aee8de7a..ac9bf042d 100644 --- a/files/mygui/openmw_button.skin.xml +++ b/files/mygui/openmw_button.skin.xml @@ -71,6 +71,7 @@ + diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index 2375e5277..a137d83d1 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -11,13 +11,13 @@ - + + + > - - - + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index d875fae22..98c8e4fde 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -74,9 +74,7 @@ - - - + diff --git a/files/mygui/openmw_chargen_class_description.layout b/files/mygui/openmw_chargen_class_description.layout index 43b0518fd..906ca2352 100644 --- a/files/mygui/openmw_chargen_class_description.layout +++ b/files/mygui/openmw_chargen_class_description.layout @@ -4,7 +4,7 @@ - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index e2920c742..c04cfe064 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -75,9 +75,7 @@ - - - + diff --git a/files/mygui/openmw_chargen_generate_class_result.layout b/files/mygui/openmw_chargen_generate_class_result.layout index 83595fafd..d49f57dde 100644 --- a/files/mygui/openmw_chargen_generate_class_result.layout +++ b/files/mygui/openmw_chargen_generate_class_result.layout @@ -3,7 +3,7 @@ - + diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout index 04bd9cc53..31f0436d5 100644 --- a/files/mygui/openmw_chargen_race.layout +++ b/files/mygui/openmw_chargen_race.layout @@ -76,9 +76,7 @@ - - - + diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout index 1099fcd3b..927296fad 100644 --- a/files/mygui/openmw_chargen_review.layout +++ b/files/mygui/openmw_chargen_review.layout @@ -112,9 +112,7 @@ - - - + diff --git a/files/mygui/openmw_confirmation_dialog.layout b/files/mygui/openmw_confirmation_dialog.layout index 39e77cb93..c5eb573a7 100644 --- a/files/mygui/openmw_confirmation_dialog.layout +++ b/files/mygui/openmw_confirmation_dialog.layout @@ -10,6 +10,7 @@ + diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index 854568f9b..a58327a48 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -13,11 +13,13 @@ + + diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index a85bd5b02..1ed399572 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -4,7 +4,7 @@ - + diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout index 376e87efa..c2429d24b 100644 --- a/files/mygui/openmw_edit_effect.layout +++ b/files/mygui/openmw_edit_effect.layout @@ -94,9 +94,7 @@ - - - + diff --git a/files/mygui/openmw_edit_note.layout b/files/mygui/openmw_edit_note.layout index 7039c719e..edf53bde7 100644 --- a/files/mygui/openmw_edit_note.layout +++ b/files/mygui/openmw_edit_note.layout @@ -16,9 +16,7 @@ - - - + diff --git a/files/mygui/openmw_enchanting_dialog.layout b/files/mygui/openmw_enchanting_dialog.layout index 6a08e6fd0..ed3cfb03e 100644 --- a/files/mygui/openmw_enchanting_dialog.layout +++ b/files/mygui/openmw_enchanting_dialog.layout @@ -36,9 +36,7 @@ - - - + @@ -141,9 +139,7 @@ - - - + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 8fe25a5dc..cee3f4682 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -32,19 +32,19 @@ - + - + - - + + - + @@ -82,6 +82,7 @@ + diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index e53493bb1..a1c4f608b 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -8,27 +8,27 @@ - + - + - + - + - + @@ -36,11 +36,11 @@ - + - + @@ -48,11 +48,11 @@ - + - + diff --git a/files/mygui/openmw_interactive_messagebox.layout b/files/mygui/openmw_interactive_messagebox.layout index 50bc178ed..410426656 100644 --- a/files/mygui/openmw_interactive_messagebox.layout +++ b/files/mygui/openmw_interactive_messagebox.layout @@ -9,6 +9,7 @@ + diff --git a/files/mygui/openmw_interactive_messagebox_notransp.layout b/files/mygui/openmw_interactive_messagebox_notransp.layout index 6b79b9417..f5a462977 100644 --- a/files/mygui/openmw_interactive_messagebox_notransp.layout +++ b/files/mygui/openmw_interactive_messagebox_notransp.layout @@ -9,6 +9,7 @@ + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index bb707fa0d..6221799b5 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -32,18 +32,23 @@ + + + + + diff --git a/files/mygui/openmw_jail_screen.layout b/files/mygui/openmw_jail_screen.layout index ef37c73d8..76aa05b86 100644 --- a/files/mygui/openmw_jail_screen.layout +++ b/files/mygui/openmw_jail_screen.layout @@ -1,14 +1,14 @@ - + - + - + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index 1131b1bbc..964b5ea95 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -3,32 +3,27 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -36,11 +31,14 @@ + + + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index cd8a9f760..5e4a74978 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -1,6 +1,7 @@ + diff --git a/files/mygui/openmw_levelup_dialog.layout b/files/mygui/openmw_levelup_dialog.layout index fc11ddfd7..7849d507f 100644 --- a/files/mygui/openmw_levelup_dialog.layout +++ b/files/mygui/openmw_levelup_dialog.layout @@ -50,7 +50,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -121,7 +121,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -149,16 +149,14 @@ - + - - - + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 89b35d05e..69df20022 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -4,13 +4,13 @@ - + - + - + diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index b38097dce..8d45022f8 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -31,6 +31,7 @@ + diff --git a/files/mygui/openmw_map_window.skin.xml b/files/mygui/openmw_map_window.skin.xml index 31a70191f..f6c099630 100644 --- a/files/mygui/openmw_map_window.skin.xml +++ b/files/mygui/openmw_map_window.skin.xml @@ -2,6 +2,7 @@ + diff --git a/files/mygui/openmw_recharge_dialog.layout b/files/mygui/openmw_recharge_dialog.layout index d620aadab..626039065 100644 --- a/files/mygui/openmw_recharge_dialog.layout +++ b/files/mygui/openmw_recharge_dialog.layout @@ -2,7 +2,7 @@ - + @@ -27,9 +27,7 @@ - - - + diff --git a/files/mygui/openmw_repair.layout b/files/mygui/openmw_repair.layout index 340711bb1..bf58b6c81 100644 --- a/files/mygui/openmw_repair.layout +++ b/files/mygui/openmw_repair.layout @@ -2,7 +2,7 @@ - + @@ -34,9 +34,7 @@ - - - + diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout index f18218430..236eaaa61 100644 --- a/files/mygui/openmw_savegame_dialog.layout +++ b/files/mygui/openmw_savegame_dialog.layout @@ -2,7 +2,7 @@ - + @@ -46,6 +46,7 @@ + diff --git a/files/mygui/openmw_scroll.skin.xml b/files/mygui/openmw_scroll.skin.xml index 415c2479a..cff0ad7eb 100644 --- a/files/mygui/openmw_scroll.skin.xml +++ b/files/mygui/openmw_scroll.skin.xml @@ -3,11 +3,13 @@ + + diff --git a/files/mygui/openmw_spellcreation_dialog.layout b/files/mygui/openmw_spellcreation_dialog.layout index e14674ad6..e7c27655d 100644 --- a/files/mygui/openmw_spellcreation_dialog.layout +++ b/files/mygui/openmw_spellcreation_dialog.layout @@ -68,9 +68,7 @@ - - - + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index 163b9d134..5f96d0a57 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -66,6 +66,7 @@ color_misc=0,205,205 # ???? + @@ -81,6 +82,7 @@ color_misc=0,205,205 # ???? + @@ -96,6 +98,7 @@ color_misc=0,205,205 # ???? + @@ -103,6 +106,7 @@ color_misc=0,205,205 # ???? + diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index a7eeabfa5..bdc224ecd 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -32,7 +32,7 @@ - + @@ -44,7 +44,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -103,9 +103,7 @@ - - - + @@ -122,9 +120,7 @@ - - - + @@ -137,7 +133,7 @@ - + @@ -159,7 +155,7 @@ - + @@ -211,7 +207,7 @@ - + @@ -245,7 +241,7 @@ - + @@ -277,7 +273,7 @@ - + diff --git a/files/mygui/openmw_trade_window.layout b/files/mygui/openmw_trade_window.layout index 6d5dfb89b..0af9d59fe 100644 --- a/files/mygui/openmw_trade_window.layout +++ b/files/mygui/openmw_trade_window.layout @@ -3,24 +3,29 @@ - + + + + + + @@ -29,40 +34,47 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - - + + + + + + + + + + + + - - - + + + - + + - + - - - diff --git a/files/mygui/openmw_wait_dialog.layout b/files/mygui/openmw_wait_dialog.layout index 112fa6001..b02db3764 100644 --- a/files/mygui/openmw_wait_dialog.layout +++ b/files/mygui/openmw_wait_dialog.layout @@ -2,7 +2,7 @@ - + @@ -26,9 +26,7 @@ - - - + diff --git a/files/mygui/tes3mp_text_input.layout b/files/mygui/tes3mp_text_input.layout index fd73ab168..7898d3d96 100644 --- a/files/mygui/tes3mp_text_input.layout +++ b/files/mygui/tes3mp_text_input.layout @@ -24,10 +24,20 @@ - - - - + + + + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index a0460326b..253883402 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -6,9 +6,9 @@ # 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: +# the detailed documentation at: # -# https://wiki.openmw.org/index.php?title=Settings +# http://openmw.readthedocs.io/en/master/reference/modding/settings/index.html # [Camera] @@ -313,11 +313,11 @@ 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 +buffer cache min = 56 # 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 +buffer cache max = 64 # Specifies whether to enable HRTF processing. Valid values are: -1 = auto, # 0 = off, 1 = on. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5ca0d1e83..5833a592f 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -4,7 +4,7 @@ endif() # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) -set(DDIR ${OPENMW_SHADERS_ROOT}/resources/shaders) +set(DDIRRELATIVE resources/shaders) set(SHADER_FILES water_vertex.glsl @@ -18,4 +18,4 @@ set(SHADER_FILES parallax.glsl ) -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index c04233fcf..931422d5e 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -6,7 +6,7 @@ // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -const float VISIBILITY = 1200.0; // how far you can look through water +const float VISIBILITY = 2.5; const float BIG_WAVES_X = 0.1; // strength of big waves const float BIG_WAVES_Y = 0.1; @@ -31,15 +31,76 @@ const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness +const float BUMP_SUPPRESS_DEPTH = 0.3; // at what water depth bumpmap will be supressed for reflections and refractions (prevents artifacts at shores) + 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); +// ---------------- rain ripples related stuff --------------------- + +const float RAIN_RIPPLE_GAPS = 5.0; +const float RAIN_RIPPLE_RADIUS = 0.1; + +int modulo(int v1, int v2) +{ + return v1 - v2 * int(floor(float(v1) / float(v2))); +} + +vec2 randOffset(vec2 c) +{ + return fract(vec2( + c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2, + c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7)); +} + +float randPhase(vec2 c) +{ + return fract((c.x * c.y) / (c.x + c.y + 0.1)); +} + +vec4 circle(vec2 coords, vec2 i_part, float phase) +{ + vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part) - 1.0); + vec2 toCenter = coords - center; + float d = length(toCenter); + + float r = RAIN_RIPPLE_RADIUS * phase; + + if (d > r) + return vec4(0.0,0.0,1.0,0.0); + + float sinValue = (sin(d / r * 1.2) + 0.7) / 2.0; + + float height = (1.0 - abs(phase)) * pow(sinValue,3.0); + + vec3 normal = normalize(mix(vec3(0.0,0.0,1.0),vec3(normalize(toCenter),0.0),height)); + + return vec4(normal,height); +} + +vec4 rain(vec2 uv, float time) +{ + vec2 i_part = floor(uv * RAIN_RIPPLE_GAPS); + vec2 f_part = fract(uv * RAIN_RIPPLE_GAPS); + return circle(f_part,i_part,fract(time * 1.2 + randPhase(i_part))); +} + +vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple height in w +{ + return + rain(uv,time) + + rain(uv + vec2(10.5,5.7),time) + + rain(uv * 0.75 + vec2(3.7,18.9),time) + + rain(uv * 0.9 + vec2(5.7,30.1),time) + + rain(uv * 0.8 + vec2(1.2,3.0),time); +} + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 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; @@ -54,11 +115,16 @@ float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta) result = 1.0; /* TIR (no refracted component) */ return result; -} + } + +vec2 normalCoords(vec2 uv, float scale, float speed, float time, float timer1, float timer2, vec3 previousNormal) + { + return uv * (WAVE_SCALE * scale) + WIND_DIR * time * (WIND_SPEED * speed) -(previousNormal.xy/previousNormal.zz) * WAVE_CHOPPYNESS + vec2(time * timer1,time * timer2); + } -varying vec3 screenCoordsPassthrough; -varying vec4 position; -varying float depthPassthrough; +varying vec3 screenCoordsPassthrough; +varying vec4 position; +varying float depthPassthrough; uniform sampler2D normalMap; @@ -74,8 +140,20 @@ uniform float near; uniform float far; uniform vec3 nodePosition; +uniform float rainIntensity; + +float frustumDepth; + +float linearizeDepth(float depth) // takes <0,1> non-linear depth value and returns <0,1> linearized value + { + float z_n = 2.0 * depth - 1.0; + depth = 2.0 * near * far / (far + near - z_n * frustumDepth); + return depth / frustumDepth; + } + void main(void) { + frustumDepth = abs(far - near); vec3 worldPos = position.xyz + nodePosition.xyz; vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; UV.y *= -1.0; @@ -89,26 +167,26 @@ void main(void) #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 normal0 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.05, 0.04, waterTimer, -0.015, -0.005, vec3(0.0,0.0,0.0))).rgb - 1.0; + vec3 normal1 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.1, 0.08, waterTimer, 0.02, 0.015, normal0)).rgb - 1.0; + vec3 normal2 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.25, 0.07, waterTimer, -0.04, -0.03, normal1)).rgb - 1.0; + vec3 normal3 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.5, 0.09, waterTimer, 0.03, 0.04, normal2)).rgb - 1.0; + vec3 normal4 = 2.0 * texture2D(normalMap,normalCoords(UV, 1.0, 0.4, waterTimer, -0.02, 0.1, normal3)).rgb - 1.0; + vec3 normal5 = 2.0 * texture2D(normalMap,normalCoords(UV, 2.0, 0.7, waterTimer, 0.1, -0.06, normal4)).rgb - 1.0; + vec4 rainRipple; + if (rainIntensity > 0.01) + rainRipple = rainCombined(position.xy / 1000.0,waterTimer) * clamp(rainIntensity,0.0,1.0); + else + rainRipple = vec4(0.0,0.0,0.0,0.0); + + vec3 rippleAdd = rainRipple.xyz * rainRipple.w * 10.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); + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y + + rippleAdd); normal = normalize(vec3(normal.x * BUMP, normal.y * BUMP, normal.z)); @@ -117,11 +195,12 @@ void main(void) // 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; + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1 + + rippleAdd).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; @@ -139,53 +218,64 @@ void main(void) 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)); + 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 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); +#if REFRACTION + float normalization = frustumDepth / 1000; + float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x) * normalization; + float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-(normal.xy*REFR_BUMP)).x) * normalization; + float surfaceDepth = linearizeDepth(gl_FragCoord.z) * normalization; + float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum + float shore = clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); +#else + float shore = 1.0; +#endif + vec2 screenCoordsOffset = normal.xy * REFL_BUMP * shore; + // reflection - vec3 reflection = texture2D(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb; + vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; // refraction #if REFRACTION - vec3 refraction = texture2D(refractionMap, screenCoords-(normal.xy*REFR_BUMP)).rgb; + vec3 refraction = texture2D(refractionMap, screenCoords - screenCoordsOffset).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 REFRACTION if (cameraPos.z > 0.0) - refraction = mix(refraction, waterColor, clamp(waterDepth/VISIBILITY, 0.0, 1.0)); + refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/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].xyz += vec3(rainRipple.w) * 0.2; +#else + gl_FragData[0].xyz += vec3(rainRipple.w) * 0.7; +#endif + #if REFRACTION gl_FragData[0].w = 1.0; #else - gl_FragData[0].w = clamp(fresnel*2.0 + specular, 0.0, 1.0); + gl_FragData[0].w = clamp(fresnel*6.0 + specular, 0.0, 1.0); //clamp(fresnel*2.0 + specular, 0.0, 1.0); #endif } diff --git a/tes3mp-credits.md b/tes3mp-credits.md index f474702a4..ad603c16f 100644 --- a/tes3mp-credits.md +++ b/tes3mp-credits.md @@ -26,7 +26,7 @@ Community administrators Art --- - Texafornian - tes3mp logo + Texafornian - TES3MP logo Special thanks (in alphabetical order) @@ -51,6 +51,7 @@ Special thanks (in alphabetical order) Rhiyo Scorcio Shnatsel + Simon Nemes Zaphida Zaric Zhakaron All the developers of OpenMW for creating an amazing open source project