diff --git a/.travis.yml b/.travis.yml index c93795241..383e30c61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,19 +9,15 @@ env: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE=" -cache: - ccache: true - directories: - - ${HOME}/.ccache +cache: ccache addons: apt: sources: - sourceline: 'ppa:openmw/openmw' - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-7 packages: [ # Dev - cmake, clang-7, clang-tools-7, gcc-8, g++-8, ccache, + cmake, clang-tools, gcc-9, g++-9, ccache, # Boost libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg @@ -45,50 +41,51 @@ matrix: os: osx osx_image: xcode10.2 if: branch != coverity_scan - - name: OpenMW (all) on Ubuntu Xenial GCC-5 + - name: OpenMW (all) on Ubuntu Bionic GCC-7 os: linux - dist: xenial + dist: bionic sudo: required if: branch != coverity_scan - - name: OpenMW (all) on Ubuntu Xenial GCC-8 + - name: OpenMW (all) on Ubuntu Bionic GCC-9 os: linux - dist: xenial + dist: bionic sudo: required env: - - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" + - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" if: branch != coverity_scan - - name: OpenMW (openmw) on Ubuntu Xenial Clang-7 with Static Analysis + - name: OpenMW (openmw) on Ubuntu Bionic Clang-6 with Static Analysis os: linux - dist: xenial + dist: bionic sudo: required env: - - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - - ANALYZE="scan-build-7 --force-analyze-debug-code --use-cc clang-7 --use-c++ clang++-7" + - MATRIX_EVAL="CC=clang && CXX=clang++" + - ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - BUILD_OPENMW_CS="OFF" if: branch != coverity_scan compiler: clang - - name: OpenMW (openmw-cs) on Ubuntu Xenial Clang-7 with Static Analysis + - name: OpenMW (openmw-cs) on Ubuntu Bionic Clang-6 with Static Analysis os: linux - dist: xenial + dist: bionic sudo: required env: - - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - - ANALYZE="scan-build-7 --force-analyze-debug-code --use-cc clang-7 --use-c++ clang++-7" + - MATRIX_EVAL="CC=clang && CXX=clang++" + - ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - BUILD_OPENMW="OFF" if: branch != coverity_scan compiler: clang - name: OpenMW Components Coverity Scan os: linux - dist: xenial + dist: bionic sudo: required if: branch = coverity_scan # allow_failures: -# - name: OpenMW (openmw) on Ubuntu Xenial Clang-7 with Static Analysis +# - name: OpenMW (openmw) on Ubuntu Bionic Clang-6 with Static Analysis before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./CI/before_install.${TRAVIS_OS_NAME}.sh; fi before_script: + - ccache -z - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./CI/before_script.${TRAVIS_OS_NAME}.sh; fi script: - cd ./build @@ -98,6 +95,7 @@ script: - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - cd "${TRAVIS_BUILD_DIR}" + - ccache -s deploy: provider: script script: ./CI/deploy.osx.sh diff --git a/AUTHORS.md b/AUTHORS.md index 1ec909d10..2c77e522e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -74,6 +74,7 @@ Programmers Fil Krynicki (filkry) Finbar Crago (finbar-crago) Florian Weber (Florianjw) + Frédéric Chardon (fr3dz10) Gaëtan Dezeiraud (Brouilles) Gašper Sedej Gijsbert ter Horst (Ghostbird) @@ -87,6 +88,7 @@ Programmers Jacob Essex (Yacoby) Jake Westrip (16bitint) James Carty (MrTopCat) + James Moore (moore.work) James Stephens (james-h-stephens) Jan-Peter Nilsson (peppe) Jan Borsodi (am0s) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11145e61b..985c49c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Bug #3977: Non-ASCII characters in object ID's are not supported Bug #4009: Launcher does not show data files on the first run after installing Bug #4077: Enchanted items are not recharged if they are not in the player's inventory + Bug #4141: PCSkipEquip isn't set to 1 when reading books/scrolls Bug #4202: Open .omwaddon files without needing toopen openmw-cs first Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect Bug #4262: Rain settings are hardcoded @@ -36,10 +37,13 @@ Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4449: Value returned by GetWindSpeed is incorrect Bug #4456: AiActivate should not be cancelled after target activation + Bug #4493: If the setup doesn't find what it is expecting, it fails silently and displays the requester again instead of letting the user know what wasn't found. + Bug #4523: "player->ModCurrentFatigue -0.001" in global script does not cause the running player to fall Bug #4540: Rain delay when exiting water Bug #4594: Actors without AI packages don't use Hello dialogue Bug #4598: Script parser does not support non-ASCII characters Bug #4600: Crash when no sound output is available or --no-sound is used. + Bug #4601: Filtering referenceables by gender is broken Bug #4639: Black screen after completing first mages guild mission + training Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog Bug #4680: Heap corruption on faulty esp @@ -192,9 +196,19 @@ Bug #5239: OpenMW-CS does not support non-ASCII characters in path names Bug #5241: On-self absorb spells cannot be detected Bug #5242: ExplodeSpell behavior differs from Cast behavior + Bug #5246: Water ripples persist after cell change Bug #5249: Wandering NPCs start walking too soon after they hello Bug #5250: Creatures display shield ground mesh instead of shield body part Bug #5255: "GetTarget, player" doesn't return 1 during NPC hello + Bug #5261: Creatures can sometimes become stuck playing idles and never wander again + Bug #5264: "Damage Fatigue" Magic Effect Can Bring Fatigue below 0 + Bug #5269: Editor: Cell lighting in resaved cleaned content files is corrupted + Bug #5278: Console command Show doesn't fall back to global variable after local var not found + Bug #5300: NPCs don't switch from torch to shield when starting combat + Bug #5308: World map copying makes save loading much slower + Bug #5313: Node properties of identical type are not applied in the correct order + Bug #5326: Formatting issues in the settings.cfg + Bug #5328: Skills aren't properly reset for dead actors Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls @@ -215,9 +229,11 @@ Feature #4544: Actors movement deceleration Feature #4673: Weapon sheathing Feature #4675: Support for NiRollController + Feature #4708: Radial fog support Feature #4730: Native animated containers support Feature #4784: Launcher: Duplicate Content Lists Feature #4812: Support NiSwitchNode + Feature #4831: Item search in the player's inventory Feature #4836: Daytime node switch Feature #4840: Editor: Transient terrain change support Feature #4859: Make water reflections more configurable @@ -240,6 +256,7 @@ Feature #5091: Human-readable light source duration Feature #5094: Unix like console hotkeys Feature #5098: Allow user controller bindings + Feature #5114: Refresh launcher mod list Feature #5121: Handle NiTriStrips and NiTriStripsData Feature #5122: Use magic glow for enchanted arrows Feature #5131: Custom skeleton bones @@ -247,9 +264,13 @@ Feature #5146: Safe Dispose corpse Feature #5147: Show spell magicka cost in spell buying window Feature #5170: Editor: Land shape editing, land selection + Feature #5172: Editor: Delete instances/references with keypress in scene window Feature #5193: Weapon sheathing Feature #5219: Impelement TestCells console command Feature #5224: Handle NiKeyframeController for NiTriShape + Feature #5274: Editor: Keyboard shortcut to drop objects to ground/obstacle in scene view + Feature #5304: Morrowind-style bump-mapping + Feature #5314: Ingredient filter in the alchemy window Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 526e09f14..94b0d45ec 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -42,6 +42,8 @@ New Editor Features: - "Faction Ranks" table for "Faction" records (#4209) - Changes to height editing can be cancelled without changes to data (press esc to cancel) (#4840) - Land heightmap/shape editing and vertex selection (#5170) +- Deleting instances with a keypress (#5172) +- Dropping objects with keyboard shortcuts (#5274) Bug Fixes: - The Mouse Wheel can now be used for key bindings (#2679) diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index fd4e4829c..eff3fd719 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,4 +1,4 @@ #!/bin/bash -ex -sudo ln -sf /usr/bin/clang-7 /usr/local/bin/clang -sudo ln -sf /usr/bin/clang++-7 /usr/local/bin/clang++ +#sudo ln -sf /usr/bin/clang-6 /usr/local/bin/clang +#sudo ln -sf /usr/bin/clang++-6 /usr/local/bin/clang++ diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 250955e49..c9b5b55bf 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -7,9 +7,7 @@ GOOGLETEST_DIR="$(pwd)/googletest/build" mkdir build cd build -export CODE_COVERAGE=1 -if [[ "${CC}" =~ "clang" ]]; then export CODE_COVERAGE=0; fi if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi if [[ -z "${BUILD_OPENMW_CS}" ]]; then export BUILD_OPENMW_CS=ON; fi @@ -28,7 +26,6 @@ ${ANALYZE} cmake \ -DBUILD_WIZARD=${BUILD_OPENMW_CS} \ -DBUILD_NIFTEST=${BUILD_OPENMW_CS} \ -DBUILD_MYGUI_PLUGIN=${BUILD_OPENMW_CS} \ - -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \ -DBUILD_UNITTESTS=1 \ -DUSE_SYSTEM_TINYXML=1 \ -DDESIRED_QT_VERSION=5 \ diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index e9fbb117b..847435ac5 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -421,16 +421,16 @@ if [ -z $SKIP_DOWNLOAD ]; then fi download "Qt 5.7.0" \ - "https://download.qt.io/archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ + "https://download.qt.io/new_archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ "qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \ "https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \ "qt-5-install.qs" fi # SDL2 - download "SDL 2.0.7" \ - "https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \ - "SDL2-2.0.7.zip" + download "SDL 2.0.12" \ + "https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \ + "SDL2-2.0.12.zip" # Google test and mock if [ ! -z $TEST_FRAMEWORK ]; then @@ -697,16 +697,16 @@ fi cd $DEPS echo # SDL2 -printf "SDL 2.0.7... " +printf "SDL 2.0.12... " { - if [ -d SDL2-2.0.7 ]; then + if [ -d SDL2-2.0.12 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.7 - eval 7z x -y SDL2-2.0.7.zip $STRIP + rm -rf SDL2-2.0.12 + eval 7z x -y SDL2-2.0.12.zip $STRIP fi - export SDL2DIR="$(real_pwd)/SDL2-2.0.7" - add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll" + export SDL2DIR="$(real_pwd)/SDL2-2.0.12" + add_runtime_dlls "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll" echo Done. } cd $DEPS diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index b7cbd8f65..a3f98792c 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -534,10 +534,18 @@ void Record::print() if (mData.mData.mFlags & ESM::Cell::Interior && !(mData.mData.mFlags & ESM::Cell::QuasiEx)) { - std::cout << " Ambient Light Color: " << mData.mAmbi.mAmbient << std::endl; - std::cout << " Sunlight Color: " << mData.mAmbi.mSunlight << std::endl; - std::cout << " Fog Color: " << mData.mAmbi.mFog << std::endl; - std::cout << " Fog Density: " << mData.mAmbi.mFogDensity << std::endl; + if (mData.hasAmbient()) + { + // TODO: see if we can change the integer representation to something more sensible + std::cout << " Ambient Light Color: " << mData.mAmbi.mAmbient << std::endl; + std::cout << " Sunlight Color: " << mData.mAmbi.mSunlight << std::endl; + std::cout << " Fog Color: " << mData.mAmbi.mFog << std::endl; + std::cout << " Fog Density: " << mData.mAmbi.mFogDensity << std::endl; + } + else + { + std::cout << " No Ambient Information" << std::endl; + } std::cout << " Water Level: " << mData.mWater << std::endl; } else diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 6f2811e66..e1a5d28af 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -89,6 +89,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); } + loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); // Input Settings loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); @@ -152,6 +153,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); + saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); // Input Settings saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index e679392c4..81544b094 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -62,10 +62,13 @@ void Launcher::DataFilesPage::buildView() { ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); + QToolButton * refreshButton = mSelector->refreshButton(); + //tool buttons ui.newProfileButton->setToolTip ("Create a new Content List"); ui.cloneProfileButton->setToolTip ("Clone the current Content List"); ui.deleteProfileButton->setToolTip ("Delete an existing Content List"); + refreshButton->setToolTip("Refresh Data Files"); //combo box ui.profilesComboBox->addItem(mDefaultContentListName); @@ -76,6 +79,7 @@ void Launcher::DataFilesPage::buildView() ui.newProfileButton->setDefaultAction (ui.newProfileAction); ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction); ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + refreshButton->setDefaultAction(ui.refreshDataFilesAction); //establish connections connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), @@ -86,6 +90,8 @@ void Launcher::DataFilesPage::buildView() connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), this, SLOT (slotProfileChangedByUser(QString, QString))); + + connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); } bool Launcher::DataFilesPage::loadSettings() @@ -114,6 +120,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (!mDataLocal.isEmpty()) paths.insert(0, mDataLocal); + mSelector->clearFiles(); + for (const QString &path : paths) mSelector->addFiles(path); @@ -167,7 +175,16 @@ QStringList Launcher::DataFilesPage::selectedFilePaths() QStringList filePaths; for (const ContentSelectorModel::EsmFile *item : items) { - filePaths.append(item->filePath()); + QFile file(item->filePath()); + + if(file.exists()) + { + filePaths.append(item->filePath()); + } + else + { + slotRefreshButtonClicked(); + } } return filePaths; } @@ -221,6 +238,18 @@ void Launcher::DataFilesPage::slotProfileDeleted (const QString &item) removeProfile (item); } +void Launcher::DataFilesPage:: refreshDataFilesView () +{ + QString currentProfile = ui.profilesComboBox->currentText(); + saveSettings(currentProfile); + populateFileViews(currentProfile); +} + +void Launcher::DataFilesPage::slotRefreshButtonClicked () +{ + refreshDataFilesView(); +} + void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) { setProfile(previous, current, true); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 36a0db616..af54fe75e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -61,6 +61,7 @@ namespace Launcher void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); void slotAddonDataChanged (); + void slotRefreshButtonClicked (); void updateNewProfileOkButton(const QString &text); void updateCloneProfileOkButton(const QString &text); @@ -100,6 +101,7 @@ namespace Launcher void checkForDefaultProfile(); void populateFileViews(const QString& contentModelName); void reloadCells(QStringList selectedFiles); + void refreshDataFilesView (); class PathIterator { diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f34ad66fb..af8bf8d55 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -97,10 +97,6 @@ opencs_units_noqt (view/render cellarrow cellmarker cellborder pathgrid ) -opencs_hdrs_noqt (view/render - mask - ) - opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 1bf5752f0..89b5283a4 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -355,6 +355,11 @@ void CSMPrefs::State::declare() declareShortcut ("scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); + declareShortcut ("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); + declareShortcut ("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); + declareShortcut ("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); + declareShortcut ("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence()); + declareShortcut ("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence()); declareShortcut ("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); declareShortcut ("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); declareShortcut ("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 948174b30..bec5008d3 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -76,53 +76,6 @@ namespace CSMWorld 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.mDataTypes & 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) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 87690423f..ccc18263b 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1770,7 +1770,7 @@ namespace CSMWorld struct GenderNpcColumn : public Column { GenderNpcColumn() - : Column(Columns::ColumnId_GenderNpc, ColumnBase::Display_GenderNpc) + : Column(Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc) {} virtual QVariant get(const Record& record) const @@ -2461,17 +2461,6 @@ namespace CSMWorld 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; @@ -2529,8 +2518,7 @@ 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::LandNormalsColumn::DataType) Q_DECLARE_METATYPE(CSMWorld::LandHeightsColumn::DataType) Q_DECLARE_METATYPE(CSMWorld::LandColoursColumn::DataType) Q_DECLARE_METATYPE(CSMWorld::LandTexturesColumn::DataType) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index e2c3be789..eaea66c2f 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -288,7 +288,6 @@ namespace CSMWorld { ColumnId_UChar, "Value [0..255]" }, { ColumnId_NpcMisc, "NPC Misc" }, { ColumnId_Level, "Level" }, - { ColumnId_GenderNpc, "Gender"}, { ColumnId_Mana, "Mana" }, { ColumnId_Fatigue, "Fatigue" }, { ColumnId_NpcDisposition, "NPC Disposition" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 085e6e178..c85eaac5f 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -273,7 +273,7 @@ namespace CSMWorld ColumnId_UChar = 250, ColumnId_NpcMisc = 251, ColumnId_Level = 252, - ColumnId_GenderNpc = 254, + // unused ColumnId_Mana = 255, ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index bc630806a..b45df35b1 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -79,8 +79,10 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); - defines["forcePPL"] = "0"; - defines["clamp"] = "1"; + defines["forcePPL"] = "0"; // Don't force per-pixel lighting + defines["clamp"] = "1"; // Clamp lighting + defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind + defines["radialFog"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); @@ -443,7 +445,6 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat 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); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 76338efe5..edf325b49 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -867,6 +867,8 @@ namespace CSMWorld switch (subColIndex) { case 0: return isInterior; + // While the ambient information is not necessarily valid if the subrecord wasn't loaded, + // the user should still be allowed to edit it case 1: return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mAmbient : QVariant(QVariant::UserType); case 2: return (isInterior && !behaveLikeExterior) ? @@ -912,7 +914,10 @@ namespace CSMWorld case 1: { if (isInterior && !behaveLikeExterior) + { cell.mAmbi.mAmbient = static_cast(value.toInt()); + cell.setHasAmbient(true); + } else return; // return without saving break; @@ -920,7 +925,10 @@ namespace CSMWorld case 2: { if (isInterior && !behaveLikeExterior) + { cell.mAmbi.mSunlight = static_cast(value.toInt()); + cell.setHasAmbient(true); + } else return; // return without saving break; @@ -928,7 +936,10 @@ namespace CSMWorld case 3: { if (isInterior && !behaveLikeExterior) + { cell.mAmbi.mFog = static_cast(value.toInt()); + cell.setHasAmbient(true); + } else return; // return without saving break; @@ -936,7 +947,10 @@ namespace CSMWorld case 4: { if (isInterior && !behaveLikeExterior) + { cell.mAmbi.mFogDensity = value.toFloat(); + cell.setHasAmbient(true); + } else return; // return without saving break; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index ff146bb91..3502d6319 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -486,7 +486,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_BodyPart)); npcColumns.mHead = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_GenderNpc, ColumnBase::Display_GenderNpc)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc)); npcColumns.mGender = &mColumns.back(); npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential)); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 056c50e45..48659865a 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "../../model/world/idtable.hpp" @@ -21,7 +22,6 @@ #include "cellborder.hpp" #include "cellarrow.hpp" #include "cellmarker.hpp" -#include "mask.hpp" #include "pathgrid.hpp" #include "terrainstorage.hpp" #include "object.hpp" @@ -92,7 +92,7 @@ bool CSVRender::Cell::addObjects (int start, int end) std::unique_ptr object (new Object (mData, mCellNode, id, false)); - if (mSubModeElementMask & Mask_Reference) + if (mSubModeElementMask & SceneUtil::Mask_EditorReference) object->setSubMode (mSubMode); mObjects.insert (std::make_pair (id, object.release())); @@ -134,7 +134,7 @@ void CSVRender::Cell::updateLand() else { mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, - mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain)); + mData.getResourceSystem().get(), mTerrainStorage)); } mTerrain->loadCell(esmLand.mX, esmLand.mY); @@ -434,7 +434,7 @@ void CSVRender::Cell::reloadAssets() void CSVRender::Cell::setSelection (int elementMask, Selection mode) { - if (elementMask & Mask_Reference) + if (elementMask & SceneUtil::Mask_EditorReference) { for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) @@ -451,7 +451,7 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) iter->second->setSelected (selected); } } - if (mPathgrid && elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & SceneUtil::Mask_Pathgrid) { // Only one pathgrid may be selected, so some operations will only have an effect // if the pathgrid is already focused @@ -546,12 +546,12 @@ std::vector > CSVRender::Cell::getSelection (un { std::vector > result; - if (elementMask & Mask_Reference) + if (elementMask & SceneUtil::Mask_EditorReference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) if (iter->second->getSelected()) result.push_back (iter->second->getTag()); - if (mPathgrid && elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & SceneUtil::Mask_Pathgrid) if (mPathgrid->isSelected()) result.push_back(mPathgrid->getTag()); @@ -562,7 +562,7 @@ std::vector > CSVRender::Cell::getEdited (unsig { std::vector > result; - if (elementMask & Mask_Reference) + if (elementMask & SceneUtil::Mask_EditorReference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) if (iter->second->isEdited()) @@ -576,7 +576,7 @@ void CSVRender::Cell::setSubMode (int subMode, unsigned int elementMask) mSubMode = subMode; mSubModeElementMask = elementMask; - if (elementMask & Mask_Reference) + if (elementMask & SceneUtil::Mask_EditorReference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->setSubMode (subMode); @@ -584,10 +584,10 @@ void CSVRender::Cell::setSubMode (int subMode, unsigned int elementMask) void CSVRender::Cell::reset (unsigned int elementMask) { - if (elementMask & Mask_Reference) + if (elementMask & SceneUtil::Mask_EditorReference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->reset(); - if (mPathgrid && elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & SceneUtil::Mask_Pathgrid) mPathgrid->resetIndicators(); } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index b6fee1545..3fb4f711d 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -11,11 +11,10 @@ #include "../../model/prefs/shortcutmanager.hpp" #include - -#include "mask.hpp" +#include CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) -: TagBase (Mask_CellArrow), mArrow (arrow) +: TagBase (SceneUtil::Mask_EditorCellArrow), mArrow (arrow) {} CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const @@ -175,7 +174,7 @@ CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, mParentNode->addChild (mBaseNode); - mBaseNode->setNodeMask (Mask_CellArrow); + mBaseNode->setNodeMask (SceneUtil::Mask_EditorCellArrow); adjustTransform(); buildShape(); diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp index 6073807ce..0789ee22a 100644 --- a/apps/opencs/view/render/cellborder.cpp +++ b/apps/opencs/view/render/cellborder.cpp @@ -7,8 +7,7 @@ #include #include - -#include "mask.hpp" +#include #include "../../model/world/cellcoordinates.hpp" @@ -20,7 +19,7 @@ CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoor : mParentNode(cellNode) { mBaseNode = new osg::PositionAttitudeTransform(); - mBaseNode->setNodeMask(Mask_CellBorder); + mBaseNode->setNodeMask(SceneUtil::Mask_EditorCellBorder); mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10)); mParentNode->addChild(mBaseNode); diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index 3de96ab02..e629aa827 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -8,7 +8,7 @@ #include CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) -: TagBase(Mask_CellMarker), mMarker(marker) +: TagBase(SceneUtil::Mask_EditorCellMarker), mMarker(marker) {} CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const @@ -79,7 +79,7 @@ CSVRender::CellMarker::CellMarker( mMarkerNode->getOrCreateStateSet()->setAttribute(mat); mMarkerNode->setUserData(new CellMarkerTag(this)); - mMarkerNode->setNodeMask(Mask_CellMarker); + mMarkerNode->setNodeMask(SceneUtil::Mask_EditorCellMarker); mCellNode->addChild(mMarkerNode); diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index 435178860..8edbc1cd1 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -11,12 +11,12 @@ #include #include #include +#include #include "../../model/world/cell.hpp" #include "../../model/world/cellcoordinates.hpp" #include "../../model/world/data.hpp" -#include "mask.hpp" namespace CSVRender { @@ -38,7 +38,7 @@ namespace CSVRender mWaterTransform->setPosition(osg::Vec3f(cellCoords.getX() * CellSize + CellSize / 2.f, cellCoords.getY() * CellSize + CellSize / 2.f, 0)); - mWaterTransform->setNodeMask(Mask_Water); + mWaterTransform->setNodeMask(SceneUtil::Mask_Water); mParentNode->addChild(mWaterTransform); mWaterNode = new osg::Geode(); diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 0cf58038d..7c0020f1e 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -3,18 +3,25 @@ #include #include +#include #include "../../model/prefs/state.hpp" +#include +#include +#include +#include + #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" +#include "../../model/prefs/shortcut.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" -#include "mask.hpp" +#include #include "object.hpp" #include "worldspacewidget.hpp" @@ -89,13 +96,26 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) return pos * combined; } -CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", +CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) +: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), SceneUtil::Mask_EditorReference | SceneUtil::Mask_Terrain, "Instance editing", parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), - mDragAxis (-1), mLocked (false), mUnitScaleDist(1) + mDragAxis (-1), mLocked (false), mUnitScaleDist(1), mParentNode (parentNode) { connect(this, SIGNAL(requestFocus(const std::string&)), worldspaceWidget, SIGNAL(requestFocus(const std::string&))); + + CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); + connect(deleteShortcut, SIGNAL(activated(bool)), this, SLOT(deleteSelectedInstances(bool))); + + // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and Qt5.14 + CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); + connect(dropToCollisionShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollision())); + CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); + connect(dropToTerrainLevelShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrain())); + CSMPrefs::Shortcut* dropToCollisionShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-collision-separately", worldspaceWidget); + connect(dropToCollisionShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollisionSeparately())); + CSMPrefs::Shortcut* dropToTerrainLevelShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); + connect(dropToTerrainLevelShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrainSeparately())); } void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) @@ -137,13 +157,13 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) std::string subMode = mSubMode->getCurrentId(); - getWorldspaceWidget().setSubMode (getSubModeFromId (subMode), Mask_Reference); + getWorldspaceWidget().setSubMode (getSubModeFromId (subMode), SceneUtil::Mask_EditorReference); } void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) { mDragMode = DragMode_None; - getWorldspaceWidget().reset (Mask_Reference); + getWorldspaceWidget().reset (SceneUtil::Mask_EditorReference); if (mSelectionMode) { @@ -196,7 +216,7 @@ void CSVRender::InstanceMode::secondaryEditPressed (const WorldspaceHitResult& h void CSVRender::InstanceMode::primarySelectPressed (const WorldspaceHitResult& hit) { - getWorldspaceWidget().clearSelection (Mask_Reference); + getWorldspaceWidget().clearSelection (SceneUtil::Mask_EditorReference); if (hit.tag) { @@ -231,13 +251,13 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_EditorReference); if (selection.empty()) { // Only change selection at the start of drag if no object is already selected if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { - getWorldspaceWidget().clearSelection (Mask_Reference); + getWorldspaceWidget().clearSelection (SceneUtil::Mask_EditorReference); if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; @@ -245,7 +265,7 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) } } - selection = getWorldspaceWidget().getSelection (Mask_Reference); + selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_EditorReference); if (selection.empty()) return false; } @@ -271,7 +291,7 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) mDragMode = DragMode_Scale; // Calculate scale factor - std::vector > editedSelection = getWorldspaceWidget().getEdited (Mask_Reference); + std::vector > editedSelection = getWorldspaceWidget().getEdited (SceneUtil::Mask_EditorReference); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); int widgetHeight = getWorldspaceWidget().height(); @@ -307,7 +327,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou osg::Vec3f offset; osg::Quat rotation; - std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); + std::vector > selection = getWorldspaceWidget().getEdited (SceneUtil::Mask_EditorReference); if (mDragMode == DragMode_Move) { @@ -464,7 +484,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) { std::vector > selection = - getWorldspaceWidget().getEdited (Mask_Reference); + getWorldspaceWidget().getEdited (SceneUtil::Mask_EditorReference); QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); @@ -496,7 +516,7 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) void CSVRender::InstanceMode::dragAborted() { - getWorldspaceWidget().reset (Mask_Reference); + getWorldspaceWidget().reset (SceneUtil::Mask_EditorReference); mDragMode = DragMode_None; } @@ -515,7 +535,7 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) offset *= diff * speedFactor; std::vector > selection = - getWorldspaceWidget().getEdited (Mask_Reference); + getWorldspaceWidget().getEdited (SceneUtil::Mask_EditorReference); for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) @@ -657,5 +677,207 @@ void CSVRender::InstanceMode::subModeChanged (const std::string& id) { mSubModeId = id; getWorldspaceWidget().abortDrag(); - getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); + getWorldspaceWidget().setSubMode (getSubModeFromId (id), SceneUtil::Mask_EditorReference); +} + +void CSVRender::InstanceMode::deleteSelectedInstances(bool active) +{ + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_EditorReference); + if (selection.empty()) return; + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& referencesTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + QUndoStack& undoStack = document.getUndoStack(); + + CSMWorld::CommandMacro macro (undoStack, "Delete Instances"); + for(osg::ref_ptr tag: selection) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + macro.push(new CSMWorld::DeleteCommand(referencesTable, objectTag->mObject->getReferenceId())); + + getWorldspaceWidget().clearSelection (SceneUtil::Mask_EditorReference); +} + +void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight) +{ + osg::Vec3d point = object->getPosition().asVec3(); + + osg::Vec3d start = point; + start.z() += objectHeight; + osg::Vec3d end = point; + end.z() = std::numeric_limits::lowest(); + + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::MODEL, start, end) ); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); + + if (dropMode == TerrainSep) + visitor.setTraversalMask(SceneUtil::Mask_Terrain); + if (dropMode == CollisionSep) + visitor.setTraversalMask(SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference); + + mParentNode->accept(visitor); + + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) + { + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + ESM::Position position = object->getPosition(); + object->setEdited (Object::Override_Position); + position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight; + object->setPosition(position.pos); + + return; + } +} + +float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) +{ + osg::Vec3d point = object->getPosition().asVec3(); + + osg::Vec3d start = point; + start.z() += objectHeight; + osg::Vec3d end = point; + end.z() = std::numeric_limits::lowest(); + + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::MODEL, start, end) ); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); + + if (dropMode == Terrain) + visitor.setTraversalMask(SceneUtil::Mask_Terrain); + if (dropMode == Collision) + visitor.setTraversalMask(SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference); + + mParentNode->accept(visitor); + + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) + { + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + float collisionLevel = intersection.getWorldIntersectPoint().z(); + return point.z() - collisionLevel + objectHeight; + } + + return 0.0f; +} + +void CSVRender::InstanceMode::dropSelectedInstancesToCollision() +{ + handleDropMethod(Collision, "Drop instances to next collision"); +} + +void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() +{ + handleDropMethod(Terrain, "Drop instances to terrain level"); +} + +void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() +{ + handleDropMethod(TerrainSep, "Drop instances to next collision level separately"); +} + +void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() +{ + handleDropMethod(CollisionSep, "Drop instances to terrain level separately"); +} + +void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) +{ + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_EditorReference); + if (selection.empty()) + return; + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + QUndoStack& undoStack = document.getUndoStack(); + + CSMWorld::CommandMacro macro (undoStack, commandMsg); + + DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget()); + + switch (dropMode) + { + case Terrain: + case Collision: + { + float smallestDropHeight = std::numeric_limits::max(); + int counter = 0; + for(osg::ref_ptr tag: selection) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); + if (thisDrop < smallestDropHeight) + smallestDropHeight = thisDrop; + counter++; + } + for(osg::ref_ptr tag: selection) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + objectTag->mObject->setEdited (Object::Override_Position); + ESM::Position position = objectTag->mObject->getPosition(); + position.pos[2] -= smallestDropHeight; + objectTag->mObject->setPosition(position.pos); + objectTag->mObject->apply (macro); + } + } + break; + + case TerrainSep: + case CollisionSep: + { + int counter = 0; + for(osg::ref_ptr tag: selection) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); + objectTag->mObject->apply (macro); + counter++; + } + } + break; + } +} + +CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget) + : mWorldspaceWidget(worldspacewidget) +{ + std::vector > selection = mWorldspaceWidget->getSelection (SceneUtil::Mask_EditorReference); + for(osg::ref_ptr tag: selection) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); + osg::ref_ptr objectNodeWithoutGUI = objectTag->mObject->getBaseNode(); + + osg::ComputeBoundsVisitor computeBounds; + computeBounds.setTraversalMask(SceneUtil::Mask_EditorReference); + objectNodeWithoutGUI->accept(computeBounds); + osg::BoundingBox bounds = computeBounds.getBoundingBox(); + float boundingBoxOffset = 0.0f; + if (bounds.valid()) + boundingBoxOffset = bounds.zMin(); + + mObjectHeights.emplace_back(boundingBoxOffset); + mOldMasks.emplace_back(objectNodeWithGUI->getNodeMask()); + + objectNodeWithGUI->setNodeMask(SceneUtil::Mask_Disabled); + } + } +} + +CSVRender::DropObjectDataHandler::~DropObjectDataHandler() +{ + std::vector > selection = mWorldspaceWidget->getSelection (SceneUtil::Mask_EditorReference); + int counter = 0; + for(osg::ref_ptr tag: selection) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); + objectNodeWithGUI->setNodeMask(mOldMasks[counter]); + counter++; + } + } } diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 6ddaa254f..beb60aff3 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -1,7 +1,10 @@ #ifndef CSV_RENDER_INSTANCEMODE_H #define CSV_RENDER_INSTANCEMODE_H +#include + #include +#include #include #include @@ -16,6 +19,7 @@ namespace CSVRender { class TagBase; class InstanceSelectionMode; + class Object; class InstanceMode : public EditMode { @@ -29,6 +33,14 @@ namespace CSVRender DragMode_Scale }; + enum DropMode + { + Collision, + Terrain, + CollisionSep, + TerrainSep + }; + CSVWidget::SceneToolMode *mSubMode; std::string mSubModeId; InstanceSelectionMode *mSelectionMode; @@ -36,6 +48,7 @@ namespace CSVRender int mDragAxis; bool mLocked; float mUnitScaleDist; + osg::ref_ptr mParentNode; int getSubModeFromId (const std::string& id) const; @@ -44,10 +57,12 @@ namespace CSVRender osg::Vec3f getSelectionCenter(const std::vector >& selection) const; osg::Vec3f getScreenCoords(const osg::Vec3f& pos); + void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); + float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: - InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0); + InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent = 0); virtual void activate (CSVWidget::SceneToolbar *toolbar); @@ -92,6 +107,25 @@ namespace CSVRender private slots: void subModeChanged (const std::string& id); + void deleteSelectedInstances(bool active); + void dropSelectedInstancesToCollision(); + void dropSelectedInstancesToTerrain(); + void dropSelectedInstancesToCollisionSeparately(); + void dropSelectedInstancesToTerrainSeparately(); + void handleDropMethod(DropMode dropMode, QString commandMsg); + }; + + /// \brief Helper class to handle object mask data in safe way + class DropObjectDataHandler + { + public: + DropObjectDataHandler(WorldspaceWidget* worldspacewidget); + ~DropObjectDataHandler(); + std::vector mObjectHeights; + + private: + WorldspaceWidget* mWorldspaceWidget; + std::vector mOldMasks; }; } diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index bf8ede0eb..470f34e5d 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -6,13 +6,15 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include + #include "worldspacewidget.hpp" #include "object.hpp" namespace CSVRender { InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) - : SelectionMode(parent, worldspaceWidget, Mask_Reference) + : SelectionMode(parent, worldspaceWidget, SceneUtil::Mask_EditorReference) { mSelectSame = new QAction("Extend selection to instances with same object ID", this); mDeleteSelection = new QAction("Delete selected instances", this); @@ -36,12 +38,12 @@ namespace CSVRender void InstanceSelectionMode::selectSame() { - getWorldspaceWidget().selectAllWithSameParentId(Mask_Reference); + getWorldspaceWidget().selectAllWithSameParentId(SceneUtil::Mask_EditorReference); } void InstanceSelectionMode::deleteSelection() { - std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); + std::vector > selection = getWorldspaceWidget().getSelection(SceneUtil::Mask_EditorReference); CSMWorld::IdTable& referencesTable = dynamic_cast( *getWorldspaceWidget().getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_References)); diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp deleted file mode 100644 index 55b7c823f..000000000 --- a/apps/opencs/view/render/mask.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CSV_RENDER_ELEMENTS_H -#define CSV_RENDER_ELEMENTS_H - -namespace CSVRender -{ - - /// Node masks used on the OSG scene graph in OpenMW-CS. - /// @note See the respective file in OpenMW (apps/openmw/mwrender/vismask.hpp) - /// for general usage hints about node masks. - /// @copydoc MWRender::VisMask - enum Mask - { - // internal use within NifLoader, do not change - Mask_UpdateVisitor = 0x1, - - // elements that are part of the actual scene - Mask_Reference = 0x2, - Mask_Pathgrid = 0x4, - Mask_Water = 0x8, - Mask_Fog = 0x10, - Mask_Terrain = 0x20, - - // used within models - Mask_ParticleSystem = 0x100, - - Mask_Lighting = 0x200, - - // control elements - Mask_CellMarker = 0x10000, - Mask_CellArrow = 0x20000, - Mask_CellBorder = 0x40000 - }; -} - -#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index fe4adfcb6..6b33cdeb2 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -29,9 +29,9 @@ #include #include #include +#include #include "actor.hpp" -#include "mask.hpp" const float CSVRender::Object::MarkerShaftWidth = 30; @@ -58,7 +58,7 @@ namespace CSVRender::ObjectTag::ObjectTag (Object* object) -: TagBase (Mask_Reference), mObject (object) +: TagBase (SceneUtil::Mask_EditorReference), mObject (object) {} QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const @@ -140,7 +140,7 @@ void CSVRender::Object::update() if (light) { bool isExterior = false; // FIXME - SceneUtil::addLight(mBaseNode, light, Mask_ParticleSystem, Mask_Lighting, isExterior); + SceneUtil::addLight(mBaseNode, light, isExterior); } } @@ -429,7 +429,7 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, parentNode->addChild (mRootNode); - mRootNode->setNodeMask(Mask_Reference); + mRootNode->setNodeMask(SceneUtil::Mask_EditorReference); if (referenceable) { @@ -477,6 +477,16 @@ bool CSVRender::Object::getSelected() const return mSelected; } +osg::ref_ptr CSVRender::Object::getRootNode() +{ + return mRootNode; +} + +osg::ref_ptr CSVRender::Object::getBaseNode() +{ + return mBaseNode; +} + bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -705,7 +715,7 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) CSMWorld::Columns::ColumnId_PositionXRot+i)); commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, column), mPositionOverride.rot[i])); + model->index (recordIndex, column), osg::RadiansToDegrees(mPositionOverride.rot[i]))); } } diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 10a46fc10..4d1763f80 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -146,6 +146,12 @@ namespace CSVRender bool getSelected() const; + /// Get object node with GUI graphics + osg::ref_ptr getRootNode(); + + /// Get object node without GUI graphics + osg::ref_ptr getBaseNode(); + /// \return Did this call result in a modification of the visual representation of /// this object? bool referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index b5d9234e4..8fafa8459 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -21,7 +21,6 @@ #include "../widget/scenetooltoggle2.hpp" #include "editmode.hpp" -#include "mask.hpp" #include "cameracontroller.hpp" #include "cellarrow.hpp" #include "terraintexturemode.hpp" @@ -127,8 +126,8 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Button_Terrain, Mask_Terrain, "Terrain"); - tool->addButton (Button_Fog, Mask_Fog, "Fog", "", true); + tool->addButton (Button_Terrain, SceneUtil::Mask_Terrain, "Terrain"); + //tool->addButton (Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( @@ -142,16 +141,16 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( tool->addButton ( new TerrainTextureMode (this, mRootNode, tool), "terrain-texture"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), + new EditMode (this, QIcon (":placeholder"), SceneUtil::Mask_EditorReference, "Terrain vertex paint editing"), "terrain-vertex"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain movement"), + new EditMode (this, QIcon (":placeholder"), SceneUtil::Mask_EditorReference, "Terrain movement"), "terrain-move"); } void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { - if (hit.tag && hit.tag->getMask()==Mask_CellArrow) + if (hit.tag && hit.tag->getMask()==SceneUtil::Mask_EditorCellArrow) { if (CellArrowTag *cellArrowTag = dynamic_cast (hit.tag.get())) { @@ -874,9 +873,9 @@ CSVWidget::SceneToolToggle2 *CSVRender::PagedWorldspaceWidget::makeControlVisibi mControlElements = new CSVWidget::SceneToolToggle2 (parent, "Controls & Guides Visibility", ":scenetoolbar/scene-view-marker-c", ":scenetoolbar/scene-view-marker-"); - mControlElements->addButton (1, Mask_CellMarker, "Cell Marker"); - mControlElements->addButton (2, Mask_CellArrow, "Cell Arrows"); - mControlElements->addButton (4, Mask_CellBorder, "Cell Border"); + mControlElements->addButton (1, SceneUtil::Mask_EditorCellMarker, "Cell Marker"); + mControlElements->addButton (2, SceneUtil::Mask_EditorCellArrow, "Cell Arrows"); + mControlElements->addButton (4, SceneUtil::Mask_EditorCellBorder, "Cell Border"); mControlElements->setSelectionMask (0xffffffff); diff --git a/apps/opencs/view/render/pathgrid.cpp b/apps/opencs/view/render/pathgrid.cpp index d8acfe2e1..b2714014b 100644 --- a/apps/opencs/view/render/pathgrid.cpp +++ b/apps/opencs/view/render/pathgrid.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "../../model/world/cell.hpp" #include "../../model/world/commands.hpp" @@ -31,7 +32,7 @@ namespace CSVRender }; PathgridTag::PathgridTag(Pathgrid* pathgrid) - : TagBase(Mask_Pathgrid), mPathgrid(pathgrid) + : TagBase(SceneUtil::Mask_Pathgrid), mPathgrid(pathgrid) { } @@ -70,7 +71,7 @@ namespace CSVRender mBaseNode->setPosition(osg::Vec3f(mCoords.getX() * CoordScalar, mCoords.getY() * CoordScalar, 0.f)); mBaseNode->setUserData(mTag); mBaseNode->setUpdateCallback(new PathgridNodeCallback()); - mBaseNode->setNodeMask(Mask_Pathgrid); + mBaseNode->setNodeMask(SceneUtil::Mask_Pathgrid); mParent->addChild(mBaseNode); mPathgridGeode = new osg::Geode(); diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 8863ad235..33c1b8b42 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../../model/prefs/state.hpp" @@ -15,7 +16,6 @@ #include "../widget/scenetoolbar.hpp" #include "cell.hpp" -#include "mask.hpp" #include "pathgrid.hpp" #include "pathgridselectionmode.hpp" #include "worldspacewidget.hpp" @@ -23,7 +23,7 @@ namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, + : EditMode(worldspaceWidget, QIcon(":placeholder"), SceneUtil::Mask_Pathgrid | SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference, getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) @@ -110,7 +110,7 @@ namespace CSVRender void PathgridMode::primarySelectPressed(const WorldspaceHitResult& hit) { - getWorldspaceWidget().clearSelection(Mask_Pathgrid); + getWorldspaceWidget().clearSelection(SceneUtil::Mask_Pathgrid); if (hit.tag) { @@ -131,7 +131,7 @@ namespace CSVRender { if (tag->getPathgrid()->getId() != mLastId) { - getWorldspaceWidget().clearSelection(Mask_Pathgrid); + getWorldspaceWidget().clearSelection(SceneUtil::Mask_Pathgrid); mLastId = tag->getPathgrid()->getId(); } @@ -142,12 +142,12 @@ namespace CSVRender } } - getWorldspaceWidget().clearSelection(Mask_Pathgrid); + getWorldspaceWidget().clearSelection(SceneUtil::Mask_Pathgrid); } bool PathgridMode::primaryEditStartDrag(const QPoint& pos) { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_Pathgrid); if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { @@ -156,7 +156,7 @@ namespace CSVRender if (dynamic_cast(hit.tag.get())) { primarySelectPressed(hit); - selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_Pathgrid); } } @@ -192,7 +192,7 @@ namespace CSVRender { if (mDragMode == DragMode_Move) { - std::vector > selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); + std::vector > selection = getWorldspaceWidget().getSelection(SceneUtil::Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { @@ -233,7 +233,7 @@ namespace CSVRender { if (mDragMode == DragMode_Move) { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) @@ -272,11 +272,11 @@ namespace CSVRender } mDragMode = DragMode_None; - getWorldspaceWidget().reset(Mask_Pathgrid); + getWorldspaceWidget().reset(SceneUtil::Mask_Pathgrid); } void PathgridMode::dragAborted() { - getWorldspaceWidget().reset(Mask_Pathgrid); + getWorldspaceWidget().reset(SceneUtil::Mask_Pathgrid); } } diff --git a/apps/opencs/view/render/pathgridselectionmode.cpp b/apps/opencs/view/render/pathgridselectionmode.cpp index db41faf50..43050d52a 100644 --- a/apps/opencs/view/render/pathgridselectionmode.cpp +++ b/apps/opencs/view/render/pathgridselectionmode.cpp @@ -13,7 +13,7 @@ namespace CSVRender { PathgridSelectionMode::PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) - : SelectionMode(parent, worldspaceWidget, Mask_Pathgrid) + : SelectionMode(parent, worldspaceWidget, SceneUtil::Mask_Pathgrid) { mRemoveSelectedNodes = new QAction("Remove selected nodes", this); mRemoveSelectedEdges = new QAction("Remove edges between selected nodes", this); @@ -37,7 +37,7 @@ namespace CSVRender void PathgridSelectionMode::removeSelectedNodes() { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { @@ -54,7 +54,7 @@ namespace CSVRender void PathgridSelectionMode::removeSelectedEdges() { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 8ae9d8a62..cd671f875 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "../widget/scenetoolmode.hpp" @@ -25,7 +26,6 @@ #include "../../model/prefs/shortcuteventhandler.hpp" #include "lighting.hpp" -#include "mask.hpp" #include "cameracontroller.hpp" namespace CSVRender @@ -71,7 +71,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; lightMgr->setStartLight(1); - lightMgr->setLightingMask(Mask_Lighting); + lightMgr->setLightingMask(SceneUtil::Mask_Lighting); mRootNode = lightMgr; mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); @@ -88,7 +88,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) // Add ability to signal osg to show its statistics for debugging purposes mView->addEventHandler(new osgViewer::StatsHandler); - mView->getCamera()->setCullMask(~(Mask_UpdateVisitor)); + mView->getCamera()->setCullMask(~(SceneUtil::Mask_UpdateVisitor)); viewer.addView(mView); viewer.setDone(false); @@ -100,6 +100,14 @@ RenderWidget::~RenderWidget() try { CompositeViewer::get().removeView(mView); + +#if OSG_VERSION_LESS_THAN(3,6,5) + // before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released. + // 3.6.4 moved it into the object cache, which meant it usually got released, but not here. + // 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache. + osg::ref_ptr graphicsContext = mView->getCamera()->getGraphicsContext(); + osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState()); +#endif } catch(const std::exception& e) { @@ -114,7 +122,7 @@ void RenderWidget::flagAsModified() void RenderWidget::setVisibilityMask(int mask) { - mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); + mView->getCamera()->setCullMask(mask | SceneUtil::Mask_ParticleSystem | SceneUtil::Mask_Lighting); } osg::Camera *RenderWidget::getCamera() @@ -204,7 +212,7 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste mOrbitCamControl = new OrbitCameraController(this); mCurrentCamControl = mFreeCamControl; - mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); + mOrbitCamControl->setPickingMask(SceneUtil::Mask_EditorReference | SceneUtil::Mask_Terrain); mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); @@ -213,7 +221,7 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste setLighting(&mLightingDay); - mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); + mResourceSystem->getSceneManager()->setParticleSystemMask(SceneUtil::Mask_ParticleSystem); // Recieve mouse move event even if mouse button is not pressed setMouseTracking(true); @@ -342,7 +350,7 @@ void SceneWidget::update(double dt) } else { - mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); + mCurrentCamControl->setup(mRootNode, SceneUtil::Mask_EditorReference | SceneUtil::Mask_Terrain, CameraController::WorldUp); mCamPositionSet = true; } } diff --git a/apps/opencs/view/render/selectionmode.hpp b/apps/opencs/view/render/selectionmode.hpp index f28888bfd..18c751290 100644 --- a/apps/opencs/view/render/selectionmode.hpp +++ b/apps/opencs/view/render/selectionmode.hpp @@ -3,8 +3,6 @@ #include "../widget/scenetoolmode.hpp" -#include "mask.hpp" - class QAction; namespace CSVRender diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp index 3ddd68690..bdd648102 100644 --- a/apps/opencs/view/render/tagbase.cpp +++ b/apps/opencs/view/render/tagbase.cpp @@ -1,9 +1,9 @@ #include "tagbase.hpp" -CSVRender::TagBase::TagBase (Mask mask) : mMask (mask) {} +CSVRender::TagBase::TagBase (SceneUtil::VisMask mask) : mMask (mask) {} -CSVRender::Mask CSVRender::TagBase::getMask() const +SceneUtil::VisMask CSVRender::TagBase::getMask() const { return mMask; } diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp index d1ecd2cfd..6a0bc4aef 100644 --- a/apps/opencs/view/render/tagbase.hpp +++ b/apps/opencs/view/render/tagbase.hpp @@ -5,19 +5,19 @@ #include -#include "mask.hpp" +#include namespace CSVRender { class TagBase : public osg::Referenced { - Mask mMask; + SceneUtil::VisMask mMask; public: - TagBase (Mask mask); + TagBase (SceneUtil::VisMask mask); - Mask getMask() const; + SceneUtil::VisMask getMask() const; virtual QString getToolTip (bool hideBasics) const; diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 77a178c2a..2a6d7f33f 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "../widget/brushshapes.hpp" #include "../widget/modebutton.hpp" @@ -38,13 +39,12 @@ #include "editmode.hpp" #include "pagedworldspacewidget.hpp" -#include "mask.hpp" #include "tagbase.hpp" #include "terrainselection.hpp" #include "worldspacewidget.hpp" CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) -: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), +: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference, "Terrain land editing", parent), mParentNode(parentNode) { } @@ -273,7 +273,6 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - int landMapLodColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandMapLodIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); QUndoStack& undoStack = document.getUndoStack(); @@ -287,9 +286,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - const CSMWorld::LandMapLodColumn::DataType landMapLodPointer = landTable.data(landTable.getModelIndex(cellId, landMapLodColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); - CSMWorld::LandMapLodColumn::DataType mapLodShapeNew(landMapLodPointer); CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); // Generate land height record @@ -304,26 +301,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() } } - // Generate WNAM record - int sqrtLandGlobalMapLodSize = sqrt(ESM::Land::LAND_GLOBAL_MAP_LOD_SIZE); - for(int i = 0; i < sqrtLandGlobalMapLodSize; ++i) - { - for(int j = 0; j < sqrtLandGlobalMapLodSize; ++j) - { - int col = (static_cast(j) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1); - int row = (static_cast(i) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1); - signed char lodHeight = 0; - float floatLodHeight = 0; - if (landShapeNew[col * ESM::Land::LAND_SIZE + row] > 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 128; - if (landShapeNew[col * ESM::Land::LAND_SIZE + row] <= 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 16; - if (floatLodHeight > std::numeric_limits::max()) lodHeight = std::numeric_limits::max(); - else if (floatLodHeight < std::numeric_limits::min()) lodHeight = std::numeric_limits::min(); - else lodHeight = static_cast(floatLodHeight); - mapLodShapeNew[j * sqrtLandGlobalMapLodSize + i] = lodHeight; - } - } pushEditToCommand(landShapeNew, document, landTable, cellId); - pushLodToCommand(mapLodShapeNew, document, landTable, cellId); } for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) @@ -1136,18 +1114,6 @@ void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandN undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); } -void CSVRender::TerrainShapeMode::pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, const std::string& cellId) -{ - QVariant changedLod; - changedLod.setValue(newLandMapLod); - - QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandMapLodIndex))); - - QUndoStack& undoStack = document.getUndoStack(); - undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLod)); -} - bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index ce2ea5465..68f2fbf9d 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -148,10 +148,6 @@ namespace CSVRender void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId); - /// Generate new land map LOD - void pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, const std::string& cellId); - bool noCell(const std::string& cellId); bool noLand(const std::string& cellId); diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 40dfc9474..efdb600b8 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" @@ -34,12 +35,11 @@ #include "editmode.hpp" #include "pagedworldspacewidget.hpp" -#include "mask.hpp" #include "object.hpp" // Something small needed regarding pointers from here () #include "worldspacewidget.hpp" CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) -: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent), +: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference, "Terrain texture editing", parent), mBrushTexture("L0#0"), mBrushSize(1), mBrushShape(0), diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index b1088aa60..5f5441b83 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -16,7 +16,6 @@ #include "../widget/scenetooltoggle2.hpp" #include "cameracontroller.hpp" -#include "mask.hpp" #include "tagbase.hpp" void CSVRender::UnpagedWorldspaceWidget::update() @@ -304,8 +303,8 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Button_Terrain, Mask_Terrain, "Terrain", "", true); - tool->addButton (Button_Fog, Mask_Fog, "Fog"); + tool->addButton (Button_Terrain, SceneUtil::Mask_Terrain, "Terrain", "", true); + //tool->addButton (Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 1eca61e73..6ab4b041b 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -26,8 +26,9 @@ #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" +#include + #include "object.hpp" -#include "mask.hpp" #include "instancemode.hpp" #include "pathgridmode.hpp" #include "cameracontroller.hpp" @@ -138,7 +139,7 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti { float alpha = setting->toDouble(); // getSelection is virtual, thus this can not be called from the constructor - auto selection = getSelection(Mask_Reference); + auto selection = getSelection(SceneUtil::Mask_EditorReference); for (osg::ref_ptr tag : selection) { if (auto objTag = dynamic_cast(tag.get())) @@ -345,7 +346,7 @@ unsigned int CSVRender::WorldspaceWidget::getVisibilityMask() const void CSVRender::WorldspaceWidget::setInteractionMask (unsigned int mask) { - mInteractionMask = mask | Mask_CellMarker | Mask_CellArrow; + mInteractionMask = mask | SceneUtil::Mask_EditorCellMarker | SceneUtil::Mask_EditorCellArrow; } unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const @@ -361,15 +362,15 @@ void CSVRender::WorldspaceWidget::setEditLock (bool locked) void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { - tool->addButton (Button_Reference, Mask_Reference, "Instances"); - tool->addButton (Button_Water, Mask_Water, "Water"); - tool->addButton (Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); + tool->addButton (Button_Reference, SceneUtil::Mask_EditorReference, "Instances"); + tool->addButton (Button_Water, SceneUtil::Mask_Water, "Water"); + tool->addButton (Button_Pathgrid, SceneUtil::Mask_Pathgrid, "Pathgrid"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) { /// \todo replace EditMode with suitable subclasses - tool->addButton (new InstanceMode (this, tool), "object"); + tool->addButton (new InstanceMode (this, mRootNode, tool), "object"); tool->addButton (new PathgridMode (this, tool), "pathgrid"); } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index a80032b82..5ac63c673 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -8,7 +8,6 @@ #include "../../model/world/tablemimedata.hpp" #include "scenewidget.hpp" -#include "mask.hpp" namespace CSMPrefs { diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 802415682..8a94a782c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -18,10 +18,10 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - actors objects renderingmanager animation rotatecontroller sky npcanimation vismask + actors objects renderingmanager animation rotatecontroller sky npcanimation creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths + renderbin actoranimation landmanager navmesh actorspaths recastmesh ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 50d6dc649..444cc6e82 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -26,6 +26,7 @@ #include +#include #include #include @@ -47,8 +48,6 @@ #include "mwworld/player.hpp" #include "mwworld/worldimp.hpp" -#include "mwrender/vismask.hpp" - #include "mwclass/classes.hpp" #include "mwdialogue/dialoguemanagerimp.hpp" @@ -558,7 +557,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) std::string myguiResources = (mResDir / "mygui").string(); osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); - guiRoot->setNodeMask(MWRender::Mask_GUI); + guiRoot->setNodeMask(SceneUtil::Mask_GUI); rootNode->addChild(guiRoot); MWGui::WindowManager* window = new MWGui::WindowManager(mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 471be77d9..35b808d0a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -325,6 +325,8 @@ namespace MWBase virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; @@ -653,6 +655,8 @@ namespace MWBase virtual MWPhysics::PhysicsSystem* getPhysicsSystem(void) = 0; virtual void toggleWaterRTT(bool enable) = 0; + + virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index c54b1c369..3f4e13c1c 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -19,7 +20,6 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwrender/vismask.hpp" #include "../mwgui/tooltips.hpp" @@ -34,7 +34,7 @@ namespace MWClass if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model, true); - ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Static); } } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 369077d6c..3adeb19d9 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -317,7 +317,6 @@ namespace MWClass { if (!state.mHasCustomState) return; - const ESM::ContainerState& state2 = dynamic_cast (state); if (!ptr.getRefData().getCustomData()) { @@ -326,21 +325,21 @@ namespace MWClass ptr.getRefData().setCustomData (data.release()); } - dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. - readState (state2.mInventory); + ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); + const ESM::ContainerState& containerState = state.asContainerState(); + customData.mContainerStore.readState (containerState.mInventory); } void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - ESM::ContainerState& state2 = dynamic_cast (state); - if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } - dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. - writeState (state2.mInventory); + const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); + ESM::ContainerState& containerState = state.asContainerState(); + customData.mContainerStore.writeState (containerState.mInventory); } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a157353a5..dde1c962b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -777,8 +777,6 @@ namespace MWClass if (!state.mHasCustomState) return; - const ESM::CreatureState& state2 = dynamic_cast (state); - if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) @@ -798,16 +796,14 @@ namespace MWClass ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - - customData.mContainerStore->readState (state2.mInventory); - customData.mCreatureStats.readState (state2.mCreatureStats); + const ESM::CreatureState& creatureState = state.asCreatureState(); + customData.mContainerStore->readState (creatureState.mInventory); + customData.mCreatureStats.readState (creatureState.mCreatureStats); } void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - ESM::CreatureState& state2 = dynamic_cast (state); - if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; @@ -815,9 +811,9 @@ namespace MWClass } const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - - customData.mContainerStore->writeState (state2.mInventory); - customData.mCreatureStats.writeState (state2.mCreatureStats); + ESM::CreatureState& creatureState = state.asCreatureState(); + customData.mContainerStore->writeState (creatureState.mInventory); + customData.mCreatureStats.writeState (creatureState.mCreatureStats); } int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 1dab9e483..1f47b483f 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -151,19 +151,16 @@ namespace MWClass if (!state.mHasCustomState) return; - const ESM::CreatureLevListState& state2 = dynamic_cast (state); - ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - customData.mSpawnActorId = state2.mSpawnActorId; - customData.mSpawn = state2.mSpawn; + const ESM::CreatureLevListState& levListState = state.asCreatureLevListState(); + customData.mSpawnActorId = levListState.mSpawnActorId; + customData.mSpawn = levListState.mSpawn; } void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - ESM::CreatureLevListState& state2 = dynamic_cast (state); - if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; @@ -171,7 +168,8 @@ namespace MWClass } const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - state2.mSpawnActorId = customData.mSpawnActorId; - state2.mSpawn = customData.mSpawn; + ESM::CreatureLevListState& levListState = state.asCreatureLevListState(); + levListState.mSpawnActorId = customData.mSpawnActorId; + levListState.mSpawn = customData.mSpawn; } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index cd2039869..7d1c1d38a 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -25,7 +26,6 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/animation.hpp" -#include "../mwrender/vismask.hpp" #include "../mwmechanics/actorutil.hpp" @@ -58,7 +58,7 @@ namespace MWClass if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model, true); - ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Static); } } @@ -370,11 +370,11 @@ namespace MWClass { if (!state.mHasCustomState) return; + ensureCustomData(ptr); DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); - - const ESM::DoorState& state2 = dynamic_cast(state); - customData.mDoorState = static_cast(state2.mDoorState); + const ESM::DoorState& doorState = state.asDoorState(); + customData.mDoorState = MWWorld::DoorState(doorState.mDoorState); } void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const @@ -384,10 +384,10 @@ namespace MWClass state.mHasCustomState = false; return; } - const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); - ESM::DoorState& state2 = dynamic_cast(state); - state2.mDoorState = static_cast(customData.mDoorState); + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); + ESM::DoorState& doorState = state.asDoorState(); + doorState.mDoorState = int(customData.mDoorState); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 237ed23c9..57270f6e6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1307,8 +1307,6 @@ namespace MWClass if (!state.mHasCustomState) return; - const ESM::NpcState& state2 = dynamic_cast (state); - if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) @@ -1322,17 +1320,15 @@ namespace MWClass ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - - customData.mInventoryStore.readState (state2.mInventory); - customData.mNpcStats.readState (state2.mNpcStats); - static_cast (customData.mNpcStats).readState (state2.mCreatureStats); + const ESM::NpcState& npcState = state.asNpcState(); + customData.mInventoryStore.readState (npcState.mInventory); + customData.mNpcStats.readState (npcState.mNpcStats); + customData.mNpcStats.readState (npcState.mCreatureStats); } void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - ESM::NpcState& state2 = dynamic_cast (state); - if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; @@ -1340,10 +1336,10 @@ namespace MWClass } const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - - customData.mInventoryStore.writeState (state2.mInventory); - customData.mNpcStats.writeState (state2.mNpcStats); - static_cast (customData.mNpcStats).writeState (state2.mCreatureStats); + ESM::NpcState& npcState = state.asNpcState(); + customData.mInventoryStore.writeState (npcState.mInventory); + customData.mNpcStats.writeState (npcState.mNpcStats); + customData.mNpcStats.writeState (npcState.mCreatureStats); } int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5551b3d73..2cb00c497 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" #include "../mwphysics/physicssystem.hpp" @@ -9,7 +10,6 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwrender/vismask.hpp" namespace MWClass { @@ -19,7 +19,7 @@ namespace MWClass if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); - ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Static); } } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index ad66735bf..8dc44059f 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -17,6 +18,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include #include #include "inventoryitemmodel.hpp" @@ -29,6 +31,7 @@ namespace MWGui { AlchemyWindow::AlchemyWindow() : WindowBase("openmw_alchemy_window.layout") + , mModel(nullptr) , mSortModel(nullptr) , mAlchemy(new MWMechanics::Alchemy()) , mApparatus (4) @@ -50,6 +53,8 @@ namespace MWGui getWidget(mDecreaseButton, "DecreaseButton"); getWidget(mNameEdit, "NameEdit"); getWidget(mItemView, "ItemView"); + getWidget(mFilterValue, "FilterValue"); + getWidget(mFilterType, "FilterType"); mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged); mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); @@ -72,6 +77,9 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); + mFilterValue->eventComboChangePosition += MyGUI::newDelegate(this, &AlchemyWindow::onFilterChanged); + mFilterValue->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited); + mFilterType->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::switchFilterType); center(); } @@ -136,16 +144,110 @@ namespace MWGui removeIngredient(mIngredients[i]); } + updateFilters(); update(); } + void AlchemyWindow::initFilter() + { + auto const& wm = MWBase::Environment::get().getWindowManager(); + auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); + auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects"); + + if (mFilterType->getCaption() == ingredient) + mCurrentFilter = FilterType::ByName; + else + mCurrentFilter = FilterType::ByEffect; + updateFilters(); + mFilterValue->clearIndexSelected(); + updateFilters(); + } + + void AlchemyWindow::switchFilterType(MyGUI::Widget* _sender) + { + auto const& wm = MWBase::Environment::get().getWindowManager(); + auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); + auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects"); + auto *button = _sender->castType(); + + if (button->getCaption() == ingredient) + { + button->setCaption(effect); + mCurrentFilter = FilterType::ByEffect; + } + else + { + button->setCaption(ingredient); + mCurrentFilter = FilterType::ByName; + } + mSortModel->setNameFilter({}); + mSortModel->setEffectFilter({}); + mFilterValue->clearIndexSelected(); + updateFilters(); + mItemView->update(); + } + + void AlchemyWindow::updateFilters() + { + std::set itemNames, itemEffects; + for (size_t i = 0; i < mModel->getItemCount(); ++i) + { + auto const& base = mModel->getItem(i).mBase; + if (base.getTypeName() != typeid(ESM::Ingredient).name()) + continue; + + itemNames.insert(base.getClass().getName(base)); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + auto const alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); + + auto const effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill); + itemEffects.insert(effects.begin(), effects.end()); + } + + mFilterValue->removeAllItems(); + auto const addItems = [&](auto const& container) + { + for (auto const& item : container) + mFilterValue->addItem(item); + }; + switch (mCurrentFilter) + { + case FilterType::ByName: addItems(itemNames); break; + case FilterType::ByEffect: addItems(itemEffects); break; + } + } + + void AlchemyWindow::applyFilter(const std::string& filter) + { + switch (mCurrentFilter) + { + case FilterType::ByName: mSortModel->setNameFilter(filter); break; + case FilterType::ByEffect: mSortModel->setEffectFilter(filter); break; + } + mItemView->update(); + } + + void AlchemyWindow::onFilterChanged(MyGUI::ComboBox* _sender, size_t _index) + { + // ignore spurious event fired when one edit the content after selection. + // onFilterEdited will handle it. + if (_index != MyGUI::ITEM_NONE) + applyFilter(_sender->getItemNameAt(_index)); + } + + void AlchemyWindow::onFilterEdited(MyGUI::EditBox* _sender) + { + applyFilter(_sender->getCaption()); + } + void AlchemyWindow::onOpen() { mAlchemy->clear(); mAlchemy->setAlchemist (MWMechanics::getPlayer()); - InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer()); - mSortModel = new SortFilterItemModel(model); + mModel = new InventoryItemModel(MWMechanics::getPlayer()); + mSortModel = new SortFilterItemModel(mModel); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mItemView->setModel (mSortModel); mItemView->resetScrollBars(); @@ -167,6 +269,7 @@ namespace MWGui } update(); + initFilter(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index a3f1cb52e..82dd0a243 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -5,7 +5,9 @@ #include #include +#include +#include #include #include "windowbase.hpp" @@ -19,6 +21,7 @@ namespace MWGui { class ItemView; class ItemWidget; + class InventoryItemModel; class SortFilterItemModel; class AlchemyWindow : public WindowBase @@ -36,8 +39,11 @@ namespace MWGui static const float sCountChangeInterval; // in seconds std::string mSuggestedPotionName; + enum class FilterType { ByName, ByEffect }; + FilterType mCurrentFilter; ItemView* mItemView; + InventoryItemModel* mModel; SortFilterItemModel* mSortModel; MyGUI::Button* mCreateButton; @@ -47,6 +53,8 @@ namespace MWGui MyGUI::Button* mIncreaseButton; MyGUI::Button* mDecreaseButton; + Gui::AutoSizedButton* mFilterType; + MyGUI::ComboBox* mFilterValue; MyGUI::EditBox* mNameEdit; Gui::NumericEditBox* mBrewCountEdit; @@ -60,6 +68,13 @@ namespace MWGui void onCountValueChanged(int value); void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); + void applyFilter(const std::string& filter); + void initFilter(); + void onFilterChanged(MyGUI::ComboBox* _sender, size_t _index); + void onFilterEdited(MyGUI::EditBox* _sender); + void switchFilterType(MyGUI::Widget* _sender); + void updateFilters(); + void addRepeatController(MyGUI::Widget* widget); void onIncreaseButtonTriggered(); diff --git a/apps/openmw/mwgui/backgroundimage.hpp b/apps/openmw/mwgui/backgroundimage.hpp index 3db5bab16..3ea8b3bba 100644 --- a/apps/openmw/mwgui/backgroundimage.hpp +++ b/apps/openmw/mwgui/backgroundimage.hpp @@ -9,7 +9,7 @@ namespace MWGui /** * @brief A variant of MyGUI::ImageBox with aspect ratio correction using black bars */ - class BackgroundImage : public MyGUI::ImageBox + class BackgroundImage final : public MyGUI::ImageBox { MYGUI_RTTI_DERIVED(BackgroundImage) @@ -22,8 +22,8 @@ namespace MWGui */ void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool stretch=true); - virtual void setSize (const MyGUI::IntSize &_value); - virtual void setCoord (const MyGUI::IntCoord &_value); + void setSize (const MyGUI::IntSize &_value) final; + void setCoord (const MyGUI::IntCoord &_value) final; private: MyGUI::ImageBox* mChild; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 8f8ca4bb5..5724defcd 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -817,7 +817,7 @@ namespace }; } -class PageDisplay : public MyGUI::ISubWidgetText +class PageDisplay final : public MyGUI::ISubWidgetText { MYGUI_RTTI_DERIVED(PageDisplay) protected: @@ -1140,7 +1140,7 @@ public: i->second->createDrawItem (mNode); } - void setVisible (bool newVisible) + void setVisible (bool newVisible) final { if (mVisible == newVisible) return; @@ -1162,7 +1162,7 @@ public: } } - void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) + void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) final { mNode = node; @@ -1230,9 +1230,9 @@ public: // ISubWidget should not necessarily be a drawitem // in this case, it is not... - void doRender() { } + void doRender() final { } - void _updateView () + void _updateView () final { _checkMargin(); @@ -1241,7 +1241,7 @@ public: mNode->outOfDate (i->second->mRenderItem); } - void _correctView() + void _correctView() final { _checkMargin (); @@ -1251,7 +1251,7 @@ public: } - void destroyDrawItem() + void destroyDrawItem() final { for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->destroyDrawItem (mNode); @@ -1261,7 +1261,7 @@ public: }; -class BookPageImpl : public BookPage +class BookPageImpl final : public BookPage { MYGUI_RTTI_DERIVED(BookPage) public: @@ -1271,24 +1271,24 @@ public: { } - void showPage (TypesetBook::Ptr book, size_t page) + void showPage (TypesetBook::Ptr book, size_t page) final { mPageDisplay->showPage (book, page); } - void adviseLinkClicked (std::function linkClicked) + void adviseLinkClicked (std::function linkClicked) final { mPageDisplay->mLinkClicked = linkClicked; } - void unadviseLinkClicked () + void unadviseLinkClicked () final { mPageDisplay->mLinkClicked = std::function (); } protected: - virtual void initialiseOverride() + void initialiseOverride() final { Base::initialiseOverride(); @@ -1302,24 +1302,24 @@ protected: } } - void onMouseLostFocus(Widget* _new) + void onMouseLostFocus(Widget* _new) final { // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had focus). // Child widgets may already be destroyed! So be careful. mPageDisplay->onMouseLostFocus (); } - void onMouseMove(int left, int top) + void onMouseMove(int left, int top) final { mPageDisplay->onMouseMove (left, top); } - void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) + void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) final { mPageDisplay->onMouseButtonPressed (left, top, id); } - void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) + void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) final { mPageDisplay->onMouseButtonReleased (left, top, id); } diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index b2639e938..c069ae885 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -46,9 +46,11 @@ CompanionWindow::CompanionWindow(DragAndDrop *dragAndDrop, MessageBoxManager* ma getWidget(mCloseButton, "CloseButton"); getWidget(mProfitLabel, "ProfitLabel"); getWidget(mEncumbranceBar, "EncumbranceBar"); + getWidget(mFilterEdit, "FilterEdit"); getWidget(mItemView, "ItemView"); mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &CompanionWindow::onBackgroundSelected); mItemView->eventItemClicked += MyGUI::newDelegate(this, &CompanionWindow::onItemSelected); + mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &CompanionWindow::onNameFilterChanged); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked); @@ -92,6 +94,12 @@ void CompanionWindow::onItemSelected(int index) dragItem (nullptr, count); } +void CompanionWindow::onNameFilterChanged(MyGUI::EditBox* _sender) + { + mSortModel->setNameFilter(_sender->getCaption()); + mItemView->update(); + } + void CompanionWindow::dragItem(MyGUI::Widget* sender, int count) { mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); @@ -113,6 +121,7 @@ void CompanionWindow::setPtr(const MWWorld::Ptr& npc) mModel = new CompanionItemModel(npc); mSortModel = new SortFilterItemModel(mModel); + mFilterEdit->setCaption(std::string()); mItemView->setModel(mSortModel); mItemView->resetScrollBars(); diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 8ca350617..442ce57da 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -39,11 +39,13 @@ namespace MWGui DragAndDrop* mDragAndDrop; MyGUI::Button* mCloseButton; + MyGUI::EditBox* mFilterEdit; MyGUI::TextBox* mProfitLabel; Widgets::MWDynamicStat* mEncumbranceBar; MessageBoxManager* mMessageBoxManager; void onItemSelected(int index); + void onNameFilterChanged(MyGUI::EditBox* _sender); void onBackgroundSelected(); void dragItem(MyGUI::Widget* sender, int count); diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp index b75fe79ab..bd9646ec2 100644 --- a/apps/openmw/mwgui/controllers.hpp +++ b/apps/openmw/mwgui/controllers.hpp @@ -14,14 +14,14 @@ namespace MWGui namespace Controllers { /// Automatically positions a widget below the mouse cursor. - class ControllerFollowMouse : + class ControllerFollowMouse final : public MyGUI::ControllerItem { MYGUI_RTTI_DERIVED( ControllerFollowMouse ) private: - bool addTime(MyGUI::Widget* _widget, float _time); - void prepareItem(MyGUI::Widget* _widget); + bool addTime(MyGUI::Widget* _widget, float _time) final; + void prepareItem(MyGUI::Widget* _widget) final; }; } } diff --git a/apps/openmw/mwgui/cursor.hpp b/apps/openmw/mwgui/cursor.hpp index 4e3eb9097..ef5099ef8 100644 --- a/apps/openmw/mwgui/cursor.hpp +++ b/apps/openmw/mwgui/cursor.hpp @@ -11,7 +11,7 @@ namespace MWGui /// ResourceImageSetPointer that we need. /// \example MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); /// MyGUI::ResourceManager::getInstance().load("core.xml"); - class ResourceImageSetPointerFix : + class ResourceImageSetPointerFix final : public MyGUI::IPointer { MYGUI_RTTI_DERIVED( ResourceImageSetPointerFix ) @@ -20,17 +20,17 @@ namespace MWGui ResourceImageSetPointerFix(); virtual ~ResourceImageSetPointerFix(); - virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version); + void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) final; - virtual void setImage(MyGUI::ImageBox* _image); - virtual void setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point); + void setImage(MyGUI::ImageBox* _image) final; + void setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point) final; //and now for the whole point of this class, allow us to get //the hot spot, the image and the size of the cursor. - virtual MyGUI::ResourceImageSetPtr getImageSet(); - virtual MyGUI::IntPoint getHotSpot(); - virtual MyGUI::IntSize getSize(); - virtual int getRotation(); + MyGUI::ResourceImageSetPtr getImageSet(); + MyGUI::IntPoint getHotSpot(); + MyGUI::IntSize getSize(); + int getRotation(); private: MyGUI::IntPoint mPoint; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 77ecaca34..653f03153 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -21,12 +22,10 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/scriptmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwscript/interpretercontext.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -90,6 +89,7 @@ namespace MWGui getWidget(mLeftPane, "LeftPane"); getWidget(mRightPane, "RightPane"); getWidget(mArmorRating, "ArmorRating"); + getWidget(mFilterEdit, "FilterEdit"); mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); mAvatarImage->setRenderItemTexture(mPreviewTexture.get()); @@ -104,6 +104,7 @@ namespace MWGui mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); + mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &InventoryWindow::onNameFilterChanged); mFilterAll->setStateSelected(true); @@ -133,6 +134,8 @@ namespace MWGui else mSortModel = new SortFilterItemModel(mTradeModel); + mSortModel->setNameFilter(mFilterEdit->getCaption()); + mItemView->setModel(mSortModel); mFilterAll->setStateSelected(true); @@ -388,6 +391,11 @@ namespace MWGui void InventoryWindow::onOpen() { + // Reset the filter focus when opening the window + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (focus == mFilterEdit) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); + if (!mPtr.isEmpty()) { updateEncumbranceBar(); @@ -465,6 +473,12 @@ namespace MWGui width*mScaleFactor/float(mPreview->getTextureWidth()), height*mScaleFactor/float(mPreview->getTextureHeight()))); } + void InventoryWindow::onNameFilterChanged(MyGUI::EditBox* _sender) + { + mSortModel->setNameFilter(_sender->getCaption()); + mItemView->update(); + } + void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) @@ -477,7 +491,6 @@ namespace MWGui mSortModel->setCategory(SortFilterItemModel::Category_Magic); else if (_sender == mFilterMisc) mSortModel->setCategory(SortFilterItemModel::Category_Misc); - mFilterAll->setStateSelected(false); mFilterWeapon->setStateSelected(false); mFilterApparel->setStateSelected(false); @@ -507,6 +520,16 @@ namespace MWGui void InventoryWindow::useItem(const MWWorld::Ptr &ptr, bool force) { const std::string& script = ptr.getClass().getScript(ptr); + if (!script.empty()) + { + // Don't try to equip the item if PCSkipEquip is set to 1 + if (ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1) + { + ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); + return; + } + ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 0); + } MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -526,43 +549,28 @@ namespace MWGui if (canEquip.first == 0) { - /// If PCSkipEquip is set, set OnPCEquip to 1 and don't message anything - if (!script.empty() && ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1) - ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); - else - MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); + MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); updateItemView(); return; } } } - // If the item has a script, set its OnPcEquip to 1 - if (!script.empty() - // Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards, - // the next time it is equipped will work normally, but will not set onpcequip - && (ptr != mSkippedToEquip || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1)) - ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); - - // Give the script a chance to run once before we do anything else - // this is important when setting pcskipequip as a reaction to onpcequip being set (bk_treasuryreport does this) - if (!force && !script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) + // If the item has a script, set OnPCEquip or PCSkipEquip to 1 + if (!script.empty()) { - MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + // Ingredients, books and repair hammers must not have OnPCEquip set to 1 here + const std::string& type = ptr.getTypeName(); + bool isBook = type == typeid(ESM::Book).name(); + if (!isBook && type != typeid(ESM::Ingredient).name() && type != typeid(ESM::Repair).name()) + ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); + // Books must have PCSkipEquip set to 1 instead + else if (isBook) + ptr.getRefData().getLocals().setVarByInt(script, "pcskipequip", 1); } - mSkippedToEquip = MWWorld::Ptr(); - if (ptr.getRefData().getCount()) // make sure the item is still there, the script might have removed it - { - if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) - { - std::shared_ptr action = ptr.getClass().use(ptr, force); - action->execute(player); - } - else - mSkippedToEquip = ptr; - } + std::shared_ptr action = ptr.getClass().use(ptr, force); + action->execute(player); if (isVisible()) { diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 8d0a86bbf..f2ab7f1ee 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -92,8 +92,8 @@ namespace MWGui MyGUI::Button* mFilterApparel; MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; - - MWWorld::Ptr mSkippedToEquip; + + MyGUI::EditBox* mFilterEdit; GuiMode mGuiMode; @@ -121,6 +121,7 @@ namespace MWGui void onWindowResize(MyGUI::Window* _sender); void onFilterChanged(MyGUI::Widget* _sender); + void onNameFilterChanged(MyGUI::EditBox* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); diff --git a/apps/openmw/mwgui/itemchargeview.hpp b/apps/openmw/mwgui/itemchargeview.hpp index 956272aec..2522f55d1 100644 --- a/apps/openmw/mwgui/itemchargeview.hpp +++ b/apps/openmw/mwgui/itemchargeview.hpp @@ -21,7 +21,7 @@ namespace MWGui class ItemModel; class ItemWidget; - class ItemChargeView : public MyGUI::Widget + class ItemChargeView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(ItemChargeView) public: @@ -36,7 +36,7 @@ namespace MWGui /// Register needed components with MyGUI's factory manager static void registerComponents(); - virtual void initialiseOverride(); + void initialiseOverride() final; /// Takes ownership of \a model void setModel(ItemModel* model); @@ -47,8 +47,8 @@ namespace MWGui void layoutWidgets(); void resetScrollbars(); - virtual void setSize(const MyGUI::IntSize& value); - virtual void setCoord(const MyGUI::IntCoord& value); + void setSize(const MyGUI::IntSize& value) final; + void setCoord(const MyGUI::IntCoord& value) final; MyGUI::delegates::CMultiDelegate2 eventItemClicked; diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index fa6ef29f9..a5e537aa0 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -8,7 +8,7 @@ namespace MWGui { - class ItemView : public MyGUI::Widget + class ItemView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(ItemView) public: @@ -33,12 +33,12 @@ namespace MWGui void resetScrollBars(); private: - virtual void initialiseOverride(); + void initialiseOverride() final; void layoutWidgets(); - virtual void setSize(const MyGUI::IntSize& _value); - virtual void setCoord(const MyGUI::IntCoord& _value); + void setSize(const MyGUI::IntSize& _value) final; + void setCoord(const MyGUI::IntCoord& _value) final; void onSelectedItem (MyGUI::Widget* sender); void onSelectedBackground (MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 0cb22c7f7..748a44445 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -41,7 +41,7 @@ namespace MWGui void setFrame (const std::string& frame, const MyGUI::IntCoord& coord); protected: - virtual void initialiseOverride(); + void initialiseOverride() final; MyGUI::ImageBox* mItem; MyGUI::ImageBox* mItemShadow; diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 7355dc1f4..3f98b8607 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -16,9 +16,6 @@ namespace MWGui bool shouldAcceptKeyFocus(MyGUI::Widget* w) { - if (w && w->getUserString("IgnoreTabKey") == "y") - return false; - return w && !w->castType(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled(); } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 54382ab4d..ddaa9063a 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -14,14 +14,13 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" -#include "../mwrender/vismask.hpp" - #include "backgroundimage.hpp" namespace MWGui @@ -335,8 +334,8 @@ namespace MWGui // Turn off rendering except the GUI int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile); - mViewer->getCamera()->setCullMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile); + mViewer->getUpdateVisitor()->setTraversalMask(SceneUtil::Mask_GUI|SceneUtil::Mask_PreCompile); + mViewer->getCamera()->setCullMask(SceneUtil::Mask_GUI|SceneUtil::Mask_PreCompile); MWBase::Environment::get().getInputManager()->update(0, true, true); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 02d2be577..bc65ee021 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -54,7 +54,7 @@ namespace /// @brief A widget that changes its color when hovered. - class MarkerWidget: public MyGUI::Widget + class MarkerWidget final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(MarkerWidget) @@ -74,12 +74,12 @@ namespace MyGUI::Colour mNormalColour; MyGUI::Colour mHoverColour; - void onMouseLostFocus(MyGUI::Widget* _new) + void onMouseLostFocus(MyGUI::Widget* _new) final { setColour(mNormalColour); } - void onMouseSetFocus(MyGUI::Widget* _old) + void onMouseSetFocus(MyGUI::Widget* _old) final { setColour(mHoverColour); } diff --git a/apps/openmw/mwgui/resourceskin.hpp b/apps/openmw/mwgui/resourceskin.hpp index 5a1f05c05..bdf0d2f0b 100644 --- a/apps/openmw/mwgui/resourceskin.hpp +++ b/apps/openmw/mwgui/resourceskin.hpp @@ -5,12 +5,12 @@ namespace MWGui { - class AutoSizedResourceSkin : public MyGUI::ResourceSkin + class AutoSizedResourceSkin final : public MyGUI::ResourceSkin { MYGUI_RTTI_DERIVED( AutoSizedResourceSkin ) public: - virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version); + void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) final; }; } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 23f8a121b..615e2dfc9 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -23,6 +23,8 @@ #include "../mwworld/nullaction.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/alchemy.hpp" + namespace { bool compareType(const std::string& type1, const std::string& type2) @@ -151,6 +153,8 @@ namespace MWGui : mCategory(Category_All) , mFilter(0) , mSortByType(true) + , mNameFilter("") + , mEffectFilter("") { mSourceModel = sourceModel; } @@ -199,8 +203,39 @@ namespace MWGui if (!(category & mCategory)) return false; - if ((mFilter & Filter_OnlyIngredients) && base.getTypeName() != typeid(ESM::Ingredient).name()) - return false; + if (mFilter & Filter_OnlyIngredients) + { + if (base.getTypeName() != typeid(ESM::Ingredient).name()) + return false; + + if (!mNameFilter.empty() && !mEffectFilter.empty()) + throw std::logic_error("name and magic effect filter are mutually exclusive"); + + if (!mNameFilter.empty()) + { + const auto itemName = Misc::StringUtils::lowerCase(base.getClass().getName(base)); + return itemName.find(mNameFilter) != std::string::npos; + } + + if (!mEffectFilter.empty()) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + const auto alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); + + const auto effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill); + + for (const auto& effect : effects) + { + const auto ciEffect = Misc::StringUtils::lowerCase(effect); + + if (ciEffect.find(mEffectFilter) != std::string::npos) + return true; + } + return false; + } + return true; + } + if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) return false; if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() @@ -250,6 +285,10 @@ namespace MWGui return false; } + std::string compare = Misc::StringUtils::lowerCase(item.mBase.getClass().getName(item.mBase)); + if(compare.find(mNameFilter) == std::string::npos) + return false; + return true; } @@ -277,6 +316,16 @@ namespace MWGui mFilter = filter; } + void SortFilterItemModel::setNameFilter (const std::string& filter) + { + mNameFilter = Misc::StringUtils::lowerCase(filter); + } + + void SortFilterItemModel::setEffectFilter (const std::string& filter) + { + mEffectFilter = Misc::StringUtils::lowerCase(filter); + } + void SortFilterItemModel::update() { mSourceModel->update(); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 98da8d8c9..3e616875e 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -25,6 +25,8 @@ namespace MWGui void setCategory (int category); void setFilter (int filter); + void setNameFilter (const std::string& filter); + void setEffectFilter (const std::string& filter); /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } @@ -57,6 +59,9 @@ namespace MWGui int mCategory; int mFilter; bool mSortByType; + + std::string mNameFilter; // filter by item name + std::string mEffectFilter; // filter by magic effect }; } diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp index 66113d869..a387cac39 100644 --- a/apps/openmw/mwgui/spellview.hpp +++ b/apps/openmw/mwgui/spellview.hpp @@ -19,7 +19,7 @@ namespace MWGui class SpellModel; ///@brief Displays a SpellModel in a list widget - class SpellView : public MyGUI::Widget + class SpellView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(SpellView) public: @@ -47,10 +47,10 @@ namespace MWGui /// Fired when a spell was clicked EventHandle_ModelIndex eventSpellClicked; - virtual void initialiseOverride(); + void initialiseOverride() final; - virtual void setSize(const MyGUI::IntSize& _value); - virtual void setCoord(const MyGUI::IntCoord& _value); + void setSize(const MyGUI::IntSize& _value) final; + void setCoord(const MyGUI::IntCoord& _value) final; void resetScrollbars(); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index d0be05dfb..41be4f3a8 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -45,8 +45,6 @@ namespace MWGui getWidget(mEffectBox, "EffectsBox"); getWidget(mFilterEdit, "FilterEdit"); - mFilterEdit->setUserString("IgnoreTabKey", "y"); - mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged); deleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onDeleteClicked); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0c9b31b65..b102b13ce 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -69,6 +69,7 @@ namespace MWGui getWidget(mTotalBalance, "TotalBalance"); getWidget(mTotalBalanceLabel, "TotalBalanceLabel"); getWidget(mBottomPane, "BottomPane"); + getWidget(mFilterEdit, "FilterEdit"); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &TradeWindow::onItemSelected); @@ -80,6 +81,7 @@ namespace MWGui mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); + mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &TradeWindow::onNameFilterChanged); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onCancelButtonClicked); mOfferButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onOfferButtonClicked); @@ -135,8 +137,7 @@ namespace MWGui setTitle(actor.getClass().getName(actor)); onFilterChanged(mFilterAll); - - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTotalBalance); + mFilterEdit->setCaption(""); } void TradeWindow::onFrame(float dt) @@ -144,6 +145,12 @@ namespace MWGui checkReferenceAvailable(); } + void TradeWindow::onNameFilterChanged(MyGUI::EditBox* _sender) + { + mSortModel->setNameFilter(_sender->getCaption()); + mItemView->update(); + } + void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 514d24022..0730df04f 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -59,6 +59,8 @@ namespace MWGui MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; + MyGUI::EditBox* mFilterEdit; + MyGUI::Button* mIncreaseButton; MyGUI::Button* mDecreaseButton; MyGUI::TextBox* mTotalBalanceLabel; @@ -86,6 +88,7 @@ namespace MWGui void sellItem (MyGUI::Widget* sender, int count); void onFilterChanged(MyGUI::Widget* _sender); + void onNameFilterChanged(MyGUI::EditBox* _sender); void onOfferButtonClicked(MyGUI::Widget* _sender); void onAccept(MyGUI::EditBox* sender); void onCancelButtonClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index d18eaebdd..64f912298 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -227,9 +227,9 @@ namespace MWGui void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) { - if (key == MyGUI::KeyCode::ArrowDown) + if (key == MyGUI::KeyCode::ArrowUp) mHourSlider->setScrollPosition(std::min(mHourSlider->getScrollPosition()+1, mHourSlider->getScrollRange()-1)); - else if (key == MyGUI::KeyCode::ArrowUp) + else if (key == MyGUI::KeyCode::ArrowDown) mHourSlider->setScrollPosition(std::max(static_cast(mHourSlider->getScrollPosition())-1, 0)); else return; diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 4b42b888a..ff3b2311a 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -90,7 +90,7 @@ namespace MWGui typedef std::vector SpellEffectList; - class MWSkill : public MyGUI::Widget + class MWSkill final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSkill ) public: @@ -116,7 +116,7 @@ namespace MWGui protected: virtual ~MWSkill(); - virtual void initialiseOverride(); + void initialiseOverride() final; void onClicked(MyGUI::Widget* _sender); @@ -131,7 +131,7 @@ namespace MWGui }; typedef MWSkill* MWSkillPtr; - class MWAttribute : public MyGUI::Widget + class MWAttribute final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWAttribute ) public: @@ -156,7 +156,7 @@ namespace MWGui protected: virtual ~MWAttribute(); - virtual void initialiseOverride(); + void initialiseOverride() final; void onClicked(MyGUI::Widget* _sender); @@ -175,7 +175,7 @@ namespace MWGui * @todo remove this class and use MWEffectList instead */ class MWSpellEffect; - class MWSpell : public MyGUI::Widget + class MWSpell final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSpell ) public: @@ -199,7 +199,7 @@ namespace MWGui protected: virtual ~MWSpell(); - virtual void initialiseOverride(); + void initialiseOverride() final; private: void updateWidgets(); @@ -209,7 +209,7 @@ namespace MWGui }; typedef MWSpell* MWSpellPtr; - class MWEffectList : public MyGUI::Widget + class MWEffectList final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWEffectList ) public: @@ -241,7 +241,7 @@ namespace MWGui protected: virtual ~MWEffectList(); - virtual void initialiseOverride(); + void initialiseOverride() final; private: void updateWidgets(); @@ -250,7 +250,7 @@ namespace MWGui }; typedef MWEffectList* MWEffectListPtr; - class MWSpellEffect : public MyGUI::Widget + class MWSpellEffect final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSpellEffect ) public: @@ -265,7 +265,7 @@ namespace MWGui protected: virtual ~MWSpellEffect(); - virtual void initialiseOverride(); + void initialiseOverride() final; private: static const int sIconOffset = 24; @@ -279,7 +279,7 @@ namespace MWGui }; typedef MWSpellEffect* MWSpellEffectPtr; - class MWDynamicStat : public MyGUI::Widget + class MWDynamicStat final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWDynamicStat ) public: @@ -294,7 +294,7 @@ namespace MWGui protected: virtual ~MWDynamicStat(); - virtual void initialiseOverride(); + void initialiseOverride() final; private: diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index ebcbcb85e..f3f423801 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -50,8 +51,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwrender/vismask.hpp" - #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" @@ -1905,8 +1904,8 @@ namespace MWGui // Turn off all rendering except for the GUI int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); - mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); + mViewer->getUpdateVisitor()->setTraversalMask(SceneUtil::Mask_GUI); + mViewer->getCamera()->setCullMask(SceneUtil::Mask_GUI); MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); sizeVideo(screenSize.width, screenSize.height); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e2bf08d82..8ee248ee5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -24,8 +25,6 @@ #include "../mwmechanics/aibreathe.hpp" -#include "../mwrender/vismask.hpp" - #include "spellcasting.hpp" #include "steering.hpp" #include "npcstats.hpp" @@ -452,22 +451,12 @@ namespace MWMechanics return; CreatureStats &stats = actor.getClass().getCreatureStats(actor); - int hello = stats.getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello == 0) - return; - - if (MWBase::Environment::get().getWorld()->isSwimming(actor)) - return; - - MWWorld::Ptr player = getPlayer(); - osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - osg::Vec3f dir = playerPos - actorPos; - const MWMechanics::AiSequence& seq = stats.getAiSequence(); int packageId = seq.getTypeId(); - if (seq.isInCombat() || (packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1)) + if (seq.isInCombat() || + MWBase::Environment::get().getWorld()->isSwimming(actor) || + (packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1)) { stats.setTurningToPlayer(false); stats.setGreetingTimer(0); @@ -475,6 +464,11 @@ namespace MWMechanics return; } + MWWorld::Ptr player = getPlayer(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + osg::Vec3f dir = playerPos - actorPos; + if (stats.isTurningToPlayer()) { // Reduce the turning animation glitch by using a *HUGE* value of @@ -492,11 +486,10 @@ namespace MWMechanics return; // Play a random voice greeting if the player gets too close - float helloDistance = static_cast(hello); static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); - helloDistance *= iGreetDistanceMultiplier; + float helloDistance = static_cast(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); int greetingTimer = stats.getGreetingTimer(); GreetingState greetingState = stats.getGreetingState(); @@ -1241,6 +1234,11 @@ namespace MWMechanics if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) inventoryStore.unequipItem(*heldIter, ptr); } + else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) + { + // For hostile NPCs, see if they have anything better to equip first + inventoryStore.autoEquip(ptr); + } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); @@ -1420,11 +1418,11 @@ namespace MWMechanics const float dist = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length(); if (dist > mActorsProcessingRange) { - ptr.getRefData().getBaseNode()->setNodeMask(0); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Disabled); return; } else - ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Actor); // Fade away actors on large distance (>90% of actor's processing distance) float visibilityRatio = 1.0; @@ -1748,12 +1746,12 @@ namespace MWMechanics if (!inRange) { - iter->first.getRefData().getBaseNode()->setNodeMask(0); + iter->first.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Disabled); world->setActorCollisionMode(iter->first, false, false); continue; } else if (!isPlayer) - iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + iter->first.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Actor); const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead(); if (!isDead && iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) @@ -1852,6 +1850,8 @@ namespace MWMechanics stats.getActiveSpells().visitEffectSources(soulTrap); } + // Magic effects will be reset later, and the magic effect that could kill the actor + // needs to be determined now calculateCreatureStatModifiers(iter->first, 0); if (cls.isEssential(iter->first)) @@ -1869,7 +1869,10 @@ namespace MWMechanics // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); + // Reset dynamic stats, attributes and skills calculateCreatureStatModifiers(iter->first, 0); + if (iter->first.getClass().isNpc()) + calculateNpcStatModifiers(iter->first, 0); if( iter->first == getPlayer()) { diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index f7761e58f..e4319b425 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -44,7 +44,7 @@ namespace MWMechanics void fastForward(const MWWorld::Ptr& actor, AiState& state); - virtual osg::Vec3f getDestination() { return osg::Vec3f(mX, mY, mZ); } + virtual osg::Vec3f getDestination() const { return osg::Vec3f(mX, mY, mZ); } private: std::string mCellId; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 7b5a9e63e..24263bbc0 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -76,7 +76,7 @@ namespace MWMechanics void fastForward(const MWWorld::Ptr& actor, AiState& state); - virtual osg::Vec3f getDestination() + virtual osg::Vec3f getDestination() const { MWWorld::Ptr target = getTarget(); if (target.isEmpty()) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index b9b3baf64..ec0715e52 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -102,7 +102,7 @@ namespace MWMechanics /// Return true if this package should repeat. Currently only used for Wander packages. virtual bool getRepeat() const; - virtual osg::Vec3f getDestination() { return osg::Vec3f(0, 0, 0); } + virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } // Return true if any loaded actor with this AI package must be active. virtual bool alwaysActive() const { return false; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5760069e7..5f3931fcf 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -28,6 +28,10 @@ void AiSequence::copy (const AiSequence& sequence) for (std::list::const_iterator iter (sequence.mPackages.begin()); iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); + + // We need to keep an AiWander storage, if present - it has a state machine. + // Not sure about another temporary storages + sequence.mAiState.copy(mAiState); } AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(-1) {} diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 949fb74dd..976e21c65 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -38,6 +38,14 @@ namespace MWMechanics //return a reference to the (new allocated) object return *result; } + + template< class Derived > + void copy(DerivedClassStorage& destination) const + { + Derived* result = dynamic_cast(mStorage); + if (result != nullptr) + destination.store(*result); + } template< class Derived > void store( const Derived& payload ) diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 0f3b22e11..dba70316b 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -54,8 +54,9 @@ namespace MWMechanics stats.setMovementFlag(CreatureStats::Flag_Run, false); stats.setDrawState(DrawState_Nothing); + // Note: we should cancel internal "return after combat" package, if original location is too far away if (!isWithinMaxRange(targetPos, actorPos)) - return false; + return mHidden; // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. // If we got close to target, check for actors nearby. If they are, finish AI package. diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index a333c83fd..e7895462f 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -36,7 +36,7 @@ namespace MWMechanics virtual bool alwaysActive() const { return true; } - virtual osg::Vec3f getDestination() { return osg::Vec3f(mX, mY, mZ); } + virtual osg::Vec3f getDestination() const { return osg::Vec3f(mX, mY, mZ); } private: float mX; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index b20b1cb97..ff213b219 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -61,6 +61,34 @@ namespace MWMechanics rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0)); return position + osg::Vec3f(distance, 0.0, 0.0) * rotation; } + + bool isDestinationHidden(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) + { + const auto position = actor.getRefData().getPosition().asVec3(); + const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); + const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + osg::Vec3f direction = destination - position; + direction.normalize(); + const auto visibleDestination = ( + isWaterCreature || isFlyingCreature + ? destination + : destination + osg::Vec3f(0, 0, halfExtents.z()) + ) + direction * std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + const int mask = MWPhysics::CollisionType_World + | MWPhysics::CollisionType_HeightMap + | MWPhysics::CollisionType_Door + | MWPhysics::CollisionType_Actor; + return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); + } + + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) + { + const auto world = MWBase::Environment::get().getWorld(); + const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); + const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); + } } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): @@ -145,15 +173,6 @@ namespace MWMechanics // get or create temporary storage AiWanderStorage& storage = state.get(); - const MWWorld::CellStore*& currentCell = storage.mCell; - bool cellChange = currentCell && (actor.getCell() != currentCell); - if(!currentCell || cellChange) - { - stopWalking(actor, storage); - currentCell = actor.getCell(); - storage.mPopulateAvailableNodes = true; - mStoredInitialActorPosition = false; - } mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); @@ -200,14 +219,13 @@ namespace MWMechanics if (AI_REACTION_TIME <= lastReaction) { lastReaction = 0; - return reactionTimeActions(actor, storage, currentCell, cellChange, pos); + return reactionTimeActions(actor, storage, pos); } else return false; } - bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) + bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) { if (mDistance <= 0) storage.mCanWanderAlongPathGrid = false; @@ -229,7 +247,7 @@ namespace MWMechanics // Initialization to discover & store allowed node points for this actor. if (storage.mPopulateAvailableNodes) { - getAllowedNodes(actor, currentCell->getCell(), storage); + getAllowedNodes(actor, actor.getCell()->getCell(), storage); } if (canActorMoveByZAxis(actor) && mDistance > 0) { @@ -258,10 +276,6 @@ namespace MWMechanics completeManualWalking(actor, storage); } - // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. - if(mDistance && cellChange) - mDistance = 0; - AiWanderStorage::WanderState& wanderState = storage.mState; if ((wanderState == AiWanderStorage::Wander_MoveNow) && storage.mCanWanderAlongPathGrid) { @@ -279,6 +293,11 @@ namespace MWMechanics completeManualWalking(actor, storage); } + if (wanderState == AiWanderStorage::Wander_Walking + && (isDestinationHidden(actor, mPathFinder.getPath().back()) + || isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back()))) + completeManualWalking(actor, storage); + return false; // AiWander package not yet completed } @@ -330,7 +349,7 @@ namespace MWMechanics if (!isWaterCreature && !isFlyingCreature) { // findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance - if (const auto destination = navigator->findRandomPointAroundCircle(halfExtents, currentPosition, wanderDistance, navigatorFlags)) + if (const auto destination = navigator->findRandomPointAroundCircle(halfExtents, mInitialActorPosition, wanderDistance, navigatorFlags)) mDestination = *destination; else mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius); @@ -342,7 +361,10 @@ namespace MWMechanics if (!isWaterCreature && destinationIsAtWater(actor, mDestination)) continue; - if ((isWaterCreature || isFlyingCreature) && destinationThroughGround(currentPosition, mDestination)) + if (isDestinationHidden(actor, mDestination)) + continue; + + if (isAreaOccupiedByOtherActor(actor, mDestination)) continue; if (isWaterCreature || isFlyingCreature) @@ -371,16 +393,6 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); } - /* - * Returns true if the start to end point travels through a collision point (land). - */ - bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) { - const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; - return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(), - destination.x(), destination.y(), destination.z(), - mask); - } - void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { stopWalking(actor, storage); mObstacleCheck.clear(); @@ -529,7 +541,7 @@ namespace MWMechanics unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); - ToWorldCoordinates(dest, storage.mCell->getCell()); + ToWorldCoordinates(dest, actor.getCell()->getCell()); // actor position is already in world coordinates const osg::Vec3f start = actorPos.asVec3(); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 72f9b3228..376be3a25 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -27,8 +27,6 @@ namespace MWMechanics { float mReaction; // update some actions infrequently - const MWWorld::CellStore* mCell; // for detecting cell change - // AiWander states enum WanderState { @@ -60,7 +58,6 @@ namespace MWMechanics AiWanderStorage(): mReaction(0), - mCell(nullptr), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), @@ -100,6 +97,8 @@ namespace MWMechanics virtual int getTypeId() const; + virtual bool useVariableSpeed() const { return true;} + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); @@ -108,6 +107,14 @@ namespace MWMechanics osg::Vec3f getDestination(const MWWorld::Ptr& actor) const; + virtual osg::Vec3f getDestination() const + { + if (!mHasDestination) + return osg::Vec3f(0, 0, 0); + + return mDestination; + } + private: // NOTE: mDistance and mDuration must be set already void init(); @@ -125,12 +132,10 @@ namespace MWMechanics void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); - bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); + bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); - bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); int mDistance; // how far the actor can wander from the spawn point @@ -141,7 +146,7 @@ namespace MWMechanics bool mRepeat; bool mStoredInitialActorPosition; - osg::Vec3f mInitialActorPosition; + osg::Vec3f mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell bool mHasDestination; osg::Vec3f mDestination; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 87ee47b49..b490db436 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -554,3 +554,37 @@ std::string MWMechanics::Alchemy::suggestPotionName() return MWBase::Environment::get().getWorld()->getStore().get().find( ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); } + +std::vector MWMechanics::Alchemy::effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySkill) +{ + std::vector effects; + + const auto& item = ptr.get()->mBase; + const auto& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const static auto fWortChanceValue = gmst.find("fWortChanceValue")->mValue.getFloat(); + const auto& data = item->mData; + + for (auto i = 0; i < 4; ++i) + { + const auto effectID = data.mEffectID[i]; + const auto skillID = data.mSkills[i]; + const auto attributeID = data.mAttributes[i]; + + if (alchemySkill < fWortChanceValue * (i + 1)) + break; + + if (effectID != -1) + { + std::string effect = gmst.find(ESM::MagicEffect::effectIdToString(effectID))->mValue.getString(); + + if (skillID != -1) + effect += " " + gmst.find(ESM::Skill::sSkillNameIds[skillID])->mValue.getString(); + else if (attributeID != -1) + effect += " " + gmst.find(ESM::Attribute::sGmstAttributeIds[attributeID])->mValue.getString(); + + effects.push_back(effect); + + } + } + return effects; +} diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index 9f9f0b21c..d23f978ea 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -131,6 +131,8 @@ namespace MWMechanics ///< Try to create potions from the ingredients, place them in the inventory of the alchemist and /// adjust the skills of the alchemist accordingly. /// \param name must not be an empty string, or Result_NoName is returned + + static std::vector effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySKill); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index aa6cd142b..ee48ea7d5 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -455,6 +455,11 @@ void MWMechanics::NpcStats::setTimeToStartDrowning(float time) mTimeToStartDrowning=time; } +void MWMechanics::NpcStats::writeState (ESM::CreatureStats& state) const +{ + CreatureStats::writeState(state); +} + void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const { for (std::map::const_iterator iter (mFactionRank.begin()); @@ -494,6 +499,10 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mTimeToStartDrowning = mTimeToStartDrowning; } +void MWMechanics::NpcStats::readState (const ESM::CreatureStats& state) +{ + CreatureStats::readState(state); +} void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) { diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 47784ac1d..9bd8e20ad 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -127,8 +127,10 @@ namespace MWMechanics /// @param time value from [0,20] void setTimeToStartDrowning(float time); + void writeState (ESM::CreatureStats& state) const; void writeState (ESM::NpcStats& state) const; + void readState (const ESM::CreatureStats& state); void readState (const ESM::NpcStats& state); }; } diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 6268eaddf..e30a2947f 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -120,6 +120,7 @@ namespace MWMechanics mWalkState = WalkState::Norm; mStateDuration = 0; mPrev = position; + mInitialDistance = (destination - position).length(); return; } @@ -129,10 +130,11 @@ namespace MWMechanics const float prevDistance = (destination - mPrev).length(); const float currentDistance = (destination - position).length(); const float movedDistance = prevDistance - currentDistance; + const float movedFromInitialDistance = mInitialDistance - currentDistance; mPrev = position; - if (movedDistance >= distSameSpot) + if (movedDistance >= distSameSpot && movedFromInitialDistance >= distSameSpot) { mWalkState = WalkState::Norm; mStateDuration = 0; @@ -143,6 +145,7 @@ namespace MWMechanics { mWalkState = WalkState::CheckStuck; mStateDuration = duration; + mInitialDistance = (destination - position).length(); return; } diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 8314031ea..6c2197d81 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -54,6 +54,7 @@ namespace MWMechanics float mStateDuration; int mEvadeDirectionIndex; + float mInitialDistance = 0; void chooseEvasionDirection(); }; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f7e7c277c..a7bba5b63 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -314,7 +314,9 @@ namespace MWMechanics { mPath.clear(); - buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); + // If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path + if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath))) + mPath.push_back(endPoint); mConstructed = true; } @@ -335,24 +337,27 @@ namespace MWMechanics mConstructed = true; } - void PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, std::back_insert_iterator> out) { - try + const auto world = MWBase::Environment::get().getWorld(); + const auto stepSize = getPathStepSize(actor); + const auto navigator = world->getNavigator(); + const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out); + + if (status == DetourNavigator::Status::NavMeshNotFound) + return false; + + if (status != DetourNavigator::Status::Success) { - const auto world = MWBase::Environment::get().getWorld(); - const auto stepSize = getPathStepSize(actor); - const auto navigator = world->getNavigator(); - navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out); - } - catch (const DetourNavigator::NavigatorException& exception) - { - Log(Debug::Debug) << "Build path by navigator exception: \"" << exception.what() + Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() << ") from " << startPoint << " to " << endPoint << " with flags (" << DetourNavigator::WriteFlags {flags} << ")"; } + + return true; } void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, @@ -367,26 +372,30 @@ namespace MWMechanics if (sqrDistanceIgnoreZ(mPath.front(), startPoint) <= 4 * stepSize * stepSize) return; - try + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + std::deque prePath; + auto prePathInserter = std::back_inserter(prePath); + const auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, + prePathInserter); + + if (status == DetourNavigator::Status::NavMeshNotFound) + return; + + if (status != DetourNavigator::Status::Success) { - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - std::deque prePath; - navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, std::back_inserter(prePath)); - - while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.front(), startPoint) < stepSize * stepSize) - prePath.pop_front(); - - while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.back(), mPath.front()) < stepSize * stepSize) - prePath.pop_back(); - - std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); - } - catch (const DetourNavigator::NavigatorException& exception) - { - Log(Debug::Debug) << "Build path by navigator exception: \"" << exception.what() + Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() << ") from " << startPoint << " to " << mPath.front() << " with flags (" << DetourNavigator::WriteFlags {flags} << ")"; + return; } + + while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.front(), startPoint) < stepSize * stepSize) + prePath.pop_front(); + + while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.back(), mPath.front()) < stepSize * stepSize) + prePath.pop_back(); + + std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index b413810f4..06b4aa10d 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -201,7 +201,7 @@ namespace MWMechanics void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); - void buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + bool buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, std::back_insert_iterator> out); }; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 0b7c27b86..21fa7f369 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1199,10 +1199,10 @@ namespace MWMechanics return false; } - void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude) + void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false) { DynamicStat stat = creatureStats.getDynamic(index); - stat.setCurrent(stat.getCurrent() + magnitude, index == 2); + stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero); creatureStats.setDynamic(index, stat); } @@ -1241,9 +1241,12 @@ namespace MWMechanics case ESM::MagicEffect::DamageMagicka: case ESM::MagicEffect::DamageFatigue: - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); + { + int index = effectKey.mId-ESM::MagicEffect::DamageHealth; + static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); + adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue); break; - + } case ESM::MagicEffect::AbsorbHealth: if (magnitude > 0.f) receivedMagicDamage = true; diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp new file mode 100644 index 000000000..58e7373e5 --- /dev/null +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -0,0 +1,72 @@ +#ifndef OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H +#define OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H + +#include +#include +#include +#include + +#include + +namespace MWPhysics +{ + // https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection + bool testAabbAgainstSphere(const btVector3& aabbMin, const btVector3& aabbMax, + const btVector3& position, const btScalar radius) + { + const btVector3 nearest( + std::max(aabbMin.x(), std::min(aabbMax.x(), position.x())), + std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())), + std::max(aabbMin.z(), std::min(aabbMax.z(), position.z())) + ); + return nearest.distance(position) < radius; + } + + class HasSphereCollisionCallback final : public btBroadphaseAabbCallback + { + public: + HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, + const int mask, const int group) + : mPosition(position), + mRadius(radius), + mCollisionObject(object), + mCollisionFilterMask(mask), + mCollisionFilterGroup(group) + { + } + + bool process(const btBroadphaseProxy* proxy) final + { + if (mResult) + return false; + const auto collisionObject = static_cast(proxy->m_clientObject); + if (collisionObject == mCollisionObject) + return true; + if (needsCollision(*proxy)) + mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); + return !mResult; + } + + bool getResult() const + { + return mResult; + } + + private: + btVector3 mPosition; + btScalar mRadius; + btCollisionObject* mCollisionObject; + int mCollisionFilterMask; + int mCollisionFilterGroup; + bool mResult = false; + + bool needsCollision(const btBroadphaseProxy& proxy) const + { + bool collides = (proxy.m_collisionFilterGroup & mCollisionFilterMask) != 0; + collides = collides && (mCollisionFilterGroup & proxy.m_collisionFilterMask); + return collides; + } + }; +} + +#endif diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 341dca3e1..f02c6fd95 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -51,6 +51,7 @@ #include "trace.h" #include "object.hpp" #include "heightfield.hpp" +#include "hasspherecollisioncallback.hpp" namespace MWPhysics { @@ -1539,4 +1540,20 @@ namespace MWPhysics mCollisionWorld->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor); } + + bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + { + btCollisionObject* object = nullptr; + const auto it = mActors.find(ignore); + if (it != mActors.end()) + object = it->second->getCollisionObject(); + const auto bulletPosition = Misc::Convert::toBullet(position); + const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); + const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); + const int mask = MWPhysics::CollisionType_Actor; + const int group = 0xff; + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); + mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); + return callback.getResult(); + } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 364a59ab1..d74e2de16 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -187,6 +187,8 @@ namespace MWPhysics std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); } + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + private: void updateWater(); diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index fcffe220b..1c54f0684 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -31,8 +32,6 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" -#include "vismask.hpp" - namespace MWRender { @@ -367,7 +366,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) // Otherwise add the enchanted glow to it. if (!showHolsteredWeapons) { - weaponNode->setNodeMask(0); + weaponNode->setNodeMask(SceneUtil::Mask_Disabled); } else { @@ -541,7 +540,7 @@ void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); osg::Vec4f ambient(1,1,1,1); - osg::ref_ptr lightSource = SceneUtil::createLightSource(esmLight, Mask_Lighting, exterior, ambient); + osg::ref_ptr lightSource = SceneUtil::createLightSource(esmLight, exterior, ambient); mInsert->addChild(lightSource); diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp index 35b255355..ec90949bc 100644 --- a/apps/openmw/mwrender/actorspaths.cpp +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -1,7 +1,7 @@ #include "actorspaths.hpp" -#include "vismask.hpp" #include +#include #include @@ -43,7 +43,7 @@ namespace MWRender const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); if (newGroup) { - newGroup->setNodeMask(Mask_Debug); + newGroup->setNodeMask(SceneUtil::Mask_Debug); mRootNode->addChild(newGroup); mGroups[actor] = newGroup; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5280b1123..ff2ff2bb7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -45,7 +46,6 @@ #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority #include "../mwmechanics/actorutil.hpp" -#include "vismask.hpp" #include "util.hpp" #include "rotatecontroller.hpp" @@ -579,7 +579,7 @@ namespace MWRender else { // Hide effect immediately - node->setNodeMask(0); + node->setNodeMask(SceneUtil::Mask_Disabled); mFinished = true; } } @@ -1661,7 +1661,7 @@ namespace MWRender { bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior); + SceneUtil::addLight(parent, esmLight, exterior); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) @@ -1713,7 +1713,7 @@ namespace MWRender // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; node->accept(disableFreezeOnCullVisitor); - node->setNodeMask(Mask_Effect); + node->setNodeMask(SceneUtil::Mask_Effect); params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); params.mLoop = loop; @@ -1872,7 +1872,7 @@ namespace MWRender SceneUtil::configureLight(light, radius, isExterior); mGlowLight = new SceneUtil::LightSource; - mGlowLight->setNodeMask(Mask_Lighting); + mGlowLight->setNodeMask(SceneUtil::Mask_Lighting); mInsert->addChild(mGlowLight); mGlowLight->setLight(light); } diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 4cf76e473..9883d9fe3 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -7,7 +7,7 @@ #include -#include "vismask.hpp" +#include namespace { @@ -34,7 +34,7 @@ void DebugDrawer::createGeometry() if (!mGeometry) { mGeometry = new osg::Geometry; - mGeometry->setNodeMask(Mask_Debug); + mGeometry->setNodeMask(SceneUtil::Mask_Debug); mVertices = new osg::Vec3Array; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index b2552e598..c74c70b55 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -27,7 +28,6 @@ #include "../mwmechanics/weapontype.hpp" #include "npcanimation.hpp" -#include "vismask.hpp" namespace MWRender { @@ -61,7 +61,7 @@ namespace MWRender } else { - node->setNodeMask(0); + node->setNodeMask(SceneUtil::Mask_Disabled); } } @@ -138,9 +138,9 @@ namespace MWRender mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - mCamera->setCullMask(~(Mask_UpdateVisitor)); + mCamera->setCullMask(~(SceneUtil::Mask_UpdateVisitor)); - mCamera->setNodeMask(Mask_RenderToTexture); + mCamera->setNodeMask(SceneUtil::Mask_RenderToTexture); osg::ref_ptr lightManager = new SceneUtil::LightManager; lightManager->setStartLight(1); @@ -255,7 +255,7 @@ namespace MWRender void CharacterPreview::redraw() { - mCamera->setNodeMask(Mask_RenderToTexture); + mCamera->setNodeMask(SceneUtil::Mask_RenderToTexture); mDrawOnceCallback->redrawNextFrame(); } @@ -364,7 +364,7 @@ namespace MWRender visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); - mCamera->setNodeMask(~0); + mCamera->setNodeMask(SceneUtil::Mask_Default); mCamera->accept(visitor); mCamera->setNodeMask(nodeMask); diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 3e785a769..450cb20f5 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -6,9 +6,9 @@ #include #include +#include #include "animation.hpp" -#include "vismask.hpp" #include "util.hpp" namespace MWRender @@ -29,7 +29,7 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); - node->setNodeMask(Mask_Effect); + node->setNodeMask(SceneUtil::Mask_Effect); Effect effect; effect.mAnimTime.reset(new EffectAnimationTime); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 72e8679eb..d51791da6 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -24,8 +25,6 @@ #include "../mwworld/esmstore.hpp" -#include "vismask.hpp" - namespace { @@ -76,7 +75,7 @@ namespace { if (mParent->copyResult(static_cast(node), nv->getTraversalNumber())) { - node->setNodeMask(0); + node->setNodeMask(SceneUtil::Mask_Disabled); mParent->markForRemoval(static_cast(node)); } return; @@ -288,7 +287,7 @@ namespace MWRender float srcLeft, float srcTop, float srcRight, float srcBottom) { osg::ref_ptr camera (new osg::Camera); - camera->setNodeMask(Mask_RenderToTexture); + camera->setNodeMask(SceneUtil::Mask_RenderToTexture); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setViewMatrix(osg::Matrix::identity()); camera->setProjectionMatrix(osg::Matrix::identity()); @@ -458,7 +457,7 @@ namespace MWRender if (map.mImageData.empty()) return; - Files::IMemStream istream(&map.mImageData[0], map.mImageData.size()); + Files::IMemStream istream(map.mImageData.data(), map.mImageData.size()); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) @@ -523,7 +522,7 @@ namespace MWRender if (srcBox == destBox && imageWidth == mWidth && imageHeight == mHeight) { - mOverlayImage->copySubImage(0, 0, 0, image); + mOverlayImage = image; requestOverlayTextureUpdate(0, 0, mWidth, mHeight, texture, true, false); } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index f4a54eb98..7bd202e7e 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -25,8 +26,6 @@ #include "../mwworld/cellstore.hpp" -#include "vismask.hpp" - namespace { @@ -42,7 +41,7 @@ namespace virtual void operator()(osg::Node* node, osg::NodeVisitor*) { if (mRendered) - node->setNodeMask(0); + node->setNodeMask(SceneUtil::Mask_Disabled); if (!mRendered) { @@ -178,8 +177,8 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); - camera->setNodeMask(Mask_RenderToTexture); + camera->setCullMask(SceneUtil::Mask_Scene | SceneUtil::Mask_SimpleWater | SceneUtil::Mask_Terrain | SceneUtil::Mask_Object | SceneUtil::Mask_Static); + camera->setNodeMask(SceneUtil::Mask_RenderToTexture); osg::ref_ptr stateset = new osg::StateSet; stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); @@ -376,7 +375,7 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object | Mask_Static); + computeBoundsVisitor.setTraversalMask(SceneUtil::Mask_Scene | SceneUtil::Mask_Terrain | SceneUtil::Mask_Object | SceneUtil::Mask_Static); mSceneRoot->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index bfc80914a..7aade0c23 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -1,7 +1,7 @@ #include "navmesh.hpp" -#include "vismask.hpp" #include +#include #include @@ -34,7 +34,7 @@ namespace MWRender void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) { - if (!mEnabled || (mGroup && mId == id && mGeneration >= generation && mRevision >= revision)) + if (!mEnabled || (mGroup && mId == id && mGeneration == generation && mRevision == revision)) return; mId = id; @@ -45,7 +45,7 @@ namespace MWRender mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); if (mGroup) { - mGroup->setNodeMask(Mask_Debug); + mGroup->setNodeMask(SceneUtil::Mask_Debug); mRootNode->addChild(mGroup); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 40a75d902..35a11d605 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -43,7 +44,6 @@ #include "camera.hpp" #include "rotatecontroller.hpp" #include "renderbin.hpp" -#include "vismask.hpp" namespace { @@ -81,6 +81,34 @@ std::string getVampireHead(const std::string& race, bool female) return "meshes\\" + bodyPart->mModel; } +std::string getShieldBodypartMesh(const std::vector& bodyparts, bool female) +{ + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + for (const auto& part : bodyparts) + { + if (part.mPart != ESM::PRT_Shield) + continue; + + std::string bodypartName; + if (female && !part.mFemale.empty()) + bodypartName = part.mFemale; + else if (!part.mMale.empty()) + bodypartName = part.mMale; + + if (!bodypartName.empty()) + { + const ESM::BodyPart *bodypart = partStore.search(bodypartName); + if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) + return std::string(); + if (!bodypart->mModel.empty()) + return "meshes\\" + bodypart->mModel; + } + } + + return std::string(); +} + } @@ -241,6 +269,9 @@ void HeadAnimationTime::setBlinkStop(float value) NpcAnimation::NpcType NpcAnimation::getNpcType() { const MWWorld::Class &cls = mPtr.getClass(); + // Dead vampires should typically stay vampires. + if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf()) + return mNpcType; NpcAnimation::NpcType curType = Type_Normal; if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) curType = Type_Vampire; @@ -507,7 +538,7 @@ void NpcAnimation::updateNpcBase() addAnimSource(smodel, smodel); - mObjectRoot->setNodeMask(Mask_FirstPerson); + mObjectRoot->setNodeMask(SceneUtil::Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); } @@ -519,36 +550,9 @@ std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const std::string mesh = shield.getClass().getModel(shield); const ESM::Armor *armor = shield.get()->mBase; const std::vector& bodyparts = armor->mParts.mParts; + // Try to recover the body part model, use ground model as a fallback otherwise. if (!bodyparts.empty()) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - - // Try to get shield model from bodyparts first, with ground model as fallback - for (const auto& part : bodyparts) - { - if (part.mPart != ESM::PRT_Shield) - continue; - - std::string bodypartName; - if (!mNpc->isMale() && !part.mFemale.empty()) - bodypartName = part.mFemale; - else if (!part.mMale.empty()) - bodypartName = part.mMale; - - if (!bodypartName.empty()) - { - const ESM::BodyPart *bodypart = partStore.search(bodypartName); - if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) - return std::string(); - else if (!bodypart->mModel.empty()) - { - mesh = "meshes\\" + bodypart->mModel; - break; - } - } - } - } + mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); if (mesh.empty()) return std::string(); @@ -586,7 +590,7 @@ void NpcAnimation::updateParts() int mBasePriority; } slotlist[] = { // FIXME: Priority is based on the number of reserved slots. There should be a better way. - { MWWorld::InventoryStore::Slot_Robe, 12 }, + { MWWorld::InventoryStore::Slot_Robe, 11 }, { MWWorld::InventoryStore::Slot_Skirt, 3 }, { MWWorld::InventoryStore::Slot_Helmet, 0 }, { MWWorld::InventoryStore::Slot_Cuirass, 0 }, @@ -640,7 +644,7 @@ void NpcAnimation::updateParts() ESM::PartReferenceType parts[] = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, - ESM::PRT_RForearm, ESM::PRT_LForearm + ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass }; size_t parts_size = sizeof(parts)/sizeof(parts[0]); for(size_t p = 0;p < parts_size;++p) @@ -1002,9 +1006,19 @@ void NpcAnimation::showCarriedLeft(bool show) { osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); std::string mesh = iter->getClass().getModel(*iter); - if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, - mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) + // For shields we must try to use the body part model + if (iter->getTypeName() == typeid(ESM::Armor).name()) { + const ESM::Armor *armor = iter->get()->mBase; + const std::vector& bodyparts = armor->mParts.mParts; + if (!bodyparts.empty()) + mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); + } + if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, + mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) + { + if (mesh.empty()) + reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index ec1c4397b..83fd807dc 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" @@ -12,8 +13,6 @@ #include "animation.hpp" #include "npcanimation.hpp" #include "creatureanimation.hpp" -#include "vismask.hpp" - namespace MWRender { @@ -71,7 +70,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) { insertBegin(ptr); - ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Object); osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); @@ -81,7 +80,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, bool weaponsShields) { insertBegin(ptr); - ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Actor); // CreatureAnimation osg::ref_ptr anim; @@ -98,7 +97,7 @@ void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, b void Objects::insertNPC(const MWWorld::Ptr &ptr) { insertBegin(ptr); - ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); + ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Actor); osg::ref_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index 797794457..a2c5a1f46 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" @@ -17,8 +18,6 @@ #include "../mwmechanics/pathfinding.hpp" #include "../mwmechanics/coordinateconverter.hpp" -#include "vismask.hpp" - namespace MWRender { @@ -73,7 +72,7 @@ void Pathgrid::togglePathgrid() { // add path grid meshes to already loaded cells mPathGridRoot = new osg::Group; - mPathGridRoot->setNodeMask(Mask_Debug); + mPathGridRoot->setNodeMask(SceneUtil::Mask_Pathgrid); mRootNode->addChild(mPathGridRoot); for(const MWWorld::CellStore* cell : mActiveCells) diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp new file mode 100644 index 000000000..5aec174df --- /dev/null +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -0,0 +1,92 @@ +#include "recastmesh.hpp" + +#include +#include +#include + +#include + +namespace MWRender +{ + RecastMesh::RecastMesh(const osg::ref_ptr& root, bool enabled) + : mRootNode(root) + , mEnabled(enabled) + { + } + + RecastMesh::~RecastMesh() + { + if (mEnabled) + disable(); + } + + bool RecastMesh::toggle() + { + if (mEnabled) + disable(); + else + enable(); + + return mEnabled; + } + + void RecastMesh::update(const DetourNavigator::RecastMeshTiles& tiles, const DetourNavigator::Settings& settings) + { + if (!mEnabled) + return; + + for (auto it = mGroups.begin(); it != mGroups.end();) + { + const auto tile = tiles.find(it->first); + if (tile == tiles.end()) + { + mRootNode->removeChild(it->second.mValue); + it = mGroups.erase(it); + continue; + } + + if (it->second.mGeneration != tile->second->getGeneration() + || it->second.mRevision != tile->second->getRevision()) + { + const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings); + group->setNodeMask(SceneUtil::Mask_Debug); + mRootNode->removeChild(it->second.mValue); + mRootNode->addChild(group); + it->second.mValue = group; + it->second.mGeneration = tile->second->getGeneration(); + it->second.mRevision = tile->second->getRevision(); + continue; + } + + ++it; + } + + for (const auto& tile : tiles) + { + if (mGroups.count(tile.first)) + continue; + const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings); + group->setNodeMask(SceneUtil::Mask_Debug); + mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); + mRootNode->addChild(group); + } + } + + void RecastMesh::reset() + { + std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); }); + mGroups.clear(); + } + + void RecastMesh::enable() + { + std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->addChild(v.second.mValue); }); + mEnabled = true; + } + + void RecastMesh::disable() + { + std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); }); + mEnabled = false; + } +} diff --git a/apps/openmw/mwrender/recastmesh.hpp b/apps/openmw/mwrender/recastmesh.hpp new file mode 100644 index 000000000..729438dbe --- /dev/null +++ b/apps/openmw/mwrender/recastmesh.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_MWRENDER_RECASTMESH_H +#define OPENMW_MWRENDER_RECASTMESH_H + +#include + +#include + +#include + +namespace osg +{ + class Group; + class Geometry; +} + +namespace MWRender +{ + class RecastMesh + { + public: + RecastMesh(const osg::ref_ptr& root, bool enabled); + ~RecastMesh(); + + bool toggle(); + + void update(const DetourNavigator::RecastMeshTiles& recastMeshTiles, const DetourNavigator::Settings& settings); + + void reset(); + + void enable(); + + void disable(); + + bool isEnabled() const + { + return mEnabled; + } + + private: + struct Group + { + std::size_t mGeneration; + std::size_t mRevision; + osg::ref_ptr mValue; + }; + + osg::ref_ptr mRootNode; + bool mEnabled; + std::map mGroups; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 503a79b1f..9866decd0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -59,7 +60,6 @@ #include "sky.hpp" #include "effectmanager.hpp" #include "npcanimation.hpp" -#include "vismask.hpp" #include "pathgrid.hpp" #include "camera.hpp" #include "water.hpp" @@ -67,6 +67,7 @@ #include "util.hpp" #include "navmesh.hpp" #include "actorspaths.hpp" +#include "recastmesh.hpp" #ifdef USE_OPENXR #include "../mwvr/vranimation.hpp" @@ -221,9 +222,11 @@ namespace MWRender , mFieldOfViewOverride(0.f) , mBorders(false) { - resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); + resourceSystem->getSceneManager()->setParticleSystemMask(SceneUtil::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); - resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode + // Shadows and radial fog have problems with fixed-function mode + bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows"); + resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -233,21 +236,21 @@ namespace MWRender resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; - sceneRoot->setLightingMask(Mask_Lighting); + sceneRoot->setLightingMask(SceneUtil::Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - int shadowCastingTraversalMask = Mask_Scene; + int shadowCastingTraversalMask = SceneUtil::Mask_Scene; if (Settings::Manager::getBool("actor shadows", "Shadows")) - shadowCastingTraversalMask |= Mask_Actor; + shadowCastingTraversalMask |= SceneUtil::Mask_Actor; if (Settings::Manager::getBool("player shadows", "Shadows")) - shadowCastingTraversalMask |= Mask_Player; + shadowCastingTraversalMask |= SceneUtil::Mask_Player; if (Settings::Manager::getBool("terrain shadows", "Shadows")) - shadowCastingTraversalMask |= Mask_Terrain; + shadowCastingTraversalMask |= SceneUtil::Mask_Terrain; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; if (Settings::Manager::getBool("object shadows", "Shadows")) - shadowCastingTraversalMask |= (Mask_Object|Mask_Static); + shadowCastingTraversalMask |= (SceneUtil::Mask_Object|SceneUtil::Mask_Static); mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); @@ -259,12 +262,15 @@ namespace MWRender globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0"; globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; + globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; + globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); + mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); @@ -311,11 +317,10 @@ namespace MWRender float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); mTerrain.reset(new Terrain::QuadTreeWorld( - sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, - compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); } else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); @@ -325,7 +330,7 @@ namespace MWRender mViewer->setLightingMode(osgViewer::View::NO_LIGHT); osg::ref_ptr source = new osg::LightSource; - source->setNodeMask(Mask_Lighting); + source->setNodeMask(SceneUtil::Mask_Lighting); mSunLight = new osg::Light; source->setLight(mSunLight); mSunLight->setDiffuse(osg::Vec4f(0,0,0,1)); @@ -344,7 +349,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); - sceneRoot->setNodeMask(Mask_Scene); + sceneRoot->setNodeMask(SceneUtil::Mask_Scene); sceneRoot->setName("Scene Root"); mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); @@ -372,7 +377,7 @@ namespace MWRender mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); - mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); + mViewer->getCamera()->setCullMask(~(SceneUtil::Mask_UpdateVisitor|SceneUtil::Mask_SimpleWater)); mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); @@ -574,12 +579,12 @@ namespace MWRender else if (mode == Render_Scene) { int mask = mViewer->getCamera()->getCullMask(); - bool enabled = mask&Mask_Scene; + bool enabled = mask & SceneUtil::Mask_Scene; enabled = !enabled; if (enabled) - mask |= Mask_Scene; + mask |= SceneUtil::Mask_Scene; else - mask &= ~Mask_Scene; + mask &= ~SceneUtil::Mask_Scene; mViewer->getCamera()->setCullMask(mask); return enabled; } @@ -591,6 +596,10 @@ namespace MWRender { return mActorsPaths->toggle(); } + else if (mode == Render_RecastMesh) + { + return mRecastMesh->toggle(); + } return false; } @@ -657,6 +666,7 @@ namespace MWRender } updateNavMesh(); + updateRecastMesh(); mCamera->update(dt, paused); @@ -847,7 +857,7 @@ namespace MWRender int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); if (mCamera->isFirstPerson()) - mPlayerAnimation->getObjectRoot()->setNodeMask(0); + mPlayerAnimation->getObjectRoot()->setNodeMask(SceneUtil::Mask_Disabled); for (int i = 0; i < 6; ++i) // for each cubemap side { @@ -921,7 +931,7 @@ namespace MWRender void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) { - camera->setNodeMask(Mask_RenderToTexture); + camera->setNodeMask(SceneUtil::Mask_RenderToTexture); camera->attach(osg::Camera::COLOR_BUFFER, image); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); @@ -974,7 +984,7 @@ namespace MWRender rttCamera->addChild(mWater->getReflectionCamera()); rttCamera->addChild(mWater->getRefractionCamera()); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~SceneUtil::Mask_GUI)); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -987,7 +997,7 @@ namespace MWRender return osg::Vec4f(); osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(~(Mask_ParticleSystem|Mask_Effect)); + computeBoundsVisitor.setTraversalMask(~(SceneUtil::Mask_ParticleSystem|SceneUtil::Mask_Effect)); ptr.getRefData().getBaseNode()->accept(computeBoundsVisitor); osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); @@ -1064,12 +1074,11 @@ namespace MWRender mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); mIntersectionVisitor->setIntersector(intersector); - int mask = ~0; - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); + int mask = ~(SceneUtil::Mask_RenderToTexture|SceneUtil::Mask_Sky|SceneUtil::Mask_Pathgrid|SceneUtil::Mask_Debug|SceneUtil::Mask_Effect|SceneUtil::Mask_Water|SceneUtil::Mask_SimpleWater); if (ignorePlayer) - mask &= ~(Mask_Player); + mask &= ~(SceneUtil::Mask_Player); if (ignoreActors) - mask &= ~(Mask_Actor|Mask_Player); + mask &= ~(SceneUtil::Mask_Actor|SceneUtil::Mask_Player); mIntersectionVisitor->setTraversalMask(mask); return mIntersectionVisitor; @@ -1137,6 +1146,7 @@ namespace MWRender void RenderingManager::notifyWorldSpaceChanged() { mEffectManager->clear(); + mWater->clearRipples(); } void RenderingManager::clear() @@ -1167,7 +1177,7 @@ namespace MWRender if (!mPlayerNode) { mPlayerNode = new SceneUtil::PositionAttitudeTransform; - mPlayerNode->setNodeMask(Mask_Player); + mPlayerNode->setNodeMask(SceneUtil::Mask_Player); mPlayerNode->setName("Player Root"); mSceneRoot->addChild(mPlayerNode); } @@ -1416,7 +1426,7 @@ namespace MWRender osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(modelName); osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + computeBoundsVisitor.setTraversalMask(~(SceneUtil::Mask_ParticleSystem|SceneUtil::Mask_Effect)); const_cast(node.get())->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); @@ -1502,4 +1512,12 @@ namespace MWRender } } } + + void RenderingManager::updateRecastMesh() + { + if (!mRecastMesh->isEnabled()) + return; + + mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d782eafc1..a14c46414 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -82,6 +82,7 @@ namespace MWRender class LandManager; class NavMesh; class ActorsPaths; + class RecastMesh; // Result data of ray cast methods. // Needs to be declared outside the RenderingManager class to be forward declarable @@ -256,6 +257,8 @@ namespace MWRender void updateNavMesh(); + void updateRecastMesh(); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; @@ -274,6 +277,7 @@ namespace MWRender std::unique_ptr mNavMesh; std::size_t mNavMeshNumber = 0; std::unique_ptr mActorsPaths; + std::unique_ptr mRecastMesh; std::unique_ptr mPathgrid; std::unique_ptr mObjects; std::unique_ptr mWater; diff --git a/apps/openmw/mwrender/rendermode.hpp b/apps/openmw/mwrender/rendermode.hpp index 077710f4f..3ba2f9596 100644 --- a/apps/openmw/mwrender/rendermode.hpp +++ b/apps/openmw/mwrender/rendermode.hpp @@ -13,6 +13,7 @@ namespace MWRender Render_Scene, Render_NavMesh, Render_ActorsPaths, + Render_RecastMesh, }; } diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index f7feb267a..6597dde24 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -16,8 +16,7 @@ #include #include #include - -#include "vismask.hpp" +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -104,7 +103,7 @@ RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* mParticleNode->setName("Ripple Root"); mParticleNode->addChild(updater); mParticleNode->addChild(mParticleSystem); - mParticleNode->setNodeMask(Mask_Water); + mParticleNode->setNodeMask(SceneUtil::Mask_Water); createWaterRippleStateSet(resourceSystem, mParticleNode); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 3996f472c..592cd75bf 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -42,12 +43,12 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "vismask.hpp" #include "renderbin.hpp" namespace @@ -453,7 +454,7 @@ public: void setVisible(bool visible) { - mTransform->setNodeMask(visible ? mVisibleMask : 0); + mTransform->setNodeMask(visible ? mVisibleMask : SceneUtil::Mask_Disabled); } protected: @@ -469,7 +470,7 @@ class Sun : public CelestialBody { public: Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) - : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) + : CelestialBody(parentNode, 1.0f, 1, SceneUtil::Mask_Sun) , mUpdater(new Updater) { mTransform->addUpdateCallback(mUpdater); @@ -558,19 +559,25 @@ private: osg::ref_ptr oqn = new osg::OcclusionQueryNode; oqn->setQueriesEnabled(true); +#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) + // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced + osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); +#else + osg::ref_ptr queryGeom = oqn->getQueryGeometry(); +#endif + // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry // is only called once. // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. - oqn->getQueryGeometry()->setDataVariance(osg::Object::STATIC); + queryGeom->setDataVariance(osg::Object::STATIC); // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to // circumvent this. - osg::Geometry* queryGeom = oqn->getQueryGeometry(); queryGeom->setVertexArray(mGeom->getVertexArray()); queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); - queryGeom->removePrimitiveSet(0, oqn->getQueryGeometry()->getNumPrimitiveSets()); + queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. @@ -578,6 +585,10 @@ private: // Still need a proper bounding sphere. oqn->setInitialBound(queryGeom->getBound()); +#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) + oqn->setQueryGeometry(queryGeom.release()); +#endif + osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { @@ -644,7 +655,7 @@ private: camera->setProjectionMatrix(osg::Matrix::identity()); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? camera->setViewMatrix(osg::Matrix::identity()); - camera->setClearMask(0); + camera->setClearMask(SceneUtil::Mask_Disabled); camera->setRenderOrder(osg::Camera::NESTED_RENDER); camera->setAllowEventFocus(false); @@ -1134,7 +1145,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); - skyroot->setNodeMask(Mask_Sky); + skyroot->setNodeMask(SceneUtil::Mask_Sky); parentNode->addChild(skyroot); mRootNode = skyroot; @@ -1166,7 +1177,7 @@ void SkyManager::create() mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); mAtmosphereNightNode = new osg::PositionAttitudeTransform; - mAtmosphereNightNode->setNodeMask(0); + mAtmosphereNightNode->setNodeMask(SceneUtil::Mask_Disabled); mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); osg::ref_ptr atmosphereNight; @@ -1199,7 +1210,7 @@ void SkyManager::create() mCloudUpdater2 = new CloudUpdater; mCloudUpdater2->setOpacity(0.f); mCloudMesh2->addUpdateCallback(mCloudUpdater2); - mCloudMesh2->setNodeMask(0); + mCloudMesh2->setNodeMask(SceneUtil::Mask_Disabled); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(false); @@ -1522,7 +1533,7 @@ void SkyManager::createRain() mRainFader = new RainFader(&mWeatherAlpha); mRainNode->addUpdateCallback(mRainFader); mRainNode->addCullCallback(mUnderwaterSwitch); - mRainNode->setNodeMask(Mask_WeatherParticles); + mRainNode->setNodeMask(SceneUtil::Mask_WeatherParticles); mRootNode->addChild(mRainNode); } @@ -1625,7 +1636,7 @@ void SkyManager::setEnabled(bool enabled) if (enabled && !mCreated) create(); - mRootNode->setNodeMask(enabled ? Mask_Sky : 0); + mRootNode->setNodeMask(enabled ? SceneUtil::Mask_Sky : SceneUtil::Mask_Disabled); mEnabled = enabled; } @@ -1718,7 +1729,7 @@ void SkyManager::setWeather(const WeatherResult& weather) { mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addCullCallback(mUnderwaterSwitch); - mParticleNode->setNodeMask(Mask_WeatherParticles); + mParticleNode->setNodeMask(SceneUtil::Mask_WeatherParticles); mRootNode->addChild(mParticleNode); } @@ -1788,7 +1799,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); mCloudUpdater2->setOpacity(mCloudBlendFactor); - mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0 : 0); + mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? SceneUtil::Mask_Default : SceneUtil::Mask_Disabled); } if (mCloudColour != weather.mFogColor) @@ -1833,7 +1844,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mAtmosphereNightUpdater->setFade(mStarsOpacity); } - mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); + mAtmosphereNightNode->setNodeMask(weather.mNight ? SceneUtil::Mask_Default : SceneUtil::Mask_Disabled); if (mRainFader) mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e46f2b974..fe449bc39 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -27,6 +27,7 @@ #include #include +#include #include @@ -40,7 +41,6 @@ #include "../mwworld/cellstore.hpp" -#include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" #include "util.hpp" @@ -243,8 +243,8 @@ public: setName("RefractionCamera"); setCullCallback(new InheritViewPointCallback); - setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); - setNodeMask(Mask_RenderToTexture); + setCullMask(SceneUtil::Mask_Effect|SceneUtil::Mask_Scene|SceneUtil::Mask_Object|SceneUtil::Mask_Static|SceneUtil::Mask_Terrain|SceneUtil::Mask_Actor|SceneUtil::Mask_ParticleSystem|SceneUtil::Mask_Sky|SceneUtil::Mask_Sun|SceneUtil::Mask_Player|SceneUtil::Mask_Lighting); + setNodeMask(SceneUtil::Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); // No need for Update traversal since the scene is already updated as part of the main scene graph @@ -337,7 +337,7 @@ public: setCullCallback(new InheritViewPointCallback); setInterior(isInterior); - setNodeMask(Mask_RenderToTexture); + setNodeMask(SceneUtil::Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); setViewport(0, 0, rttSize, rttSize); @@ -372,11 +372,11 @@ public: int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); unsigned int extraMask = 0; - if(reflectionDetail >= 1) extraMask |= Mask_Terrain; - if(reflectionDetail >= 2) extraMask |= Mask_Static; - if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; - if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; - setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); + if(reflectionDetail >= 1) extraMask |= SceneUtil::Mask_Terrain; + if(reflectionDetail >= 2) extraMask |= SceneUtil::Mask_Static; + if(reflectionDetail >= 3) extraMask |= SceneUtil::Mask_Effect|SceneUtil::Mask_ParticleSystem|SceneUtil::Mask_Object; + if(reflectionDetail >= 4) extraMask |= SceneUtil::Mask_Player|SceneUtil::Mask_Actor; + setCullMask(SceneUtil::Mask_Scene|SceneUtil::Mask_Sky|SceneUtil::Mask_Lighting|extraMask); } void setWaterLevel(float waterLevel) @@ -442,7 +442,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); - mWaterGeom->setNodeMask(Mask_Water); + mWaterGeom->setNodeMask(SceneUtil::Mask_Water); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); @@ -452,7 +452,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem // simple water fallback for the local map osg::ref_ptr geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); - geom2->setNodeMask(Mask_SimpleWater); + geom2->setNodeMask(SceneUtil::Mask_SimpleWater); mWaterNode->addChild(geom2); mSceneRoot->addChild(mWaterNode); @@ -707,11 +707,11 @@ void Water::update(float dt) void Water::updateVisible() { bool visible = mEnabled && mToggled; - mWaterNode->setNodeMask(visible ? ~0 : 0); + mWaterNode->setNodeMask(visible ? SceneUtil::Mask_Default : SceneUtil::Mask_Disabled); if (mRefraction) - mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0); + mRefraction->setNodeMask(visible ? SceneUtil::Mask_RenderToTexture : SceneUtil::Mask_Disabled); if (mReflection) - mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0); + mReflection->setNodeMask(visible ? SceneUtil::Mask_RenderToTexture : SceneUtil::Mask_Disabled); } bool Water::toggle() diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 6795a058f..5a333a5b7 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -463,5 +463,6 @@ op 0x200030c: RepairedOnMe op 0x200030d: RepairedOnMe, explicit op 0x200030e: TestCells op 0x200030f: TestInteriorCells +op 0x2000310: ToggleRecastMesh -opcodes 0x2000310-0x3ffffff unused +opcodes 0x2000311-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index ccf285844..b7b91cf5c 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -923,31 +923,27 @@ namespace MWScript if (!ptr.isEmpty()) { const std::string& script = ptr.getClass().getScript(ptr); - if (script.empty()) - { - output << ptr.getCellRef().getRefId() << " has no script " << std::endl; - } - else + if (!script.empty()) { const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); char type = locals.getType(var); + std::string refId = ptr.getCellRef().getRefId(); + if (refId.find(' ') != std::string::npos) + refId = '"' + refId + '"'; switch (type) { case 'l': case 's': - output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getIntVar(script, var); + output << refId << "." << var << " = " << ptr.getRefData().getLocals().getIntVar(script, var); break; case 'f': - output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getFloatVar(script, var); - break; - default: - output << "unknown local '" << var << "' for '" << ptr.getCellRef().getRefId() << "'"; + output << refId << "." << var << " = " << ptr.getRefData().getLocals().getFloatVar(script, var); break; } } } - else + if (output.rdbuf()->in_avail() == 0) { MWBase::World *world = MWBase::Environment::get().getWorld(); char type = world->getGlobalVariableType (var); @@ -955,16 +951,16 @@ namespace MWScript switch (type) { case 's': - output << runtime.getContext().getGlobalShort (var); + output << var << " = " << runtime.getContext().getGlobalShort (var); break; case 'l': - output << runtime.getContext().getGlobalLong (var); + output << var << " = " << runtime.getContext().getGlobalLong (var); break; case 'f': - output << runtime.getContext().getGlobalFloat (var); + output << var << " = " << runtime.getContext().getGlobalFloat (var); break; default: - output << "unknown global variable"; + output << "unknown variable"; } } runtime.getContext().report(output.str()); @@ -1258,6 +1254,7 @@ namespace MWScript msg << "[Deleted]" << std::endl; msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; + msg << "Memory address: " << ptr.getBase() << std::endl; if (ptr.isInCell()) { @@ -1443,6 +1440,20 @@ namespace MWScript } }; + class OpToggleRecastMesh : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + bool enabled = + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_RecastMesh); + + runtime.getContext().report (enabled ? + "Recast Mesh Rendering -> On" : "Recast Mesh Rendering -> Off"); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1548,6 +1559,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender); interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe); + interpreter.installSegment5 (Compiler::Misc::opcodeToggleRecastMesh, new OpToggleRecastMesh); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index af3ab4af5..45eafa960 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -291,8 +291,15 @@ namespace MWScript MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); - // for fatigue, a negative current value is allowed and means the actor will be knocked down - bool allowDecreaseBelowZero = (mIndex == 2); + bool allowDecreaseBelowZero = false; + if (mIndex == 2) // Fatigue-specific logic + { + // For fatigue, a negative current value is allowed and means the actor will be knocked down + allowDecreaseBelowZero = true; + // Knock down the actor immediately if a non-positive new value is the case + if (diff + current <= 0.f) + ptr.getClass().getCreatureStats(ptr).setKnockedDown(true); + } stat.setCurrent (diff + current, allowDecreaseBelowZero); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 4872625c1..9529de855 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -269,7 +269,7 @@ namespace MWWorld /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template - bool forEach (Visitor& visitor) + bool forEach (Visitor&& visitor) { if (mState != State_Loaded) return false; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 5dd380be0..6337ca440 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -290,7 +290,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr item.getCellRef().setOwner(""); item.getCellRef().resetGlobalVariable(); item.getCellRef().setFaction(""); - item.getCellRef().setFactionRank(-1); + item.getCellRef().setFactionRank(-2); // must reset the RefNum on the copied item, so that the RefNum on the original item stays unique // maybe we should do this in the copy constructor instead? diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 49456ab24..562ba6976 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include "../mwworld/manualref.hpp" @@ -35,7 +36,6 @@ #include "../mwmechanics/weapontype.hpp" #include "../mwrender/animation.hpp" -#include "../mwrender/vismask.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/util.hpp" @@ -193,7 +193,7 @@ namespace MWWorld bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; - state.mNode->setNodeMask(MWRender::Mask_Effect); + state.mNode->setNodeMask(SceneUtil::Mask_Effect); state.mNode->setPosition(pos); state.mNode->setAttitude(orient); @@ -233,7 +233,7 @@ namespace MWWorld projectileLight->setPosition(osg::Vec4(pos, 1.0)); SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource; - projectileLightSource->setNodeMask(MWRender::Mask_Lighting); + projectileLightSource->setNodeMask(SceneUtil::Mask_Lighting); projectileLightSource->setRadius(66.f); state.mNode->addChild(projectileLightSource); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 4de04251b..a366f9a75 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -255,7 +255,7 @@ namespace } } - struct AdjustPositionVisitor + struct PositionVisitor { bool operator() (const MWWorld::Ptr& ptr) { @@ -330,7 +330,7 @@ namespace MWWorld const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - (*iter)->forEach(visitor); + (*iter)->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -877,8 +877,8 @@ namespace MWWorld insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - AdjustPositionVisitor adjustPosVisitor; - cell.forEach (adjustPosVisitor); + PositionVisitor posVisitor; + cell.forEach (posVisitor); } void Scene::addObjectToScene (const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1e42edf34..9c7c0e76b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -47,7 +48,6 @@ #include "../mwrender/npcanimation.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" -#include "../mwrender/vismask.hpp" #include "../mwscript/globalscripts.hpp" @@ -1444,7 +1444,9 @@ namespace MWWorld pos.z() += 20; // place slightly above. will snap down to ground with code below - if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !isSwimming(ptr) && isActorCollisionEnabled(ptr))) + // We still should trace down dead persistent actors - they do not use the "swimdeath" animation. + bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); + if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); if (traced.z() < pos.z()) @@ -1660,6 +1662,11 @@ namespace MWWorld return result.mHit; } + bool World::castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) + { + return mPhysics->castRay(from, to, ignore, std::vector(), mask).mHit; + } + bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration) { const ESM::Position& objPos = door.getRefData().getPosition(); @@ -2285,7 +2292,7 @@ namespace MWWorld { // Adjust position so the location we wanted ends up in the middle of the object bounding box osg::ComputeBoundsVisitor computeBounds; - computeBounds.setTraversalMask(~MWRender::Mask_ParticleSystem); + computeBounds.setTraversalMask(~SceneUtil::Mask_ParticleSystem); dropped.getRefData().getBaseNode()->accept(computeBounds); osg::BoundingBox bounds = computeBounds.getBoundingBox(); if (bounds.valid()) @@ -2765,28 +2772,15 @@ namespace MWWorld } } - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (Ptr ptr) - { - if (ptr.getRefData().getBaseNode()) - mObjects.push_back(ptr); - return true; - } - }; - void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { - ListObjectsVisitor visitor; - cellstore->forEach(visitor); - - for (const Ptr &object : visitor.mObjects) - if (Misc::StringUtils::ciEqual(object.getCellRef().getOwner(), npc.getCellRef().getRefId())) - out.push_back(object); + cellstore->forEach([&] (const auto& ptr) { + if (ptr.getRefData().getBaseNode() && Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId())) + out.push_back(ptr); + return true; + }); } } @@ -4010,10 +4004,12 @@ namespace MWWorld return dropped; } + MWPhysics::PhysicsSystem* World::getPhysicsSystem(void) { return mPhysics.get(); } + int World::getActiveWeaponType(void) { if (mPlayer) @@ -4044,5 +4040,10 @@ namespace MWWorld void World::toggleWaterRTT(bool enable) { mRendering->toggleWaterRTT(enable); + } + + bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + { + return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 983376f76..68e45710c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -430,6 +430,8 @@ namespace MWWorld bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; + void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; @@ -750,6 +752,8 @@ namespace MWWorld int getActiveWeaponType(void) override; void toggleWaterRTT(bool enable) override; + + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; }; } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index f8aa8f535..df8be3781 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -73,14 +73,16 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), + Status::NavMeshNotFound); EXPECT_EQ(mPath, std::deque()); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), + Status::StartPolygonNotFound); } TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) @@ -88,7 +90,8 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), + Status::StartPolygonNotFound); } TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) @@ -108,7 +111,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), @@ -158,7 +161,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), @@ -191,7 +194,8 @@ namespace mNavigator->wait(); mPath.clear(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); + mOut = std::back_inserter(mPath); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.87826788425445556640625), @@ -242,7 +246,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.87826788425445556640625), @@ -277,7 +281,8 @@ namespace mNavigator->wait(); mPath.clear(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + mOut = std::back_inserter(mPath); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), @@ -334,7 +339,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.96328866481781005859375), @@ -390,7 +395,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.9393787384033203125), @@ -443,7 +448,7 @@ namespace mEnd.x() = 0; mEnd.z() = 300; - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, 185.33331298828125), @@ -489,7 +494,8 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut), + Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -535,7 +541,8 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut), + Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -581,7 +588,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -630,7 +637,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index a3a1816ad..e8e7820d9 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -25,12 +25,15 @@ namespace { const osg::Vec3f mAgentHalfExtents {1, 2, 3}; const TilePosition mTilePosition {0, 0}; + const std::size_t mGeneration = 0; + const std::size_t mRevision = 0; const std::vector mIndices {{0, 1, 2}}; const std::vector mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}}; const std::vector mAreaTypes {1, AreaType_ground}; const std::vector mWater {}; const std::size_t mTrianglesPerChunk {1}; - const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk}; + const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, mWater, mTrianglesPerChunk}; const std::vector mOffMeshConnections {}; unsigned char* const mData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mNavMeshData {mData, 1}; @@ -128,7 +131,8 @@ namespace const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, water, mTrianglesPerChunk}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections)); @@ -142,7 +146,8 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, water, mTrianglesPerChunk}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -162,7 +167,8 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, water, mTrianglesPerChunk}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -180,14 +186,14 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater, - mTrianglesPerChunk}; + const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, leastRecentlySetWater, mTrianglesPerChunk}; const auto leastRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1}; const std::vector mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; - const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater, - mTrianglesPerChunk}; + const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, mostRecentlySetWater, mTrianglesPerChunk}; const auto mostRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1}; @@ -212,14 +218,14 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater, - mTrianglesPerChunk}; + const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, leastRecentlyUsedWater, mTrianglesPerChunk}; const auto leastRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1}; const std::vector mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; - const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater, - mTrianglesPerChunk}; + const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, mostRecentlyUsedWater, mTrianglesPerChunk}; const auto mostRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1}; @@ -256,7 +262,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); NavMeshData tooLargeNavMeshData {tooLargeData, 2}; @@ -275,12 +281,13 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; const std::vector tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; - const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk}; + const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, tooLargeWater, mTrianglesPerChunk}; const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); NavMeshData tooLargeNavMeshData {tooLargeData, 2}; @@ -303,7 +310,8 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, + mAreaTypes, water, mTrianglesPerChunk}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -326,7 +334,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 60b754915..af6797cf0 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -30,6 +30,8 @@ namespace { Settings mSettings; TileBounds mBounds; + const std::size_t mGeneration = 0; + const std::size_t mRevision = 0; DetourNavigatorRecastMeshBuilderTest() { @@ -45,7 +47,7 @@ namespace TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty) { RecastMeshBuilder builder(mSettings, mBounds); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector()); EXPECT_EQ(recastMesh->getIndices(), std::vector()); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector()); @@ -59,7 +61,7 @@ namespace RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -80,7 +82,7 @@ namespace btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, @@ -96,7 +98,7 @@ namespace btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, -0.5, 0, 0.5, @@ -114,7 +116,7 @@ namespace btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 2, 1, -1, 2, 1, @@ -161,7 +163,7 @@ namespace btTransform::getIdentity(), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -210,7 +212,7 @@ namespace btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, @@ -234,7 +236,7 @@ namespace btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 3, 12, 2, 1, 12, 10, @@ -256,7 +258,7 @@ namespace btTransform::getIdentity(), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -284,7 +286,7 @@ namespace btTransform::getIdentity(), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.2f, 0, -0.3f, -0.3f, 0, -0.2f, @@ -309,7 +311,7 @@ namespace static_cast(-osg::PI_4))), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 0, -0.70710659027099609375, -3.535533905029296875, 0, 0.707107067108154296875, -3.535533905029296875, @@ -334,7 +336,7 @@ namespace static_cast(osg::PI_4))), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -3.535533905029296875, -0.70710659027099609375, 0, -3.535533905029296875, 0.707107067108154296875, 0, @@ -359,7 +361,7 @@ namespace static_cast(osg::PI_4))), AreaType_ground ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1.41421353816986083984375, 0, 1.1920928955078125e-07, -1.41421353816986083984375, 0, -1.1920928955078125e-07, @@ -388,7 +390,7 @@ namespace btTransform::getIdentity(), AreaType_null ); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -405,7 +407,7 @@ namespace { RecastMeshBuilder builder(mSettings, mBounds); builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))); - const auto recastMesh = builder.create(); + const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getWater(), std::vector({ RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))} })); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 3044bf270..36f251246 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -266,7 +266,7 @@ namespace MOCK_CONST_METHOD0(numRecords, std::size_t ()); MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); MOCK_CONST_METHOD0(numRoots, std::size_t ()); - MOCK_CONST_METHOD1(getString, std::string (std::size_t)); + MOCK_CONST_METHOD1(getString, std::string (uint32_t)); MOCK_METHOD1(setUseSkinning, void (bool)); MOCK_CONST_METHOD0(getUseSkinning, bool ()); MOCK_CONST_METHOD0(getFilename, std::string ()); diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 3deb30f25..9c90b0bbf 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -154,6 +154,14 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) name = QLatin1String("Bloodmoon"); break; } + logTextEdit->appendHtml(tr("

Attempting to install component %1.

").arg(name)); + mWizard->addLogText(tr("Attempting to install component %1.").arg(name)); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("%1 Installation").arg(name)); + msgBox.setIcon(QMessageBox::Information); + msgBox.setText(QObject::tr("Select a valid %1 installation media.
Hint: make sure that it contains at least one .cab file.").arg(name)); + msgBox.exec(); QString path = QFileDialog::getExistingDirectory(this, tr("Select %1 installation media").arg(name), diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 96e959983..f84658bf3 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -493,7 +493,9 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) } - if (!found) { + if (!found) + { + emit textChanged(tr("Failed to find a valid archive containing %1.bsa! Retrying.").arg(name)); QReadLocker readLock(&mLock); emit requestFileDialog(component); mWait.wait(&mLock); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 45526a5af..8d644c6de 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,7 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique + actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique vismask recastmesh ) add_component_dir (nif diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index ba96ee8de..d0bebe3c1 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -422,7 +422,8 @@ void CompressedBSAFile::convertCompressedSizesToUncompressed() std::uint64_t CompressedBSAFile::generateHash(std::string stem, std::string extension) const { size_t len = stem.length(); - if (len == 0) return 0; + if (len == 0) + return 0; std::uint64_t hash = 0; unsigned int hash2 = 0; Misc::StringUtils::lowerCaseInPlace(stem); @@ -434,12 +435,19 @@ std::uint64_t CompressedBSAFile::generateHash(std::string stem, std::string exte for (const char &c : extension) hash = hash * 0x1003f + c; } - for (size_t i = 1; i < len-2 && len > 3; i++) - hash2 = hash2 * 0x1003f + stem[i]; + if (len >= 4) + { + for (size_t i = 1; i < len-2; i++) + hash2 = hash2 * 0x1003f + stem[i]; + } hash = (hash + hash2) << 32; hash2 = (stem[0] << 24) | (len << 16); - if (len >= 3) hash2 |= stem[len-2] << 8; - if (len >= 2) hash2 |= stem[len-1]; + if (len >= 2) + { + if (len >= 3) + hash2 |= stem[len-2] << 8; + hash2 |= stem[len-1]; + } if (!extension.empty()) { if (extension == ".kf") hash2 |= 0x80; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 5bec1bca1..24578b238 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -116,7 +116,7 @@ namespace Compiler void registerExtensions (Extensions& extensions) { extensions.registerInstruction ("additem", "clX", opcodeAddItem, opcodeAddItemExplicit); - extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, + extensions.registerFunction ("getitemcount", 'l', "cX", opcodeGetItemCount, opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "clX", opcodeRemoveItem, opcodeRemoveItemExplicit); @@ -327,6 +327,7 @@ namespace Compiler extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths); extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender); extensions.registerFunction ("repairedonme", 'l', "S", opcodeRepairedOnMe, opcodeRepairedOnMeExplicit); + extensions.registerInstruction ("togglerecastmesh", "", opcodeToggleRecastMesh); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 5fa8cc170..d3446e688 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -304,6 +304,7 @@ namespace Compiler const int opcodeSetNavMeshNumberToRender = 0x200030a; const int opcodeRepairedOnMe = 0x200030c; const int opcodeRepairedOnMeExplicit = 0x200030d; + const int opcodeToggleRecastMesh = 0x2000310; } namespace Sky diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index bc47224e4..9c34e24fd 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -44,6 +44,9 @@ namespace ContentSelectorView QWidget *uiWidget() const { return ui.contentGroupBox; } + + QToolButton *refreshButton() const + { return ui.refreshButton; } private: diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 5e145486b..da0f6c874 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -3,8 +3,9 @@ namespace DetourNavigator { - CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds) - : mImpl(settings, bounds) + CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, + std::size_t generation) + : mImpl(settings, bounds, generation) {} bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 528e8dabc..5efb315e8 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -10,7 +10,7 @@ namespace DetourNavigator class CachedRecastMeshManager { public: - CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds); + CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 541680997..a17eec16a 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H #include "tilebounds.hpp" +#include "status.hpp" #include @@ -26,6 +27,25 @@ namespace DetourNavigator return stream << "TileBounds {" << value.mMin << ", " << value.mMax << "}"; } + inline std::ostream& operator <<(std::ostream& stream, Status value) + { +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(name) \ + case Status::name: return stream << "DetourNavigator::Status::"#name; + switch (value) + { + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(Success) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(NavMeshNotFound) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(StartPolygonNotFound) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(EndPolygonNotFound) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(MoveAlongSurfaceFailed) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(FindPathOverPolygonsFailed) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(GetPolyHeightFailed) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(InitNavMeshQueryFailed) + } +#undef OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE + return stream << "DetourNavigator::Error::" << static_cast(value); + } + class RecastMesh; void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision); diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index c894e6681..3888c59fe 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -14,7 +14,8 @@ namespace DetourNavigator const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings) { dtNavMeshQuery navMeshQuery; - initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes); + if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) + return boost::optional(); dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index e988dedb7..0f8f2c09a 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -7,6 +7,7 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "debug.hpp" +#include "status.hpp" #include #include @@ -97,11 +98,10 @@ namespace DetourNavigator std::reference_wrapper mSettings; }; - inline void initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) + inline bool initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) { const auto status = value.init(&navMesh, maxNodes); - if (!dtStatusSucceed(status)) - throw NavigatorException("Failed to init navmesh query"); + return dtStatusSucceed(status); } struct MoveAlongSurfaceResult @@ -110,8 +110,8 @@ namespace DetourNavigator std::vector mVisited; }; - inline MoveAlongSurfaceResult moveAlongSurface(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, - const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& filter, + inline boost::optional moveAlongSurface(const dtNavMeshQuery& navMeshQuery, + const dtPolyRef startRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& filter, const std::size_t maxVisitedSize) { MoveAlongSurfaceResult result; @@ -120,18 +120,14 @@ namespace DetourNavigator const auto status = navMeshQuery.moveAlongSurface(startRef, startPos.ptr(), endPos.ptr(), &filter, result.mResultPos.ptr(), result.mVisited.data(), &visitedNumber, static_cast(maxVisitedSize)); if (!dtStatusSucceed(status)) - { - std::ostringstream message; - message << "Failed to move along surface from " << startPos << " to " << endPos; - throw NavigatorException(message.str()); - } + return {}; assert(visitedNumber >= 0); assert(visitedNumber <= static_cast(maxVisitedSize)); result.mVisited.resize(static_cast(visitedNumber)); - return result; + return {std::move(result)}; } - inline std::vector findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, + inline boost::optional> findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, const std::size_t maxSize) { @@ -140,34 +136,26 @@ namespace DetourNavigator const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter, result.data(), &pathLen, static_cast(maxSize)); if (!dtStatusSucceed(status)) - { - std::ostringstream message; - message << "Failed to find path over polygons from " << startRef << " to " << endRef; - throw NavigatorException(message.str()); - } + return {}; assert(pathLen >= 0); assert(static_cast(pathLen) <= maxSize); result.resize(static_cast(pathLen)); - return result; + return {std::move(result)}; } - inline float getPolyHeight(const dtNavMeshQuery& navMeshQuery, const dtPolyRef ref, const osg::Vec3f& pos) + inline boost::optional getPolyHeight(const dtNavMeshQuery& navMeshQuery, const dtPolyRef ref, const osg::Vec3f& pos) { float result = 0.0f; const auto status = navMeshQuery.getPolyHeight(ref, pos.ptr(), &result); if (!dtStatusSucceed(status)) - { - std::ostringstream message; - message << "Failed to get polygon height ref=" << ref << " pos=" << pos; - throw NavigatorException(message.str()); - } + return {}; return result; } template - OutputIterator makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, + Status makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, const float stepSize, - std::vector polygonPath, std::size_t maxSmoothPathSize, OutputIterator out) + std::vector polygonPath, std::size_t maxSmoothPathSize, OutputIterator& out) { // Iterate over the path to find smooth path on the detail mesh surface. osg::Vec3f iterPos; @@ -207,12 +195,15 @@ namespace DetourNavigator const osg::Vec3f moveTgt = iterPos + delta * len; const auto result = moveAlongSurface(navMeshQuery, polygonPath.front(), iterPos, moveTgt, filter, 16); - polygonPath = fixupCorridor(polygonPath, result.mVisited); + if (!result) + return Status::MoveAlongSurfaceFailed; + + polygonPath = fixupCorridor(polygonPath, result->mVisited); polygonPath = fixupShortcuts(polygonPath, navMeshQuery); float h = 0; - navMeshQuery.getPolyHeight(polygonPath.front(), result.mResultPos.ptr(), &h); - iterPos = result.mResultPos; + navMeshQuery.getPolyHeight(polygonPath.front(), result->mResultPos.ptr(), &h); + iterPos = result->mResultPos; iterPos.y() = h; // Handle end of path and off-mesh links when close enough. @@ -259,7 +250,12 @@ namespace DetourNavigator // Move position at the other side of the off-mesh link. iterPos = endPos; - iterPos.y() = getPolyHeight(navMeshQuery, polygonPath.front(), iterPos); + const auto height = getPolyHeight(navMeshQuery, polygonPath.front(), iterPos); + + if (!height) + return Status::GetPolyHeightFailed; + + iterPos.y() = *height; } } @@ -268,16 +264,17 @@ namespace DetourNavigator ++smoothPathSize; } - return out; + return Status::Success; } template - OutputIterator findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const float stepSize, + Status findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, - const Settings& settings, OutputIterator out) + const Settings& settings, OutputIterator& out) { dtNavMeshQuery navMeshQuery; - initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes); + if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) + return Status::InitNavMeshQueryFailed; dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); @@ -293,7 +290,7 @@ namespace DetourNavigator } if (startRef == 0) - throw NavigatorException("Navmesh polygon for start point is not found"); + return Status::StartPolygonNotFound; dtPolyRef endRef = 0; osg::Vec3f endPolygonPosition; @@ -306,18 +303,20 @@ namespace DetourNavigator } if (endRef == 0) - throw NavigatorException("Navmesh polygon for end polygon is not found"); + return Status::EndPolygonNotFound; const auto polygonPath = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, settings.mMaxPolygonPathSize); - if (polygonPath.empty() || polygonPath.back() != endRef) - return out; + if (!polygonPath) + return Status::FindPathOverPolygonsFailed; - makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, std::move(polygonPath), - settings.mMaxSmoothPathSize, OutputTransformIterator(out, settings)); + if (polygonPath->empty() || polygonPath->back() != endRef) + return Status::Success; - return out; + auto outTransform = OutputTransformIterator(out, settings); + return makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, std::move(*polygonPath), + settings.mMaxSmoothPathSize, outTransform); } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 74daab5d7..99f1e258d 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -1,4 +1,4 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #include "findsmoothpath.hpp" @@ -6,6 +6,8 @@ #include "settings.hpp" #include "objectid.hpp" #include "navmeshcacheitem.hpp" +#include "recastmesh.hpp" +#include "recastmeshtiles.hpp" namespace DetourNavigator { @@ -159,8 +161,8 @@ namespace DetourNavigator * Equal to out if no path is found. */ template - OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, - const osg::Vec3f& end, const Flags includeFlags, OutputIterator out) const + Status findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags, OutputIterator& out) const { static_assert( std::is_same< @@ -171,7 +173,7 @@ namespace DetourNavigator ); const auto navMesh = getNavMesh(agentHalfExtents); if (!navMesh) - return out; + return Status::NavMeshNotFound; const auto settings = getSettings(); return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), @@ -204,6 +206,8 @@ namespace DetourNavigator */ boost::optional findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const; + + virtual RecastMeshTiles getRecastMeshTiles() = 0; }; } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index b743f26b2..3ecfd8b51 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -143,6 +143,11 @@ namespace DetourNavigator mNavMeshManager.reportStats(frameNumber, stats); } + RecastMeshTiles NavigatorImpl::getRecastMeshTiles() + { + return mNavMeshManager.getRecastMeshTiles(); + } + void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) { updateId(id, avoidId, mWaterIds); diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index b6b3b1b7f..be291f501 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -50,6 +50,8 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; + RecastMeshTiles getRecastMeshTiles() override; + private: Settings mSettings; NavMeshManager mNavMeshManager; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 885482a45..ee23e67be 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -81,6 +81,11 @@ namespace DetourNavigator void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {} + RecastMeshTiles getRecastMeshTiles() override + { + return {}; + } + private: Settings mDefaultSettings {}; SharedNavMeshCacheItem mEmptyNavMeshCacheItem; diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 4bd797326..a769981d3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -147,7 +147,7 @@ namespace DetourNavigator const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents]; auto lastPlayerTile = mPlayerTile.find(agentHalfExtents); - if (lastRevision >= mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() + if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() && lastPlayerTile->second == playerTile) return; lastRevision = mRecastMeshManager.getRevision(); @@ -220,6 +220,17 @@ namespace DetourNavigator mAsyncNavMeshUpdater.reportStats(frameNumber, stats); } + RecastMeshTiles NavMeshManager::getRecastMeshTiles() + { + std::vector tiles; + mRecastMeshManager.forEachTilePosition( + [&tiles] (const TilePosition& tile) { tiles.push_back(tile); }); + RecastMeshTiles result; + std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()), + [this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); }); + return result; + } + void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 3ef898b05..a6bdca09b 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -5,6 +5,7 @@ #include "cachedrecastmeshmanager.hpp" #include "offmeshconnectionsmanager.hpp" #include "sharednavmesh.hpp" +#include "recastmeshtiles.hpp" #include @@ -52,6 +53,8 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + RecastMeshTiles getRecastMeshTiles(); + private: const Settings& mSettings; TileCachedRecastMeshManager mRecastMeshManager; diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 476799c1f..dc56f7b93 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -5,9 +5,11 @@ namespace DetourNavigator { - RecastMesh::RecastMesh(std::vector indices, std::vector vertices, std::vector areaTypes, - std::vector water, const std::size_t trianglesPerChunk) - : mIndices(std::move(indices)) + RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, std::vector indices, std::vector vertices, + std::vector areaTypes, std::vector water, const std::size_t trianglesPerChunk) + : mGeneration(generation) + , mRevision(revision) + , mIndices(std::move(indices)) , mVertices(std::move(vertices)) , mAreaTypes(std::move(areaTypes)) , mWater(std::move(water)) diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 47d5f7963..f3259903f 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -24,8 +24,18 @@ namespace DetourNavigator btTransform mTransform; }; - RecastMesh(std::vector indices, std::vector vertices, std::vector areaTypes, - std::vector water, const std::size_t trianglesPerChunk); + RecastMesh(std::size_t generation, std::size_t revision, std::vector indices, std::vector vertices, + std::vector areaTypes, std::vector water, const std::size_t trianglesPerChunk); + + std::size_t getGeneration() const + { + return mGeneration; + } + + std::size_t getRevision() const + { + return mRevision; + } const std::vector& getIndices() const { @@ -68,6 +78,8 @@ namespace DetourNavigator } private: + std::size_t mGeneration; + std::size_t mRevision; std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 5d8a07055..d96ba2f29 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -112,9 +112,10 @@ namespace DetourNavigator mWater.push_back(RecastMesh::Water {cellSize, transform}); } - std::shared_ptr RecastMeshBuilder::create() const + std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) const { - return std::make_shared(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); + return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, + mWater, mSettings.get().mTrianglesPerChunk); } void RecastMeshBuilder::reset() diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 070b9c67d..d28558d0f 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -34,7 +34,7 @@ namespace DetourNavigator void addWater(const int mCellSize, const btTransform& transform); - std::shared_ptr create() const; + std::shared_ptr create(std::size_t generation, std::size_t revision) const; void reset(); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 39afdf56e..05f250596 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -4,8 +4,8 @@ namespace DetourNavigator { - RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds) - : mShouldRebuild(false) + RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) + : mGeneration(generation) , mMeshBuilder(settings, bounds) { } @@ -19,8 +19,8 @@ namespace DetourNavigator mObjectsOrder.erase(iterator); return false; } - mShouldRebuild = true; - return mShouldRebuild; + ++mRevision; + return true; } bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) @@ -30,8 +30,8 @@ namespace DetourNavigator return false; if (!object->second->update(transform, areaType)) return false; - mShouldRebuild = true; - return mShouldRebuild; + ++mRevision; + return true; } boost::optional RecastMeshManager::removeObject(const ObjectId id) @@ -42,7 +42,7 @@ namespace DetourNavigator const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()}; mObjectsOrder.erase(object->second); mObjects.erase(object); - mShouldRebuild = true; + ++mRevision; return result; } @@ -55,7 +55,7 @@ namespace DetourNavigator mWaterOrder.erase(iterator); return false; } - mShouldRebuild = true; + ++mRevision; return true; } @@ -64,7 +64,7 @@ namespace DetourNavigator const auto water = mWater.find(cellPosition); if (water == mWater.end()) return boost::none; - mShouldRebuild = true; + ++mRevision; const auto result = *water->second; mWaterOrder.erase(water->second); mWater.erase(water); @@ -74,7 +74,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshManager::getMesh() { rebuild(); - return mMeshBuilder.create(); + return mMeshBuilder.create(mGeneration, mLastBuildRevision); } bool RecastMeshManager::isEmpty() const @@ -84,13 +84,13 @@ namespace DetourNavigator void RecastMeshManager::rebuild() { - if (!mShouldRebuild) + if (mLastBuildRevision == mRevision) return; mMeshBuilder.reset(); for (const auto& v : mWaterOrder) mMeshBuilder.addWater(v.mCellSize, v.mTransform); for (const auto& v : mObjectsOrder) mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); - mShouldRebuild = false; + mLastBuildRevision = mRevision; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index a6f86bfcf..f1870ec6b 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -34,7 +34,7 @@ namespace DetourNavigator btTransform mTransform; }; - RecastMeshManager(const Settings& settings, const TileBounds& bounds); + RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -52,7 +52,9 @@ namespace DetourNavigator bool isEmpty() const; private: - bool mShouldRebuild; + std::size_t mRevision = 0; + std::size_t mLastBuildRevision = 0; + std::size_t mGeneration; RecastMeshBuilder mMeshBuilder; std::list mObjectsOrder; std::unordered_map::iterator> mObjects; diff --git a/components/detournavigator/recastmeshtiles.hpp b/components/detournavigator/recastmeshtiles.hpp new file mode 100644 index 000000000..68e30ba63 --- /dev/null +++ b/components/detournavigator/recastmeshtiles.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H + +#include "tileposition.hpp" +#include "recastmesh.hpp" + +#include +#include + +namespace DetourNavigator +{ + using RecastMeshTiles = std::map>; +} + +#endif diff --git a/components/detournavigator/status.hpp b/components/detournavigator/status.hpp new file mode 100644 index 000000000..3715489ac --- /dev/null +++ b/components/detournavigator/status.hpp @@ -0,0 +1,43 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_STATUS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_STATUS_H + +namespace DetourNavigator +{ + enum class Status + { + Success, + NavMeshNotFound, + StartPolygonNotFound, + EndPolygonNotFound, + MoveAlongSurfaceFailed, + FindPathOverPolygonsFailed, + GetPolyHeightFailed, + InitNavMeshQueryFailed, + }; + + constexpr const char* getMessage(Status value) + { + switch (value) + { + case Status::Success: + return "success"; + case Status::NavMeshNotFound: + return "navmesh is not found"; + case Status::StartPolygonNotFound: + return "polygon for start position is not found on navmesh"; + case Status::EndPolygonNotFound: + return "polygon for end position is not found on navmesh"; + case Status::MoveAlongSurfaceFailed: + return "move along surface on navmesh is failed"; + case Status::FindPathOverPolygonsFailed: + return "path over navmesh polygons is not found"; + case Status::GetPolyHeightFailed: + return "failed to get polygon height"; + case Status::InitNavMeshQueryFailed: + return "failed to init navmesh query"; + } + return "unknown error"; + } +} + +#endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 0cb1cfb80..bbdbd410a 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -121,7 +121,7 @@ namespace DetourNavigator tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds))).first; + CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; } if (tile->second.addWater(cellPosition, cellSize, transform)) { @@ -151,7 +151,10 @@ namespace DetourNavigator continue; const auto tileResult = tile->second.removeWater(cellPosition); if (tile->second.isEmpty()) + { tiles->erase(tile); + ++mTilesGeneration; + } if (tileResult && !result) result = tileResult; } @@ -189,7 +192,8 @@ namespace DetourNavigator auto tileBounds = makeTileBounds(mSettings, tilePosition); tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; + tile = tiles.insert(std::make_pair( + tilePosition, CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; } return tile->second.addObject(id, shape, transform, areaType); } @@ -209,7 +213,10 @@ namespace DetourNavigator return boost::optional(); const auto tileResult = tile->second.removeObject(id); if (tile->second.isEmpty()) + { tiles.erase(tile); + ++mTilesGeneration; + } return tileResult; } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index a3d0ae1e5..4351c86bb 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -48,6 +48,7 @@ namespace DetourNavigator std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; + std::size_t mTilesGeneration = 0; bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index ba2be7b45..69841528f 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -153,13 +153,19 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool esm.writeHNT("XSCL", scale); } - esm.writeHNOCString("ANAM", mOwner); + if (!inInventory) + esm.writeHNOCString("ANAM", mOwner); + esm.writeHNOCString("BNAM", mGlobalVariable); esm.writeHNOCString("XSOL", mSoul); - esm.writeHNOCString("CNAM", mFaction); - if (mFactionRank != -2) { - esm.writeHNT("INDX", mFactionRank); + if (!inInventory) + { + esm.writeHNOCString("CNAM", mFaction); + if (mFactionRank != -2) + { + esm.writeHNT("INDX", mFactionRank); + } } if (mEnchantmentCharge != -1) diff --git a/components/esm/containerstate.hpp b/components/esm/containerstate.hpp index 1ecf2b46e..33818d4f1 100644 --- a/components/esm/containerstate.hpp +++ b/components/esm/containerstate.hpp @@ -14,6 +14,15 @@ namespace ESM virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual ContainerState& asContainerState() + { + return *this; + } + virtual const ContainerState& asContainerState() const + { + return *this; + } }; } diff --git a/components/esm/creaturelevliststate.hpp b/components/esm/creaturelevliststate.hpp index da64cd7c2..4d0b726a0 100644 --- a/components/esm/creaturelevliststate.hpp +++ b/components/esm/creaturelevliststate.hpp @@ -14,6 +14,15 @@ namespace ESM virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual CreatureLevListState& asCreatureLevListState() + { + return *this; + } + virtual const CreatureLevListState& asCreatureLevListState() const + { + return *this; + } }; } diff --git a/components/esm/creaturestate.hpp b/components/esm/creaturestate.hpp index 9a3d41daa..ca0d2601a 100644 --- a/components/esm/creaturestate.hpp +++ b/components/esm/creaturestate.hpp @@ -19,6 +19,15 @@ namespace ESM virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual CreatureState& asCreatureState() + { + return *this; + } + virtual const CreatureState& asCreatureState() const + { + return *this; + } }; } diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index beb2c4c64..d337b1434 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -144,7 +144,8 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mGoldPool) esm.writeHNT ("GOLD", mGoldPool); - esm.writeHNT ("TIME", mTradeTime); + if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0) + esm.writeHNT ("TIME", mTradeTime); if (mDead) esm.writeHNT ("DEAD", mDead); diff --git a/components/esm/doorstate.hpp b/components/esm/doorstate.hpp index c2e765880..1251b9059 100644 --- a/components/esm/doorstate.hpp +++ b/components/esm/doorstate.hpp @@ -13,6 +13,15 @@ namespace ESM virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual DoorState& asDoorState() + { + return *this; + } + virtual const DoorState& asDoorState() const + { + return *this; + } }; } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index ec4155f59..5b259acef 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -110,6 +110,7 @@ namespace ESM void Cell::loadCell(ESMReader &esm, bool saveContext) { bool isLoaded = false; + mHasAmbi = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); @@ -127,6 +128,7 @@ namespace ESM break; case ESM::FourCC<'A','M','B','I'>::value: esm.getHT(mAmbi); + mHasAmbi = true; break; case ESM::FourCC<'R','G','N','N'>::value: mRegion = esm.getHString(); @@ -182,7 +184,12 @@ namespace ESM if (mData.mFlags & QuasiEx) esm.writeHNOCString("RGNN", mRegion); else - esm.writeHNT("AMBI", mAmbi, 16); + { + // Try to avoid saving ambient lighting information when it's unnecessary. + // This is to fix black lighting in resaved cell records that lack this information. + if (mHasAmbi) + esm.writeHNT("AMBI", mAmbi, 16); + } } else { @@ -272,6 +279,7 @@ namespace ESM mData.mX = 0; mData.mY = 0; + mHasAmbi = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index bc5016718..132c869ad 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -90,6 +90,7 @@ struct Cell Cell() : mName(""), mRegion(""), + mHasAmbi(true), mWater(0), mWaterInt(false), mMapColor(0), @@ -108,6 +109,7 @@ struct Cell CellId mCellId; AMBIstruct mAmbi; + bool mHasAmbi; float mWater; // Water level bool mWaterInt; @@ -152,6 +154,16 @@ struct Cell return ((mData.mFlags&HasWater) != 0) || isExterior(); } + bool hasAmbient() const + { + return mHasAmbi; + } + + void setHasAmbient(bool hasAmbi) + { + mHasAmbi = hasAmbi; + } + // Restore the given reader to the stored position. Will try to open // the file matching the stored file name. If you want to read from // somewhere other than the file system, you need to pre-open the diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index c52483681..0d9e68eb4 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -158,8 +158,24 @@ namespace ESM } } - if (mDataTypes & Land::DATA_WNAM) { - esm.writeHNT("WNAM", mWnam, 81); + if (mDataTypes & Land::DATA_WNAM) + { + // Generate WNAM record + signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE]; + float max = std::numeric_limits::max(); + float min = std::numeric_limits::min(); + float vertMult = static_cast(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; + for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) + { + for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) + { + float height = mLandData->mHeights[int(row * vertMult) * ESM::Land::LAND_SIZE + int(col * vertMult)]; + height /= height > 0 ? 128.f : 16.f; + height = std::min(max, std::max(min, height)); + wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast(height); + } + } + esm.writeHNT("WNAM", wnam, 81); } if (mLandData) diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index b4b66c601..e5faf4b31 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -70,6 +70,8 @@ struct Land static const int LAND_GLOBAL_MAP_LOD_SIZE = 81; + static const int LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; + #pragma pack(push,1) struct VHGT { diff --git a/components/esm/npcstate.hpp b/components/esm/npcstate.hpp index b90cd85e6..4ae026da6 100644 --- a/components/esm/npcstate.hpp +++ b/components/esm/npcstate.hpp @@ -21,6 +21,15 @@ namespace ESM virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual NpcState& asNpcState() + { + return *this; + } + virtual const NpcState& asNpcState() const + { + return *this; + } }; } diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 18c030256..a7a452c82 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -1,5 +1,9 @@ #include "objectstate.hpp" +#include +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -84,4 +88,74 @@ void ESM::ObjectState::blank() mHasCustomState = true; } +const ESM::NpcState& ESM::ObjectState::asNpcState() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcState"; + throw std::logic_error(error.str()); +} + +ESM::NpcState& ESM::ObjectState::asNpcState() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcState"; + throw std::logic_error(error.str()); +} + +const ESM::CreatureState& ESM::ObjectState::asCreatureState() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureState"; + throw std::logic_error(error.str()); +} + +ESM::CreatureState& ESM::ObjectState::asCreatureState() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureState"; + throw std::logic_error(error.str()); +} + +const ESM::ContainerState& ESM::ObjectState::asContainerState() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerState"; + throw std::logic_error(error.str()); +} + +ESM::ContainerState& ESM::ObjectState::asContainerState() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerState"; + throw std::logic_error(error.str()); +} + +const ESM::DoorState& ESM::ObjectState::asDoorState() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorState"; + throw std::logic_error(error.str()); +} + +ESM::DoorState& ESM::ObjectState::asDoorState() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorState"; + throw std::logic_error(error.str()); +} + +const ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; + throw std::logic_error(error.str()); +} + +ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; + throw std::logic_error(error.str()); +} + ESM::ObjectState::~ObjectState() {} diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index d14c04b64..1079d6c72 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -12,6 +12,11 @@ namespace ESM { class ESMReader; class ESMWriter; + struct ContainerState; + struct CreatureLevListState; + struct CreatureState; + struct DoorState; + struct NpcState; // format 0, saved games only @@ -48,6 +53,21 @@ namespace ESM void blank(); virtual ~ObjectState(); + + virtual const NpcState& asNpcState() const; + virtual NpcState& asNpcState(); + + virtual const CreatureState& asCreatureState() const; + virtual CreatureState& asCreatureState(); + + virtual const ContainerState& asContainerState() const; + virtual ContainerState& asContainerState(); + + virtual const DoorState& asDoorState() const; + virtual DoorState& asDoorState(); + + virtual const CreatureLevListState& asCreatureLevListState() const; + virtual CreatureLevListState& asCreatureLevListState(); }; } diff --git a/components/myguiplatform/additivelayer.hpp b/components/myguiplatform/additivelayer.hpp index f3d47bc82..f0cfc1b41 100644 --- a/components/myguiplatform/additivelayer.hpp +++ b/components/myguiplatform/additivelayer.hpp @@ -14,7 +14,7 @@ namespace osgMyGUI { /// @brief A Layer rendering with additive blend mode. - class AdditiveLayer : public MyGUI::OverlappedLayer + class AdditiveLayer final : public MyGUI::OverlappedLayer { public: MYGUI_RTTI_DERIVED( AdditiveLayer ) @@ -22,7 +22,7 @@ namespace osgMyGUI AdditiveLayer(); ~AdditiveLayer(); - virtual void renderToTarget(MyGUI::IRenderTarget* _target, bool _update); + void renderToTarget(MyGUI::IRenderTarget* _target, bool _update) final; private: osg::ref_ptr mStateSet; diff --git a/components/myguiplatform/scalinglayer.hpp b/components/myguiplatform/scalinglayer.hpp index 3ee1489f1..7ce5f84f7 100644 --- a/components/myguiplatform/scalinglayer.hpp +++ b/components/myguiplatform/scalinglayer.hpp @@ -8,18 +8,18 @@ namespace osgMyGUI ///@brief A Layer that lays out and renders widgets in screen-relative coordinates. The "Size" property determines the size of the virtual screen, /// which is then upscaled to the real screen size during rendering. The aspect ratio is kept intact, adding blanks to the sides when necessary. - class ScalingLayer : public MyGUI::OverlappedLayer + class ScalingLayer final : public MyGUI::OverlappedLayer { public: MYGUI_RTTI_DERIVED(ScalingLayer) - virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version); + void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) final; - virtual MyGUI::ILayerItem* getLayerItemByPoint(int _left, int _top) const; - virtual MyGUI::IntPoint getPosition(int _left, int _top) const; - virtual void renderToTarget(MyGUI::IRenderTarget* _target, bool _update); + MyGUI::ILayerItem* getLayerItemByPoint(int _left, int _top) const final; + MyGUI::IntPoint getPosition(int _left, int _top) const final; + void renderToTarget(MyGUI::IRenderTarget* _target, bool _update) final; - virtual void resizeView(const MyGUI::IntSize& _viewSize); + void resizeView(const MyGUI::IntSize& _viewSize) final; private: void screenToLayerCoords(int& _left, int& _top) const; diff --git a/components/nif/base.hpp b/components/nif/base.hpp index f67de0221..6e26f525e 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -59,7 +59,7 @@ public: controller.post(nif); } }; -typedef Named NiSequenceStreamHelper; +using NiSequenceStreamHelper = Named; } // Namespace #endif diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index 9b7c9319b..0b5c32a10 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -10,17 +10,18 @@ namespace Nif Named::read(nif); external = nif->getChar() != 0; - if(external) + bool internal = false; + if (external) filename = nif->getString(); else - { - nif->getChar(); // always 1 - data.read(nif); - } + internal = nif->getChar(); - pixel = nif->getInt(); - mipmap = nif->getInt(); - alpha = nif->getInt(); + if (!external && internal) + data.read(nif); + + pixel = nif->getUInt(); + mipmap = nif->getUInt(); + alpha = nif->getUInt(); nif->getChar(); // always 1 } @@ -113,8 +114,4 @@ namespace Nif mCenter = nif->getVector3(); } - - - - } diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 00ff45eda..8396eae04 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -46,13 +46,13 @@ public: 3 - Compressed 4 - Bumpmap 5 - Default */ - int pixel; + unsigned int pixel; /* Mipmap format 0 - no 1 - yes 2 - default */ - int mipmap; + unsigned int mipmap; /* Alpha 0 - none @@ -60,7 +60,7 @@ public: 2 - smooth 3 - default (use material alpha, or multiply material with texture if present) */ - int alpha; + unsigned int alpha; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 6de720b52..9a4c1b065 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -92,6 +92,12 @@ namespace Nif void NiMaterialColorController::read(NIFStream *nif) { Controller::read(nif); + // Two bits that correspond to the controlled material color. + // 00: Ambient + // 01: Diffuse + // 10: Specular + // 11: Emissive + targetColor = (flags >> 4) & 3; data.read(nif); } @@ -189,7 +195,8 @@ namespace Nif { Controller::read(nif); data.read(nif); - nif->getChar(); // always 0 + if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) + /*bool alwaysActive = */nif->getChar(); // Always 0 } void NiGeomMorpherController::post(NIFFile *nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 2fe28fe1d..364eff1cd 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -78,12 +78,13 @@ public: void read(NIFStream *nif); void post(NIFFile *nif); }; -typedef NiParticleSystemController NiBSPArrayController; +using NiBSPArrayController = NiParticleSystemController; class NiMaterialColorController : public Controller { public: NiPosDataPtr data; + unsigned int targetColor; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 828f5c368..e46c0e84d 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -225,7 +225,8 @@ void NiSkinData::read(NIFStream *nif) trafo.scale = nif->getFloat(); int boneNum = nif->getInt(); - nif->getInt(); // -1 + if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFFile::NIFVersion::VER_GAMEBRYO) + nif->skip(4); // NiSkinPartition link bones.resize(boneNum); for (BoneInfo &bi : bones) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index bced4d8e2..2070ee850 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -48,6 +48,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiSwitchNode", &construct , RC_NiSwitchNode )); newFactory.insert(makeEntry("NiLODNode", &construct , RC_NiLODNode )); newFactory.insert(makeEntry("AvoidNode", &construct , RC_AvoidNode )); + newFactory.insert(makeEntry("NiCollisionSwitch", &construct , RC_NiCollisionSwitch )); newFactory.insert(makeEntry("NiBSParticleNode", &construct , RC_NiBSParticleNode )); newFactory.insert(makeEntry("NiBSAnimationNode", &construct , RC_NiBSAnimationNode )); newFactory.insert(makeEntry("NiBillboardNode", &construct , RC_NiBillboardNode )); @@ -143,7 +144,7 @@ void NIFFile::parse(Files::IStreamPtr stream) ver = nif.getUInt(); // 4.0.0.0 is an older, practically identical version of the format. // It's not used by Morrowind assets but Morrowind supports it. - if(ver != VER_4_0_0_0 && ver != VER_MW) + if(ver != nif.generateVersion(4,0,0,0) && ver != VER_MW) fail("Unsupported NIF version: " + printVersion(ver)); // Number of records size_t recNum = nif.getInt(); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index a85e46ea5..4d5620a37 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -26,7 +26,7 @@ struct File virtual size_t numRoots() const = 0; - virtual std::string getString(size_t index) const = 0; + virtual std::string getString(uint32_t index) const = 0; virtual void setUseSkinning(bool skinning) = 0; @@ -75,26 +75,16 @@ class NIFFile final : public File void operator = (NIFFile const &); public: + // For generic versions NIFStream::generateVersion() is used instead enum NIFVersion { - // Feature-relevant - VER_4_1_0_0 = 0x04010000, // 1-byte booleans (previously 4-byte) - VER_5_0_0_1 = 0x05000001, // Optimized record type listings - VER_5_0_0_6 = 0x05000006, // Record groups - VER_10_0_1_8 = 0x0A000108, // The last version without user version - VER_20_1_0_1 = 0x14010001, // String tables - VER_20_2_0_5 = 0x14020005, // Record sizes - // Game-relevant - VER_4_0_0_0 = 0x04000000, // Freedom Force NIFs, supported by Morrowind - VER_MW = 0x04000002, // 4.0.0.2. Morrowind and Freedom Force NIFs - VER_4_2_1_0 = 0x04020100, // Used in Civ4 and Dark Age of Camelot - VER_CI = 0x04020200, // 4.2.2.0. Main Culpa Innata NIF version, also used in Civ4 - VER_ZT2 = 0x0A000100, // 10.0.1.0. Main Zoo Tycoon 2 NIF version, also used in Oblivion and Civ4 - VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version + VER_MW = 0x04000002, // 4.0.0.2. Main Morrowind NIF version. + VER_CI = 0x04020200, // 4.2.2.0. Main Culpa Innata NIF version, also used in Civ4. + VER_ZT2 = 0x0A000100, // 10.0.1.0. Main Zoo Tycoon 2 NIF version, also used in Oblivion and Civ4. + VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version. VER_GAMEBRYO = 0x0A010000, // 10.1.0.0. Lots of games use it. The first version that has Gamebryo File Format header. - VER_10_2_0_0 = 0x0A020000, // Lots of games use this version as well. VER_CIV4 = 0x14000004, // 20.0.0.4. Main Civilization IV NIF version. - VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version + VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version. VER_BGS = 0x14020007 // 20.2.0.7. Main Fallout 3/4/76/New Vegas and Skyrim/SkyrimSE NIF version. }; enum BethVersion @@ -139,8 +129,10 @@ public: size_t numRoots() const override { return roots.size(); } /// Get a given string from the file's string table - std::string getString(size_t index) const override + std::string getString(uint32_t index) const override { + if (index == std::numeric_limits::max()) + return std::string(); return strings.at(index); } diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 4ecb0e373..44be4b241 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -38,7 +38,7 @@ namespace Nif } // Convenience utility functions: get the versions of the currently read file - unsigned int NIFStream::getVersion() { return file->getVersion(); } - unsigned int NIFStream::getUserVersion() { return file->getBethVersion(); } - unsigned int NIFStream::getBethVersion() { return file->getBethVersion(); } + unsigned int NIFStream::getVersion() const { return file->getVersion(); } + unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); } + unsigned int NIFStream::getBethVersion() const { return file->getBethVersion(); } } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 7ecd66084..c78377448 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -159,9 +159,15 @@ public: std::string getString(); - unsigned int getVersion(); - unsigned int getUserVersion(); - unsigned int getBethVersion(); + unsigned int getVersion() const; + unsigned int getUserVersion() const; + unsigned int getBethVersion() const; + + // Convert human-readable version numbers into a number that can be compared. + static constexpr uint32_t generateVersion(uint8_t major, uint8_t minor, uint8_t patch, uint8_t rev) + { + return (major << 24) + (minor << 16) + (patch << 8) + rev; + } ///Read in a string of the given length std::string getSizedString(size_t length) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 71a0a93ca..4c52cd158 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -84,7 +84,8 @@ struct NiNode : Node enum Flags { Flag_Hidden = 0x0001, Flag_MeshCollision = 0x0002, - Flag_BBoxCollision = 0x0004 + Flag_BBoxCollision = 0x0004, + Flag_ActiveCollision = 0x0020 }; enum BSAnimFlags { AnimFlag_AutoPlay = 0x0020 @@ -284,7 +285,8 @@ struct NiLODNode : public NiSwitchNode void read(NIFStream *nif) { NiSwitchNode::read(nif); - lodCenter = nif->getVector3(); + if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFFile::NIFVersion::VER_ZT2) + lodCenter = nif->getVector3(); unsigned int numLodLevels = nif->getUInt(); for (unsigned int i=0; igetFloat(); - /*float lumaOffset =*/ nif->getFloat(); - /*const Vector4 *lumaMatrix =*/ nif->getVector4(); + envMapLumaBias = nif->getVector2(); + bumpMapMatrix = nif->getVector4(); } } } diff --git a/components/nif/property.hpp b/components/nif/property.hpp index a3f399b83..c72dbf6ba 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -93,6 +93,9 @@ public: std::vector textures; + osg::Vec2f envMapLumaBias; + osg::Vec4f bumpMapMatrix; + void read(NIFStream *nif); void post(NIFFile *nif); }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 67ffbc574..074dea9cf 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -40,6 +40,7 @@ enum RecordType RC_NiLODNode, RC_NiBillboardNode, RC_AvoidNode, + RC_NiCollisionSwitch, RC_NiTriShape, RC_NiTriStrips, RC_NiRotatingParticles, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 91f849421..dafee1b49 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -254,6 +254,10 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) { + // TODO: allow on-the fly collision switching via toggling this flag + if (node->recType == Nif::RC_NiCollisionSwitch && !(node->flags & Nif::NiNode::Flag_ActiveCollision)) + return; + // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node->flags; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 1842e0017..934e9b565 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "userdata.hpp" @@ -304,7 +305,7 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) { bool vis = calculate(getInputValue(nv)); // Leave 0x1 enabled for UpdateVisitor, so we can make ourselves visible again in the future from this update callback - node->setNodeMask(vis ? ~0 : 0x1); + node->setNodeMask(vis ? SceneUtil::Mask_Default : SceneUtil::Mask_UpdateVisitor); } traverse(node, nv); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 50c1c6627..ea43fc9c6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -16,6 +16,7 @@ #include #include #include +#include // particle #include @@ -62,6 +63,8 @@ namespace // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. void collectDrawableProperties(const Nif::Node* nifNode, std::vector& out) { + if (nifNode->parent) + collectDrawableProperties(nifNode->parent, out); const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i parent) - collectDrawableProperties(nifNode->parent, out); } // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale @@ -167,6 +168,19 @@ namespace namespace NifOsg { + class CollisionSwitch : public osg::MatrixTransform + { + public: + CollisionSwitch(const osg::Matrixf& transformations, bool enabled) : osg::MatrixTransform(transformations) + { + setEnabled(enabled); + } + + void setEnabled(bool enabled) + { + setNodeMask(enabled ? SceneUtil::Mask_Default : SceneUtil::Mask_Effect); + } + }; bool Loader::sShowMarkers = false; @@ -460,6 +474,14 @@ namespace NifOsg case Nif::RC_NiBillboardNode: dataVariance = osg::Object::DYNAMIC; break; + case Nif::RC_NiCollisionSwitch: + { + bool enabled = nifNode->flags & Nif::NiNode::Flag_ActiveCollision; + node = new CollisionSwitch(nifNode->trafo.toMatrix(), enabled); + dataVariance = osg::Object::STATIC; + + break; + } default: // The Root node can be created as a Group if no transformation is required. // This takes advantage of the fact root nodes can't have additional controllers @@ -553,7 +575,7 @@ namespace NifOsg { skipMeshes = true; // Leave mask for UpdateVisitor enabled - node->setNodeMask(0x1); + node->setNodeMask(SceneUtil::Mask_UpdateVisitor); } // We can skip creating meshes for hidden nodes if they don't have a VisController that @@ -568,7 +590,7 @@ namespace NifOsg skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating collision shapes // now hide this node, but leave the mask for UpdateVisitor enabled so that KeyframeController works - node->setNodeMask(0x1); + node->setNodeMask(SceneUtil::Mask_UpdateVisitor); } if ((skipMeshes || hasMarkers) && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. @@ -768,12 +790,7 @@ namespace NifOsg const Nif::NiMaterialColorController* matctrl = static_cast(ctrl.getPtr()); if (matctrl->data.empty()) continue; - // Two bits that correspond to the controlled material color. - // 00: Ambient - // 01: Diffuse - // 10: Specular - // 11: Emissive - MaterialColorController::TargetColor targetColor = static_cast((matctrl->flags >> 4) & 3); + auto targetColor = static_cast(matctrl->targetColor); osg::ref_ptr osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor)); setupController(matctrl, osgctrl, animflags); composite->addController(osgctrl); @@ -1519,6 +1536,10 @@ namespace NifOsg { // Set this texture to Off by default since we can't render it with the fixed-function pipeline stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); + osg::Matrix2 bumpMapMatrix(texprop->bumpMapMatrix.x(), texprop->bumpMapMatrix.y(), + texprop->bumpMapMatrix.z(), texprop->bumpMapMatrix.w()); + stateset->addUniform(new osg::Uniform("bumpMapMatrix", bumpMapMatrix)); + stateset->addUniform(new osg::Uniform("envMapLumaBias", texprop->envMapLumaBias)); } else if (i == Nif::NiTexturingProperty::DecalTexture) { @@ -1542,7 +1563,7 @@ namespace NifOsg texture2d->setName("diffuseMap"); break; case Nif::NiTexturingProperty::BumpTexture: - texture2d->setName("normalMap"); + texture2d->setName("bumpMap"); break; case Nif::NiTexturingProperty::GlowTexture: texture2d->setName("emissiveMap"); @@ -1698,9 +1719,8 @@ namespace NifOsg int lightmode = 1; - for (std::vector::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it) + for (const Nif::Property* property : properties) { - const Nif::Property* property = *it; switch (property->recType) { case Nif::RC_NiSpecularProperty: diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 51497cd27..1535bdbf6 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace Resource { @@ -103,14 +104,14 @@ void StatsHandler::toggle(osgViewer::ViewerBase *viewer) if (!_statsType) { - _camera->setNodeMask(0); + _camera->setNodeMask(SceneUtil::Mask_Disabled); _switch->setAllChildrenOff(); viewer->getViewerStats()->collectStats("resource", false); } else { - _camera->setNodeMask(0xffffffff); + _camera->setNodeMask(SceneUtil::Mask_Default); _switch->setSingleChildOn(_resourceStatsChildNum); viewer->getViewerStats()->collectStats("resource", true); diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp index ea8a4c2f6..b9c2fecef 100644 --- a/components/sceneutil/detourdebugdraw.cpp +++ b/components/sceneutil/detourdebugdraw.cpp @@ -50,10 +50,8 @@ namespace SceneUtil mDepthMask = state; } - void DebugDraw::texture(bool state) + void DebugDraw::texture(bool) { - if (state) - throw std::logic_error("DebugDraw does not support textures (at " __FILE__ ":" OPENMW_LINE_STRING ")"); } void DebugDraw::begin(osg::PrimitiveSet::Mode mode, float size) @@ -85,9 +83,10 @@ namespace SceneUtil vertex(pos[0], pos[1], pos[2], color, uv[0], uv[1]); } - void DebugDraw::vertex(const float, const float, const float, unsigned, const float, const float) + void DebugDraw::vertex(const float x, const float y, const float z, unsigned color, const float, const float) { - throw std::logic_error("Not implemented (at " __FILE__ ":" OPENMW_LINE_STRING ")"); + addVertex(osg::Vec3f(x, y, z)); + addColor(SceneUtil::colourFromRGBA(color)); } void DebugDraw::end() diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index e9be05908..c90fa8923 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -11,6 +11,7 @@ #include "lightcontroller.hpp" #include "util.hpp" #include "visitor.hpp" +#include "vismask.hpp" #include "positionattitudetransform.hpp" namespace SceneUtil @@ -58,7 +59,7 @@ namespace SceneUtil light->setQuadraticAttenuation(quadraticAttenuation); } - void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior) + void addLight (osg::Group* node, const ESM::Light* esmLight, bool isExterior) { SceneUtil::FindByNameVisitor visitor("AttachLight"); node->accept(visitor); @@ -71,7 +72,7 @@ namespace SceneUtil else { osg::ComputeBoundsVisitor computeBound; - computeBound.setTraversalMask(~partsysMask); + computeBound.setTraversalMask(~SceneUtil::Mask_ParticleSystem); // We want the bounds of all children of the node, ignoring the node's local transformation // So do a traverse(), not accept() computeBound.traverse(*node); @@ -85,15 +86,15 @@ namespace SceneUtil attachTo = trans; } - osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior); + osg::ref_ptr lightSource = createLightSource(esmLight, isExterior); attachTo->addChild(lightSource); } - osg::ref_ptr createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient) + osg::ref_ptr createLightSource(const ESM::Light* esmLight, bool isExterior, const osg::Vec4f& ambient) { osg::ref_ptr lightSource (new SceneUtil::LightSource); osg::ref_ptr light (new osg::Light); - lightSource->setNodeMask(lightMask); + lightSource->setNodeMask(SceneUtil::Mask_Lighting); float radius = esmLight->mData.mRadius; lightSource->setRadius(radius); diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index 7096c38b2..f72cf9f19 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -32,14 +32,14 @@ namespace SceneUtil /// @param partsysMask Node mask to ignore when computing the sub graph's bounding box. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. - void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior); + void addLight (osg::Group* node, const ESM::Light* esmLight, bool isExterior); /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and return it. /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. /// @param ambient Ambient component of the light. - osg::ref_ptr createLightSource (const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1)); + osg::ref_ptr createLightSource (const ESM::Light* esmLight, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1)); } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 9974e7097..9b3dc47f9 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -22,6 +22,8 @@ #include #include +#include + //#include #include @@ -42,7 +44,7 @@ class BaseOptimizerVisitor : public osg::NodeVisitor _optimizer(optimizer), _operationType(operation) { - setNodeMaskOverride(0xffffffff); + setNodeMaskOverride(SceneUtil::Mask_Default); } inline bool isOperationPermissibleForObject(const osg::StateSet* object) const; diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp new file mode 100644 index 000000000..2716f4683 --- /dev/null +++ b/components/sceneutil/recastmesh.cpp @@ -0,0 +1,48 @@ +#include "navmesh.hpp" +#include "detourdebugdraw.hpp" + +#include +#include + +#include + +#include + +namespace +{ + std::vector calculateNormals(const std::vector& vertices, const std::vector& indices) + { + std::vector result(indices.size()); + for (std::size_t i = 0, n = indices.size(); i < n; i += 3) + { + const float* v0_ptr = &vertices[indices[i] * 3]; + const float* v1_ptr = &vertices[indices[i + 1] * 3]; + const float* v2_ptr = &vertices[indices[i + 2] * 3]; + const osg::Vec3f v0(v0_ptr[0], v0_ptr[1], v0_ptr[2]); + const osg::Vec3f v1(v1_ptr[0], v1_ptr[1], v1_ptr[2]); + const osg::Vec3f v2(v2_ptr[0], v2_ptr[1], v2_ptr[2]); + const osg::Vec3f e0 = v1 - v0; + const osg::Vec3f e1 = v2 - v0; + osg::Vec3f normal = e0 ^ e1; + normal.normalize(); + for (std::size_t j = 0; j < 3; ++j) + result[i + j] = normal[j]; + } + return result; + } +} + +namespace SceneUtil +{ + osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, + const DetourNavigator::Settings& settings) + { + const osg::ref_ptr group(new osg::Group); + DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1.0f / settings.mRecastScaleFactor); + const auto normals = calculateNormals(recastMesh.getVertices(), recastMesh.getIndices()); + const auto texScale = 1.0f / (settings.mCellSize * 10.0f); + duDebugDrawTriMesh(&debugDraw, recastMesh.getVertices().data(), recastMesh.getVerticesCount(), + recastMesh.getIndices().data(), normals.data(), recastMesh.getTrianglesCount(), nullptr, texScale); + return group; + } +} diff --git a/components/sceneutil/recastmesh.hpp b/components/sceneutil/recastmesh.hpp new file mode 100644 index 000000000..ee5d9865e --- /dev/null +++ b/components/sceneutil/recastmesh.hpp @@ -0,0 +1,23 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H +#define OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H + +#include + +namespace osg +{ + class Group; +} + +namespace DetourNavigator +{ + class RecastMesh; + struct Settings; +} + +namespace SceneUtil +{ + osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, + const DetourNavigator::Settings& settings); +} + +#endif diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 6b88adaab..035c19a5f 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -2,6 +2,7 @@ #include +#include #include namespace SceneUtil @@ -21,7 +22,7 @@ namespace SceneUtil mShadowTechnique->enableShadows(); mShadowSettings->setLightNum(0); - mShadowSettings->setReceivesShadowTraversalMask(~0u); + mShadowSettings->setReceivesShadowTraversalMask(SceneUtil::Mask_Default); int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); numberOfShadowMapsPerLight = std::max(1, std::min(numberOfShadowMapsPerLight, 8)); diff --git a/apps/openmw/mwrender/vismask.hpp b/components/sceneutil/vismask.hpp similarity index 82% rename from apps/openmw/mwrender/vismask.hpp rename to components/sceneutil/vismask.hpp index f9f9dc74c..e9c35922c 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/components/sceneutil/vismask.hpp @@ -1,7 +1,7 @@ -#ifndef OPENMW_MWRENDER_VISMASK_H -#define OPENMW_MWRENDER_VISMASK_H +#ifndef OPENMW_COMPONENTS_SCENEUTIL_VISMASK_H +#define OPENMW_COMPONENTS_SCENEUTIL_VISMASK_H -namespace MWRender +namespace SceneUtil { /// Node masks used for controlling visibility of game objects. @@ -21,6 +21,8 @@ namespace MWRender /// compatibility if the enumeration values were to be changed. Feel free to change them when it makes sense. enum VisMask { + Mask_Disabled = 0, // For hidden nodes + Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors // child of Scene @@ -53,7 +55,19 @@ namespace MWRender Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<19) + Mask_Lighting = (1<<19), + + // For pathgrid nodes debugging + Mask_Pathgrid = (1<<20), + + // Editor control elements + Mask_EditorCellMarker = (1<<21), + Mask_EditorCellArrow = (1<<22), + Mask_EditorCellBorder = (1<<23), + Mask_EditorReference = (1<<24), + + // Default mask for OSG nodes + Mask_Default = 0xffffffff }; } diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index e256da0d6..9693bf511 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -77,6 +77,9 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con // Were there any lines at all in the file? bool existing = false; + // Is an entirely blank line queued to be added? + bool emptyLineQueued = false; + // The category/section we're currently in. std::string currentCategory; @@ -100,12 +103,22 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con // The current character position in the line. size_t i = 0; - // Don't add additional newlines at the end of the file. + // An empty line was queued. + if (emptyLineQueued) + { + emptyLineQueued = false; + // We're still going through the current category, so we should copy it. + if (currentCategory.empty() || istream.eof() || line[i] != '[') + ostream << std::endl; + } + + // Don't add additional newlines at the end of the file otherwise. if (istream.eof()) continue; - // Copy entirely blank lines. - if (!skipWhiteSpace(i, line)) { - ostream << line << std::endl; + // Queue entirely blank lines to add them if desired. + if (!skipWhiteSpace(i, line)) + { + emptyLineQueued = true; continue; } @@ -128,15 +141,22 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con continue; } - // Ensure that all options in the current category have been written. - for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { - if (mit->second == false && mit->first.first == currentCategory) { - Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " - << mit->first.second << " = " << settings.at(mit->first); - ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; - mit->second = true; - changed = true; + if (!currentCategory.empty()) + { + // Ensure that all options in the current category have been written. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) + { + if (mit->second == false && mit->first.first == currentCategory) + { + Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " + << mit->first.second << " = " << settings.at(mit->first); + ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; + mit->second = true; + changed = true; + } } + // Add an empty line after the last option in a category. + ostream << std::endl; } // Update the current category. diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index ae2da0947..7fb5d53f5 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "shadermanager.hpp" @@ -75,7 +76,7 @@ namespace Shader return newStateSet.get(); } - const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap" }; + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; bool isTextureNameRecognized(const std::string& name) { for (unsigned int i=0; igetTextureAttribute(unit, osg::StateAttribute::TEXTURE); @@ -130,6 +132,21 @@ namespace Shader diffuseMap = texture; else if (texName == "specularMap") specularMap = texture; + else if (texName == "bumpMap") + { + bumpMap = texture; + mRequirements.back().mShaderRequired = true; + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + // Bump maps are off by default as well + writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + } + else if (texName == "envMap") + { + static const bool preLightEnv = Settings::Manager::getBool("apply lighting to environment maps", "Shaders"); + if (preLightEnv) + mRequirements.back().mShaderRequired = true; + } } else Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; @@ -158,8 +175,11 @@ namespace Shader image = mImageManager.getImage(normalMapFileName); } } + // Avoid using the auto-detected normal map if it's already being used as a bump map. + // It's probably not an actual normal map. + bool hasNamesakeBumpMap = image && bumpMap && bumpMap->getImage(0) && image->getFileName() == bumpMap->getImage(0)->getFileName(); - if (image) + if (!hasNamesakeBumpMap && image) { osg::ref_ptr normalMapTex (new osg::Texture2D(image)); normalMapTex->setTextureSize(image->s(), image->t()); diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 6eabadf92..d6ecd5b5a 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -4,16 +4,17 @@ #include #include +#include + #include "world.hpp" #include "../esm/loadland.hpp" namespace Terrain { -CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask): +CellBorder::CellBorder(Terrain::World *world, osg::Group *root): mWorld(world), - mRoot(root), - mBorderMask(borderMask) + mRoot(root) { } @@ -69,7 +70,7 @@ void CellBorder::createCellBorderGeometry(int x, int y) polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); stateSet->setAttributeAndModes(polygonmode,osg::StateAttribute::ON); - borderGeode->setNodeMask(mBorderMask); + borderGeode->setNodeMask(SceneUtil::Mask_Debug); mRoot->addChild(borderGeode); diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index 908cdea09..7770204e1 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -16,7 +16,7 @@ namespace Terrain public: typedef std::map, osg::ref_ptr > CellGrid; - CellBorder(Terrain::World *world, osg::Group *root, int borderMask); + CellBorder(Terrain::World *world, osg::Group *root); void createCellBorderGeometry(int x, int y); void destroyCellBorderGeometry(int x, int y); diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 7583cd09f..a28554be9 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -150,12 +150,12 @@ void QuadTreeNode::traverseTo(ViewData* vd, float size, const osg::Vec2f& center } } -void QuadTreeNode::intersect(ViewData* vd, TerrainLineIntersector* intersector) +void QuadTreeNode::intersect(ViewData* vd, TerrainLineIntersector& intersector) { if (!hasValidBounds()) return; - if (!intersector->intersectAndClip(getBoundingBox())) + if (!intersector.intersectAndClip(getBoundingBox())) return; if (getNumChildren() == 0) diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 9aaf1beab..4adbc6025 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -20,6 +20,14 @@ namespace Terrain _parent = intersector; } + TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector) : + osgUtil::LineSegmentIntersector(intersector->getStart(), intersector->getEnd()) + { + setPrecisionHint(intersector->getPrecisionHint()); + _intersectionLimit = intersector->getIntersectionLimit(); + _parent = intersector; + } + bool intersectAndClip(const osg::BoundingBox& bbInput) { osg::Vec3d s(_start), e(_end); @@ -98,7 +106,7 @@ namespace Terrain void traverseTo(ViewData* vd, float size, const osg::Vec2f& center); /// Adds all leaf nodes which intersect the line from start to end - void intersect(ViewData* vd, TerrainLineIntersector* intersector); + void intersect(ViewData* vd, TerrainLineIntersector& intersector); private: QuadTreeNode* mParent; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index e8089e1c4..f998b7877 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -217,8 +218,8 @@ private: osg::ref_ptr mLodCallback; }; -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) - : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) + : TerrainGrid(parent, compileRoot, resourceSystem, storage) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) @@ -362,11 +363,17 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) if (!lineIntersector) throw std::runtime_error("Cannot update QuadTreeWorld: node visitor is not LineSegmentIntersector"); - osg::Matrix matrix = osg::Matrix::identity(); if (lineIntersector->getCoordinateFrame() == osgUtil::Intersector::CoordinateFrame::MODEL && iv->getModelMatrix() == 0) - matrix = lineIntersector->getTransformation(*iv, osgUtil::Intersector::CoordinateFrame::MODEL); - osg::ref_ptr terrainIntersector (new TerrainLineIntersector(lineIntersector, matrix)); - mRootNode->intersect(vd, terrainIntersector); + { + TerrainLineIntersector terrainIntersector(lineIntersector); + mRootNode->intersect(vd, terrainIntersector); + } + else + { + osg::Matrix matrix(lineIntersector->getTransformation(*iv, lineIntersector->getCoordinateFrame())); + TerrainLineIntersector terrainIntersector(lineIntersector, matrix); + mRootNode->intersect(vd, terrainIntersector); + } } } @@ -419,7 +426,7 @@ void QuadTreeWorld::enable(bool enabled) } if (mRootNode) - mRootNode->setNodeMask(enabled ? ~0 : 0); + mRootNode->setNodeMask(enabled ? SceneUtil::Mask_Default : SceneUtil::Mask_Disabled); } void QuadTreeWorld::cacheCell(View *view, int x, int y) diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index bcb671ee1..2bfd9f896 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -21,7 +21,7 @@ namespace Terrain class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) { public: - QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); ~QuadTreeWorld(); diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 7310846c2..f7a7644fd 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -18,8 +18,8 @@ public: virtual void reset() {} }; -TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask) - : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) +TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage) + : Terrain::World(parent, compileRoot, resourceSystem, storage) , mNumSplits(4) { } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index eb30fb97d..3764eb986 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -14,7 +14,7 @@ namespace Terrain class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); + TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage); ~TerrainGrid(); virtual void cacheCell(View* view, int x, int y); diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index da3bdb5c2..010ae9568 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "storage.hpp" #include "texturemanager.hpp" @@ -14,14 +15,14 @@ namespace Terrain { -World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask) +World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage) : mStorage(storage) , mParent(parent) , mResourceSystem(resourceSystem) , mBorderVisible(false) { mTerrainRoot = new osg::Group; - mTerrainRoot->setNodeMask(nodeMask); + mTerrainRoot->setNodeMask(SceneUtil::Mask_Terrain); mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); osg::ref_ptr material (new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); @@ -34,8 +35,8 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst compositeCam->setProjectionMatrix(osg::Matrix::identity()); compositeCam->setViewMatrix(osg::Matrix::identity()); compositeCam->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - compositeCam->setClearMask(0); - compositeCam->setNodeMask(preCompileMask); + compositeCam->setClearMask(SceneUtil::Mask_Disabled); + compositeCam->setNodeMask(SceneUtil::Mask_PreCompile); mCompositeMapCamera = compositeCam; compileRoot->addChild(compositeCam); @@ -47,7 +48,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); - mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); + mCellBorder.reset(new CellBorder(this,mTerrainRoot.get())); mResourceSystem->addResourceManager(mChunkManager.get()); mResourceSystem->addResourceManager(mTextureManager.get()); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 0402b8197..8929e0f6b 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -63,7 +63,7 @@ namespace Terrain /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures - World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); + World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage); virtual ~World(); /// Set a WorkQueue to delete objects in the background thread. diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index 2d9b497db..59bb7f88b 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -44,11 +44,11 @@ namespace Gui MYGUI_RTTI_DERIVED( AutoSizedTextBox ) public: - virtual MyGUI::IntSize getRequestedSize(); - virtual void setCaption(const MyGUI::UString& _value); + MyGUI::IntSize getRequestedSize() final; + void setCaption(const MyGUI::UString& _value) final; protected: - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; std::string mFontSize; }; @@ -58,14 +58,14 @@ namespace Gui public: - virtual MyGUI::IntSize getRequestedSize(); - virtual void setCaption(const MyGUI::UString& _value); + MyGUI::IntSize getRequestedSize() final; + void setCaption(const MyGUI::UString& _value) final; - virtual void initialiseOverride(); + void initialiseOverride() final; protected: - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); - virtual int getWidth(); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; + int getWidth(); std::string mFontSize; bool mShrink = false; bool mWasResized = false; @@ -77,11 +77,11 @@ namespace Gui MYGUI_RTTI_DERIVED( AutoSizedButton ) public: - virtual MyGUI::IntSize getRequestedSize(); - virtual void setCaption(const MyGUI::UString& _value); + MyGUI::IntSize getRequestedSize() final; + void setCaption(const MyGUI::UString& _value) final; protected: - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; std::string mFontSize; }; @@ -114,7 +114,7 @@ namespace Gui public: Spacer(); - virtual MyGUI::IntSize getRequestedSize() { return MyGUI::IntSize(0,0); } + MyGUI::IntSize getRequestedSize() final { return MyGUI::IntSize(0,0); } }; class HBox : public Box, public MyGUI::Widget @@ -122,18 +122,18 @@ namespace Gui MYGUI_RTTI_DERIVED( HBox ) public: - virtual void setSize (const MyGUI::IntSize &_value); - virtual void setCoord (const MyGUI::IntCoord &_value); + void setSize (const MyGUI::IntSize &_value) final; + void setCoord (const MyGUI::IntCoord &_value) final; protected: - virtual void initialiseOverride(); + void initialiseOverride() final; - virtual void align(); - virtual MyGUI::IntSize getRequestedSize(); + void align() final; + MyGUI::IntSize getRequestedSize() final; - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; - virtual void onWidgetCreated(MyGUI::Widget* _widget); + void onWidgetCreated(MyGUI::Widget* _widget) final; }; class VBox : public Box, public MyGUI::Widget @@ -141,18 +141,18 @@ namespace Gui MYGUI_RTTI_DERIVED( VBox) public: - virtual void setSize (const MyGUI::IntSize &_value); - virtual void setCoord (const MyGUI::IntCoord &_value); + void setSize (const MyGUI::IntSize &_value) final; + void setCoord (const MyGUI::IntCoord &_value) final; protected: - virtual void initialiseOverride(); + void initialiseOverride() final; - virtual void align(); - virtual MyGUI::IntSize getRequestedSize(); + void align() final; + MyGUI::IntSize getRequestedSize() final; - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; - virtual void onWidgetCreated(MyGUI::Widget* _widget); + void onWidgetCreated(MyGUI::Widget* _widget) final; }; } diff --git a/components/widgets/imagebutton.hpp b/components/widgets/imagebutton.hpp index bb2baa91f..160f24e99 100644 --- a/components/widgets/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -9,7 +9,7 @@ namespace Gui /** * @brief allows using different image textures depending on the button state */ - class ImageButton : public MyGUI::ImageBox + class ImageButton final : public MyGUI::ImageBox { MYGUI_RTTI_DERIVED(ImageButton) @@ -31,13 +31,13 @@ namespace Gui static bool sDefaultNeedKeyFocus; 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); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; + void onMouseLostFocus(MyGUI::Widget* _new) final; + void onMouseSetFocus(MyGUI::Widget* _old) final; + void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) final; + void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) final; + void onKeySetFocus(MyGUI::Widget* _old) final; + void onKeyLostFocus(MyGUI::Widget* _new) final; std::string mImageHighlighted; std::string mImageNormal; diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index cc7b75c2f..604d5dada 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -48,10 +48,10 @@ namespace Gui void scrollToTop(); - virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + void setPropertyOverride(const std::string& _key, const std::string& _value) final; protected: - void initialiseOverride(); + void initialiseOverride() final; void redraw(bool scrollbarShown = false); diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index 137583d37..ff16424d0 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -11,7 +11,7 @@ namespace Gui /** * @brief A variant of the EditBox that only allows integer inputs */ - class NumericEditBox : public FontWrapper + class NumericEditBox final : public FontWrapper { MYGUI_RTTI_DERIVED(NumericEditBox) @@ -22,8 +22,8 @@ namespace Gui { } - void initialiseOverride(); - void shutdownOverride(); + void initialiseOverride() final; + void shutdownOverride() final; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ValueChanged; EventHandle_ValueChanged eventValueChanged; @@ -36,8 +36,8 @@ namespace Gui void setMaxValue(int maxValue); private: void onEditTextChange(MyGUI::EditBox* sender); - void onKeyLostFocus(MyGUI::Widget* _new); - void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char character); + void onKeyLostFocus(MyGUI::Widget* _new) final; + void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char character) final; int mValue; diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 414349396..859543d20 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -13,7 +13,7 @@ namespace Gui typedef std::vector ButtonGroup; /// @brief A button that applies its own state changes to other widgets, to do this you define it as part of a ButtonGroup. - class SharedStateButton : public FontWrapper + class SharedStateButton final : public FontWrapper { MYGUI_RTTI_DERIVED(SharedStateButton) @@ -23,13 +23,13 @@ namespace Gui protected: void updateButtonState(); - virtual void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id); - virtual void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id); - virtual void onMouseSetFocus(MyGUI::Widget* _old); - virtual void onMouseLostFocus(MyGUI::Widget* _new); - virtual void baseUpdateEnable(); + void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) final; + void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) final; + void onMouseSetFocus(MyGUI::Widget* _old) final; + void onMouseLostFocus(MyGUI::Widget* _new) final; + void baseUpdateEnable() final; - virtual void shutdownOverride(); + void shutdownOverride() final; bool _setState(const std::string &_value); diff --git a/components/widgets/windowcaption.hpp b/components/widgets/windowcaption.hpp index b45da2d1c..f8c1a310c 100644 --- a/components/widgets/windowcaption.hpp +++ b/components/widgets/windowcaption.hpp @@ -8,17 +8,17 @@ namespace Gui /// Window caption that automatically adjusts "Left" and "Right" widgets in its skin /// based on the text size of the caption in the middle - class WindowCaption : public MyGUI::EditBox + class WindowCaption final : public MyGUI::EditBox { MYGUI_RTTI_DERIVED(WindowCaption) public: WindowCaption(); - virtual void setCaption(const MyGUI::UString &_value); - virtual void initialiseOverride(); + void setCaption(const MyGUI::UString &_value) final; + void initialiseOverride() final; - virtual void setSize(const MyGUI::IntSize& _value); - virtual void setCoord(const MyGUI::IntCoord& _value); + void setSize(const MyGUI::IntSize& _value) final; + void setCoord(const MyGUI::IntCoord& _value) final; private: MyGUI::Widget* mLeft; diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 9a3e9c84d..201704f35 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -298,3 +298,20 @@ A value of 0 means that you can only enchant one projectile. If you want to have Morrowind Code Patch-like count of projectiles being enchanted at once, set this value to 0.25 (i.e. 25% of the charge). This setting can only be configured by editing the settings configuration file. + +uncapped damage fatigue +----------------------- + +:Type: boolean +:Range: True/False +:Default: False + +There are four ways to decrease an actor's Fatigue stat in Morrowind gameplay mechanics: +Drain, Absorb, Damage Fatigue magic effects and hand-to-hand combat. +However, in Morrowind you can't knock down an actor with a Damage Fatigue spell or an Absorb Fatigue spell. +Morrowind Code Patch adds an option to make it possible for Damage Fatigue spells. This is the equivalent of that option. + +Setting the value of this setting to true will remove the 0 lower cap from the value, +allowing Damage Fatigue to reduce Fatigue to a value below zero. + +This setting can be controlled in Advanced tab of the launcher. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 083048332..c7817b6e8 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -166,6 +166,20 @@ Make visible all NPC's and creaure's plans where they are going. Works even if Navigator is disabled. Potentially decreases performance. +enable recast mesh render +---------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Render recast mesh that is built as set of culled tiles from physical mesh. +Should show similar mesh to physical one. +Little difference can be a result of floating point error. +Absent pieces usually mean a bug in recast mesh tiles building. +Allows to do in-game debug. +Potentially decreases performance. + Expert settings *************** diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b36f64285..e23cc3d54 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -124,3 +124,25 @@ terrain specular map pattern :Default: _diffusespec The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') + +apply lighting to environment maps +---------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. +Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. +Affected objects will use shaders. + +radial fog +---------- + +:Type: boolean +:Range: True/False +:Default: False + +By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. +This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. +Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. diff --git a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst index d87e4d244..0ad35d7a5 100644 --- a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst +++ b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst @@ -3,59 +3,59 @@ Normal maps from Morrowind to OpenMW ==================================== - `General introduction to normal map conversion`_ - - `Normal Mapping in OpenMW`_ - - `Activating normal mapping shaders in OpenMW`_ - - `Normal mapping in Morrowind with Morrowind Code Patch`_ - - `Normal mapping in Morrowind with MGE XE`_ + - `OpenMW normal-mapping`_ + - `Activating normal-mapping shaders in OpenMW`_ + - `Morrowind bump-mapping`_ + - `MGE XE normal-mapping`_ - `Converting PeterBitt's Scamp Replacer`_ (Mod made for the MGE XE PBR prototype) - `Tutorial - MGE`_ -- `Converting Lougian's Hlaalu Bump mapped`_ (MCP's fake bump map function, part 1: *without* custom models) - - `Tutorial - MCP, Part 1`_ -- `Converting Apel's Various Things - Sacks`_ (MCP's fake bump map function, part 2: *with* custom models) - - `Tutorial - MCP, Part 2`_ +- `Converting Lougian's Hlaalu Bump mapped`_ (Morrowind's bump-mapping, part 1: *without* custom models) + - `Tutorial - Morrowind, Part 1`_ +- `Converting Apel's Various Things - Sacks`_ (Morrowind's bump-mapping, part 2: *with* custom models) + - `Tutorial - Morrowind, Part 2`_ General introduction to normal map conversion ------------------------------------------------ -:Authors: Joakim (Lysol) Berg -:Updated: 2016-11-11 +:Authors: Joakim (Lysol) Berg, Alexei (Capo) Dobrohotov +:Updated: 2020-03-03 -This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using -the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most -(in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. +This page has general information and tutorials on how normal-mapping works in OpenMW and how you can make mods using +the old environment-mapped bump-mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most +(in)famous one to previously give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work better in OpenMW. *Note:* The conversion made in the `Converting Apel's Various Things - Sacks`_-part of this tutorial require the use of the application NifSkope_. -*Another note:* I will use the terms bump mapping and normal mapping simultaneously. -Normal mapping is one form of bump mapping. In other words, normal mapping is bump mapping, -but bump mapping isn't necessarily normal mapping. -There are several techniques for bump mapping, and normal mapping is the most common one today. +*Another note:* I will use the terms bump-mapping and normal-mapping simultaneously. +Normal-mapping is one form of bump-mapping. In other words, normal-mapping is bump-mapping, +but bump-mapping isn't necessarily normal-mapping. +There are several techniques for bump-mapping, and normal-mapping is the most common one today. So let's get on with it. -Normal Mapping in OpenMW +OpenMW normal-mapping ************************ -Normal mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, +Normal-mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, and you're done. -So to expand on this a bit, let's take a look at how a model seeks for textures. +So to expand on this a bit, let's take a look at how a model looks up textures. -Let us assume we have the model *example.nif*. In this model file, -there should be a tag (NiSourceTexture) that states what texture it should use and where to find it. Typically, -it will point to something like *exampletexture_01.dds*. This texture is supposed to be located directly in the -Textures folder since it does not state anything else. If the model is a custom made one, modders tend to group -their textures in separate folders, just to easily keep track of them. -It might be something like *./Textures/moddername/exampletexture_02.dds*. +Let us assume we have the model *example.nif*. In this model file, +there should be a tag (NiTexturingProperty) that states what textures it should use and where to find them. Typically, +the model's base (diffuse) texture reference will point to something named like *exampletexture_01.dds*. This texture is supposed to be located directly in the +Textures folder since it does not state anything else. +Modders tend to group textures for custom-made models in dedicated folders to keep track of them easily, +so it might be something like *./Textures/moddername/exampletexture_02.dds*. -When OpenMW finally adds normal mapping, it simply takes the NiSourceTexture file path, e.g., -*exampletexture_01.dds*, and looks for a *exampletexture_01_n.dds*. If it can't find this file, no normal mapping is added. -If it *does* find this file, the model will use this texture as a normal map. Simple. +OpenMW will pick the diffuse map file path from the mesh, e.g. +*exampletexture_01.dds*, and look up a texture named *exampletexture_01_n.dds*. +That file will be the normal map if it's present. Simple. -Activating normal mapping shaders in OpenMW +Activating normal-mapping shaders in OpenMW ******************************************* -Before normal (and specular and parallax) maps will show up in OpenMW, you'll need to activate them in the +Before normal (and specular and parallax) maps can show up in OpenMW, their auto-detection needs to be turned on in settings.cfg_-file. Add these rows where it would make sense: :: @@ -64,66 +64,51 @@ settings.cfg_-file. Add these rows where it would make sense: auto use object normal maps = true auto use terrain normal maps = true -And while we're at it, why not activate specular maps too just for the sake of it? - -:: - auto use object specular maps = true auto use terrain specular maps = true -Lastly, if you want really nice lights in OpenMW, add these rows: +See OpenMW's wiki page about `texture modding`_ to read more about it. -:: - - force shaders = true - clamp lighting = false - -See OpenMW's wiki page about `texture modding`_ to read further about this. - -Normal mapping in Morrowind with Morrowind Code Patch +Morrowind bump-mapping ***************************************************** **Conversion difficulty:** *Varies. Sometimes quick and easy, sometimes time-consuming and hard.* -You might have bumped (pun intended) on a few bump-mapped texture packs for Morrowind that require the -Morrowind Code Patch (MCP). You might even be thinking: Why doesn't OpenMW just support these instead of reinventing -the wheel? I know it sounds strange, but it will make sense. Here's how MCP handles normal maps: +You might have bumped (pun intended) on a few bump-mapped texture packs for Morrowind that require +Morrowind Code Patch (MCP). OpenMW supports them, and like MCP can optionally apply lighting after environment maps +are processed which makes bump-mapped models look a bit better, +can make use of the gloss map channel in the bump map and can apply bump-mapping to skinned models. +Add this to settings.cfg_-file: -Morrowind does not recognize normal maps (they weren't really a "thing" yet in 2002), so even if you have a normal map, -Morrowind will not load and display it. MCP has a clever way to solve this issue, by using something Morrowind *does* support, -namely environment maps. You could add a tag for an environment map and then add a normal map as the environment map, -but you'd end up with a shiny ugly model in the game. MCP solves this by turning down the brightness of the environment maps, -making the model look *kind of* as if it had a normal map applied to it. -I say kind of because it does not really look as good as normal mapping usually does. It was a hacky way to do it, -but it was the only way at the time, and therefore the best way. +:: -The biggest problem with this is not that it doesn't look as good as it could – no, -the biggest problem in my opinion is that it requires you to state the file paths for your normal map textures *in the models*! -For buildings, which often use several textures for one single model file, it could take *ages* to do this, -and you had to do it for dozens of model files too. You also had to ship your texture pack with model files, -making your mod bigger in file size. + [Shaders] + apply lighting to environment maps = true -These are basically the reasons why OpenMW does not support fake bump maps like MCP does. -It is just a really bad way to enhance your models, all the more when you have the possibility to do it in a better way. +But sometimes you may want them to look a bit better than in vanilla. +Technically you aren't supposed to convert bump maps because they shouldn't be normal maps that are supported by OpenMW as well, +but artists may use actual normal maps as bump maps either because they look better in vanilla... or because they're lazy. +In this case you can benefit from OpenMW's normal-mapping support by using these bump maps the way normal maps are used. +This means that you will have to drop the bump-mapping references from the model and sometimes rename the texture. -Normal mapping in Morrowind with MGE XE +MGE XE normal-mapping *************************************** **Conversion difficulty:** *Easy* -The most recent feature on this topic is that the Morrowind Graphics Extender (MGE) finally started to support real -normal mapping in an experimental version available here: `MGE XE`_ (you can't use MGE with OpenMW!). -Not only this but it also adds full support for physically based rendering (PBR), -making it one step ahead of OpenMW in terms of texturing techniques. However, -OpenMW will probably have this feature in the future too – and let's hope that OpenMW and MGE will handle PBR in a +The most recent feature on this topic is that the Morrowind Graphics Extender (MGE) finally started to support real +normal-mapping in an experimental version available here: `MGE XE`_ (you can't use MGE with OpenMW!). +Not only this but it also adds full support for physically based rendering (PBR), +making it one step ahead of OpenMW in terms of texturing techniques. However, +OpenMW will probably have this feature in the future too – and let's hope that OpenMW and MGE will handle PBR in a similar fashion in the future so that mods can be used for both MGE and OpenMW without any hassle. -I haven't researched that much on the MGE variant yet but it does support real implementation of normal mapping, -making it really easy to convert mods made for MGE into OpenMW (I'm only talking about the normal map textures though). -There's some kind of text file if I understood it correctly that MGE uses to find the normal map. -OpenMW does not need this, you just have to make sure the normal map has the same name as the diffuse texture but with +I haven't researched that much on the MGE variant yet but it does support real implementation of normal-mapping, +making it really easy to convert mods made for MGE into OpenMW (I'm only talking about the normal map textures though). +There's some kind of text file if I understood it correctly that MGE uses to find the normal map. +OpenMW does not need this, you just have to make sure the normal map has the same name as the diffuse texture but with the correct suffix after. Now, on to the tutorials. @@ -135,20 +120,20 @@ Converting PeterBitt's Scamp Replacer :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering -(PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly -disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. +So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering +(PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly +disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. Am I right? Great. Because you've come to the right place! -*A quick note before we begin*: Please note that you can only use the normal map texture and not the rest of the materials, -since PBR isn't implemented in OpenMW yet. Sometimes PBR textures can look dull without all of the texture files, +*A quick note before we begin*: Please note that you can only use the normal map texture and not the rest of the materials, +since PBR isn't implemented in OpenMW yet. Sometimes PBR textures can look dull without all of the texture files, so have that in mind. Tutorial - MGE ************** -In this tutorial, I will use PeterBitt's `PBR Scamp Replacer`_ as an example, -but any mod featuring PBR that requires the PBR version of MGE will do, +In this tutorial, I will use PeterBitt's `PBR Scamp Replacer`_ as an example, +but any mod featuring PBR that requires the PBR version of MGE will do, provided it also includes a normal map (which it probably does). So, follow these steps: @@ -163,67 +148,67 @@ So, follow these steps: #. Rename your newly extracted file (``tx_Scamp_normals.dds``) to ``tx_Scamp_n.dds`` (which is exactly the same name as the diffuse texture file, except for the added *_n* suffix before the filename extention). #. You're actually done! -So as you might notice, converting these mods is very simple and takes just a couple of minutes. +So as you might notice, converting these mods is very simple and takes just a couple of minutes. It's more or less just a matter of renaming and moving a few files. -I totally recommend you to also try this on PeterBitt's Nix Hound replacer and Flash3113's various replacers. +I totally recommend you to also try this on PeterBitt's Nix Hound replacer and Flash3113's various replacers. It should be the same principle to get those to work. -And let's hope that some one implements PBR shaders to OpenMW too, +And let's hope that some one implements PBR shaders to OpenMW too, so that we can use all the material files of these mods in the future. Converting Lougian's Hlaalu Bump mapped --------------------------------------- -**Mod made for MCP's fake bump function, without custom models** +**Mod made for Morrowind's bump-mapping, without custom models** -:Authors: Joakim (Lysol) Berg -:Updated: 2016-11-11 +:Authors: Joakim (Lysol) Berg, Alexei (Capo) Dobrohotov +:Updated: 2020-03-03 -Converting textures made for the Morrowind Code Patch (MCP) fake bump mapping can be really easy or a real pain, -depending on a few circumstances. In this tutorial, we will look at a very easy, +Converting normal maps made for the Morrowind's bump-mapping can be really easy or a real pain, +depending on a few circumstances. In this tutorial, we will look at a very easy, although in some cases a bit time-consuming, example. -Tutorial - MCP, Part 1 +Tutorial - Morrowind, Part 1 ********************** We will be converting a quite popular texture replacer of the Hlaalu architecture, namely Lougian's `Hlaalu Bump mapped`_. -Since this is just a texture pack and not a model replacer, -we can convert the mod in a few minutes by just renaming a few dozen files and by *not* extracting the included model +Since this is just a texture pack and not a model replacer, +we can convert the mod in a few minutes by just renaming a few dozen files and by *not* extracting the included model (``.nif``) files when installing the mod. #. Download Lougian's `Hlaalu Bump mapped`_. #. Install the mod by extracting the ``./Textures`` folder to a data folder the way you usually install mods (**Pro tip**: Install using OpenMW's `Multiple data folders`_ function!). - - Again, yes, *only* the ``./Textures`` folder. Do *not* extract the Meshes folder. They are only there to make the MCP hack work, which is not of any interest to us. + - Again, yes, *only* the ``./Textures`` folder. Do not extract the Meshes folder. They are there to make Morrowind bump-mapping work. #. Go to your new texture folder. If you installed the mod like I recommended, you won't have any trouble finding the files. If you instead placed all your files in Morrowinds main Data Files folder (sigh), you need to check with the mod's .rar file to see what files you should look for. Because you'll be scrolling through a lot of files. #. Find all the textures related to the texture pack in the Textures folder and take note of all the ones that ends with a *_nm.dds*. #. The *_nm.dds* files are normal map files. OpenMW's standard format is to have the normal maps with a *_n.dds* instead. Rename all the normal map textures to only have a *_n.dds* instead of the *_nm.dds*. - As a nice bonus to this tutorial, this pack actually included one specularity texture too. We should use it of course. It's the one called "``tx_glass_amber_02_reflection.dds``". For OpenMW to recognize this file and use it as a specular map, you need to change the *_reflection.dds* part to *_spec.dds*, resulting in the name ``tx_glass_amber_01_spec.dds``. #. That should be it. Really simple, but I do know that it takes a few minutes to rename all those files. -Now – if the mod you want to change includes custom made models it gets a bit more complicated I'm afraid. +Now – if the mod you want to change includes custom made models it gets a bit more complicated I'm afraid. But that is for the next tutorial. Converting Apel's Various Things - Sacks ---------------------------------------- -**Mod made for MCP's fake bump function, with custom models** +**Mod made for Morrowind bump-mapping, with custom models** -:Authors: Joakim (Lysol) Berg -:Updated: 2016-11-09 +:Authors: Joakim (Lysol) Berg, Alexei (Capostrophic) Dobrohotov +:Updated: 2020-03-03 -In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) -files so that the normal maps could be loaded in Morrowind with MCP. -We ignored those model files since they are not needed with OpenMW. In this tutorial however, -we will convert a mod that includes new, custom made models. In other words, we cannot just ignore those files this time. +In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) +files so that the bump maps could be loaded as normal maps. +We ignored those model files since they are not needed with OpenMW. In this tutorial however, +we will convert a mod that includes new, custom-made models. In other words, we cannot just ignore those files this time. -Tutorial - MCP, Part 2 +Tutorial - Morrowind, Part 2 ********************** -The sacks included in Apel's `Various Things - Sacks`_ come in two versions – Without bump mapping, and with bump mapping. -Since we want the glory of normal mapping in our OpenMW setup, we will go with the bump-mapped version. +The sacks included in Apel's `Various Things - Sacks`_ come in two versions – without bump-mapping, and with bump-mapping. +Since we want the glory of normal-mapping in our OpenMW setup, we will go with the bump-mapped version. #. Start by downloading Apel's `Various Things - Sacks`_ from Nexus. #. Once downloaded, install it the way you'd normally install your mods (**Pro tip**: Install using OpenMW's `Multiple data folders`_ function!). -#. Now, if you ran the mod right away, your sacks will be made out of lead_. This is because the normal map is loaded as an environment map which MCP fixes so that it looks less shiny. We don't use MCP, so therefore, it looks kind of like the shack was made out of lead. +#. Now, if you ran the mod right away, your sacks may look... wetter than expected. This is because the mod assumes you have the MCP feature which makes the sacks less shiny enabled. You can have its equivalent enabled to make the sacks look like in Morrowind with MCP, or you may proceed on the tutorial. #. We need to fix this by removing some tags in the model files. You need to download NifSkope_ for this, which, again, only have binaries available for Windows. #. Go the place where you installed the mod and go to ``./Meshes/o/`` to find the model files. - If you installed the mod like I suggested, finding the files will be easy as a pie, but if you installed it by dropping everything into your main Morrowind Data Files folder, then you'll have to scroll a lot to find them. Check the mod's zip file for the file names of the models if this is the case. The same thing applies to when fixing the textures. @@ -232,14 +217,14 @@ Since we want the glory of normal mapping in our OpenMW setup, we will go with t - NiSourceTexture with the value that appears to be a normal map file, in this mod, they have the suffix *_nm.dds*. #. Remove all these tags by selecting them one at a time and press right click>Block>Remove Branch. (Ctrl-Del) #. Repeat this on all the affected models. -#. If you launch OpenMW now, you'll `no longer have shiny models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here. +#. If you launch OpenMW now, you'll `no longer have wet models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here. #. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question. #. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*. #. Finally, `we are done`_! -Since these models have one or two textures applied to them, the fix was not that time-consuming. The process continues to work for more complex models that use more textures, but looking through each category for texture effects and normal mapped textures rapidly becomes tedious. Luckily, NifSkope provides a feature to do the same automatically. +Since these models have one or two textures applied to them, the fix was not that time-consuming. The process continues to work for more complex models that use more textures, but looking through each category for texture effects and normal mapped textures rapidly becomes tedious. Luckily, NifSkope provides a feature to do the same automatically. -Rightclick in NifSkope to access the *Spells* dropdown menu, also available via the top bar, hover over the *Blocks* section, and `choose the action to Remove by ID`_. You can then input the RegEx expression ``^NiTextureEffect`` (directing it to remove any block whose name starts with "NiTextureEffect") to automatically remove all texture effect blocks within the NIF. This also has the helpful side effect of listing `all the blocks within the NIF in the bottom section`_, allowing you to additionally root out any blocks referencing *_nm.dds* textures without having to painstakingly open each category. +Right-click in NifSkope to access the *Spells* dropdown menu, also available via the top bar, hover over the *Blocks* section, and `choose the action to Remove by ID`_. You can then input the RegEx expression ``^NiTextureEffect`` (directing it to remove any block whose name starts with "NiTextureEffect") to automatically remove all texture effect blocks within the NIF. This also has the helpful side effect of listing `all the blocks within the NIF in the bottom section`_, allowing you to additionally root out any blocks referencing *_nm.dds* textures without having to painstakingly open each category. .. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/? .. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/? @@ -251,10 +236,9 @@ Rightclick in NifSkope to access the *Spells* dropdown menu, also available via .. _settings.cfg: https://wiki.openmw.org/index.php?title=Settings .. _`Multiple data folders`: https://wiki.openmw.org/index.php?title=Mod_installation .. _`Various Things - Sacks`: https://www.nexusmods.com/morrowind/mods/42558/? -.. _Lead: https://imgur.com/bwpcYlc .. _NifSkope: https://wiki.openmw.org/index.php?title=Tools#NifSkope .. _Blocks: https://imgur.com/VmQC0WG -.. _`no longer have shiny models`: https://imgur.com/vu1k7n1 +.. _`no longer have wet models`: https://imgur.com/vu1k7n1 .. _`we are done`: https://imgur.com/yyZxlTw .. _`choose the action to Remove by ID`: https://imgur.com/a/qs2t0tC .. _`all the blocks within the NIF in the bottom section`: https://imgur.com/a/UFFNyWt diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index 51523af3f..65e71834c 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -23,18 +23,6 @@ To plug in a normal map, you name the normal map as the diffuse texture but with OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. See the section `Automatic use`_ further down below for detailed information. -.. note:: - While the original Morrowind engine does support the loading of a BumpTexture slot in the NIF, - it will not display it as a normal map. Morrowind Code Patch (MCP) - added a way to hack normal maps into the engine by first enabling the engine to load the BumpTexture slot as an - environment map and then turn down the brightness of the environment map. - This will imitate how a real normal map shader would display a normal map, but it will not look exactly the same. - OpenMW uses standard normal mapping, which achieves much better results. - Unfortunately, this difference can result in incompatibilities. - Some mods - (e.g. `Redoran Bump Mapped `_) - look much darker compared to the vanilla engine and will have to be recalibrated. - Specular Mapping ################ @@ -43,14 +31,12 @@ The alpha channel specifies shininess in range [0, 255]. If a specular map is used, it will override the shininess and specular color set in the NiMaterialProperty / osg::Material. -NIF files do not support specular maps. +Morrowind format NIF files do not support normal maps or specular maps. In order to use them anyway, see the next section. Automatic Use ############# -In addition to editing mesh files, -there is another way of plugging in these texture maps. Simply create the textures with appropriate naming convention (e.g. when the base texture is called foo.dds, the normal map would have to be called foo_n.dds). diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout index 714872fc3..8e1082952 100644 --- a/files/mygui/openmw_alchemy_window.layout +++ b/files/mygui/openmw_alchemy_window.layout @@ -57,8 +57,7 @@ - - + @@ -71,6 +70,21 @@ + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_companion_window.layout b/files/mygui/openmw_companion_window.layout index 1266da397..dd1fa8447 100644 --- a/files/mygui/openmw_companion_window.layout +++ b/files/mygui/openmw_companion_window.layout @@ -4,8 +4,15 @@ + + + + + + + - + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index 8f5b4c146..a555c9403 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -50,6 +50,11 @@ + + + + + diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index 67a9346f4..a3bf10684 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -24,6 +24,7 @@ + diff --git a/files/mygui/openmw_trade_window.layout b/files/mygui/openmw_trade_window.layout index 30e22302d..44f6597fd 100644 --- a/files/mygui/openmw_trade_window.layout +++ b/files/mygui/openmw_trade_window.layout @@ -27,6 +27,11 @@ + + + + + diff --git a/files/opencs/defaultfilters b/files/opencs/defaultfilters index 0ac3c8db4..cd46590d9 100644 Binary files a/files/opencs/defaultfilters and b/files/opencs/defaultfilters differ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ecc6be72e..89f873b3a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -272,6 +272,10 @@ normalise race speed = false # A value of 0 means that you can only enchant one projectile. projectiles enchant multiplier = 0 +# Make Damage Fatigue magic effect uncapped like Drain Fatigue effect. +# This means that unlike Morrowind you will be able to knock down actors using this effect. +uncapped damage fatigue = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). @@ -342,6 +346,14 @@ specular map pattern = _spec # The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') terrain specular map pattern = _diffusespec +# Apply lighting to reflections on the environment-mapped objects like in Morrowind Code Patch. +# Affected objects use shaders. +apply lighting to environment maps = false + +# Determine fog intensity based on the distance from the eye point. +# This makes fogging independent from the viewing angle. Shaders will be used to render all objects. +radial fog = false + [Input] # Capture control of the cursor prevent movement outside the window. @@ -740,6 +752,9 @@ enable nav mesh render = false # Render agents paths (true, false) enable agents paths render = false +# Render recast mesh (true, false) +enable recast mesh render = false + # Max number of navmesh tiles (value >= 0) max tiles number = 512 diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index eb605d421..31e929a90 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -42,7 +42,15 @@ uniform sampler2D specularMap; varying vec2 specularMapUV; #endif -varying float depth; +#if @bumpMap +uniform sampler2D bumpMap; +varying vec2 bumpMapUV; +uniform vec2 envMapLumaBias; +uniform mat2 bumpMapMatrix; +#endif + +varying float euclideanDepth; +varying float linearDepth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -114,7 +122,32 @@ void main() gl_FragData[0].xyz = mix(gl_FragData[0].xyz, decalTex.xyz, decalTex.a); #endif - float shadowing = unshadowedLightRatio(depth); +#if @envMap + + vec2 envTexCoordGen = envMapUV; + float envLuma = 1.0; + +#if @normalMap + // if using normal map + env map, take advantage of per-pixel normals for envTexCoordGen + vec3 viewVec = normalize(passViewPos.xyz); + vec3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + envTexCoordGen = vec2(r.x/m + 0.5, r.y/m + 0.5); +#endif + +#if @bumpMap + vec4 bumpTex = texture2D(bumpMap, bumpMapUV); + envTexCoordGen += bumpTex.rg * bumpMapMatrix; + envLuma = clamp(bumpTex.b * envMapLumaBias.x + envMapLumaBias.y, 0.0, 1.0); +#endif + +#if @preLightEnv + gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; +#endif + +#endif + + float shadowing = unshadowedLightRatio(linearDepth); #if !PER_PIXEL_LIGHTING @@ -128,26 +161,14 @@ void main() gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor, shadowing); #endif +#if @envMap && !@preLightEnv + gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; +#endif + #if @emissiveMap gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; #endif - -#if @envMap - -#if @normalMap - // if using normal map + env map, take advantage of per-pixel normals for texCoordGen - vec3 viewVec = normalize(passViewPos.xyz); - vec3 r = reflect( viewVec, viewNormal ); - float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); - vec2 texCoordGen = vec2(r.x/m + 0.5, r.y/m + 0.5); - gl_FragData[0].xyz += texture2D(envMap, texCoordGen).xyz * envMapColor.xyz; -#else - gl_FragData[0].xyz += texture2D(envMap, envMapUV).xyz * envMapColor.xyz; -#endif - -#endif - #if @specularMap vec4 specTex = texture2D(specularMap, specularMapUV); float shininess = specTex.a * 255; @@ -158,8 +179,11 @@ void main() #endif gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; - - float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); applyShadowDebugOverlay(); diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index dcb241274..dc8c91b03 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -29,11 +29,16 @@ varying vec4 passTangent; varying vec2 envMapUV; #endif +#if @bumpMap +varying vec2 bumpMapUV; +#endif + #if @specularMap varying vec2 specularMapUV; #endif -varying float depth; +varying float euclideanDepth; +varying float linearDepth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -53,10 +58,12 @@ varying vec3 passNormal; void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; - depth = gl_Position.z; vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + linearDepth = gl_Position.z; + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); #if @envMap @@ -91,6 +98,10 @@ void main(void) passTangent = gl_MultiTexCoord7.xyzw; #endif +#if @bumpMap + bumpMapUV = (gl_TextureMatrix[@bumpMapUV] * gl_MultiTexCoord@bumpMapUV).xy; +#endif + #if @specularMap specularMapUV = (gl_TextureMatrix[@specularMapUV] * gl_MultiTexCoord@specularMapUV).xy; #endif diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 4910788bb..b1917d33b 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -12,7 +12,8 @@ uniform sampler2D normalMap; uniform sampler2D blendMap; #endif -varying float depth; +varying float euclideanDepth; +varying float linearDepth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -66,7 +67,7 @@ void main() gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; #endif - float shadowing = unshadowedLightRatio(depth); + float shadowing = unshadowedLightRatio(linearDepth); #if !PER_PIXEL_LIGHTING @@ -90,7 +91,11 @@ void main() gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos), shininess, matSpec) * shadowing; - float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); applyShadowDebugOverlay(); diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 8a9cf82cc..14e291f43 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,7 +1,8 @@ #version 120 varying vec2 uv; -varying float depth; +varying float euclideanDepth; +varying float linearDepth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -21,10 +22,11 @@ varying vec3 passNormal; void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; - depth = gl_Position.z; vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + linearDepth = gl_Position.z; vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 808fab5ee..6e51a2082 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -124,7 +124,7 @@ vec2 normalCoords(vec2 uv, float scale, float speed, float time, float timer1, f varying vec3 screenCoordsPassthrough; varying vec4 position; -varying float depthPassthrough; +varying float linearDepth; uniform sampler2D normalMap; @@ -160,7 +160,7 @@ void main(void) vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; UV.y *= -1.0; - float shadow = unshadowedLightRatio(depthPassthrough); + float shadow = unshadowedLightRatio(linearDepth); vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; screenCoords.y = (1.0-screenCoords.y); @@ -203,11 +203,17 @@ void main(void) float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); // air to water; water to air float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); +#if @radialFog + float radialDepth = distance(position.xyz, cameraPos); + float radialize = radialDepth / linearDepth; +#else + float radialize = 1.0; +#endif vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if REFRACTION - float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x); - float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x); - float surfaceDepth = linearizeDepth(gl_FragCoord.z); + float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x) * radialize; + float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x) * radialize; + float surfaceDepth = linearizeDepth(gl_FragCoord.z) * radialize; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif @@ -246,7 +252,11 @@ void main(void) #endif // fog - float fogValue = clamp((depthPassthrough - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#if @radialFog + float fogValue = clamp((radialDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); applyShadowDebugOverlay(); diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 2377f0af4..02a395f95 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -2,7 +2,7 @@ varying vec3 screenCoordsPassthrough; varying vec4 position; -varying float depthPassthrough; +varying float linearDepth; #include "shadows_vertex.glsl" @@ -20,7 +20,7 @@ void main(void) position = gl_Vertex; - depthPassthrough = gl_Position.z; + linearDepth = gl_Position.z; setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz)); } diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 8748a6d0b..2ea5d777c 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -226,6 +226,16 @@ + + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + + + Uncapped Damage Fatigue + + + diff --git a/files/ui/contentselector.ui b/files/ui/contentselector.ui index 7832239b5..d13cb314e 100644 --- a/files/ui/contentselector.ui +++ b/files/ui/contentselector.ui @@ -20,7 +20,16 @@ Qt::DefaultContextMenu - + + 0 + + + 0 + + + 0 + + 0 @@ -33,10 +42,41 @@ 3 - - - false + + + Qt::NoFocus + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + + + + true + + + true + + + + diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 8e42ee7cb..ccac5050e 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -159,6 +159,22 @@ Uncheck Selection + + + + + + + + Refresh Data Files + + + Refresh Data Files + + + Ctrl+R + +