diff --git a/AUTHORS.md b/AUTHORS.md index 99080fdebd..9791171b9c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -140,6 +140,7 @@ Programmers Lordrea Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) + Mads Sandvei (Foal) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ac1067da..4e8516ae25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0 Bug #4382: Sound output device does not change when it should + Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely + Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses @@ -33,6 +35,7 @@ Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack + Bug #6932: Creatures flee from my followers and we have to chase after them Bug #6939: OpenMW-CS: ID columns are too short Bug #6949: Sun Damage effect doesn't work in quasi exteriors Bug #6964: Nerasa Dralor Won't Follow @@ -58,6 +61,7 @@ Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7163: Myar Aranath: Wheat breaks the GUI Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7204: Missing actor scripts freeze the game Bug #7229: Error marker loading failure is not handled Bug #7243: Supporting loading external files from VFS from esm files Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack @@ -70,6 +74,7 @@ Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles + Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading @@ -79,6 +84,12 @@ Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7630: Charm can be cast on creatures + Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation + Bug #7637: Actors can sometimes move while playing scripted animations + Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7642: Items in repair and recharge menus aren't sorted alphabetically + Bug #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION @@ -109,7 +120,9 @@ Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music Feature #7618: Show the player character's health in the save details + Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb + Feature #7652: Sort inactive post processing shaders list properly Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 756aaba131..b6192d3c02 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -40,8 +40,35 @@ namespace { void contentSubdirs(const QString& path, QStringList& dirs) { - QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.ba2", "*.omwscripts" }; - QStringList dirFilter{ "bookart", "icons", "meshes", "music", "sound", "textures" }; + static const QStringList fileFilter{ + "*.esm", + "*.esp", + "*.bsa", + "*.ba2", + "*.omwgame", + "*.omwaddon", + "*.omwscripts", + }; + + static const QStringList dirFilter{ + "animations", + "bookart", + "fonts", + "icons", + "interface", + "l10n", + "meshes", + "music", + "mygui", + "scripts", + "shaders", + "sound", + "splash", + "strings", + "textures", + "trees", + "video", + }; QDir currentDir(path); if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() @@ -587,18 +614,6 @@ void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString& text) mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } -QString Launcher::DataFilesPage::selectDirectory() -{ - QFileDialog fileDialog(this); - fileDialog.setFileMode(QFileDialog::Directory); - fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly); - - if (fileDialog.exec() == QDialog::Rejected) - return {}; - - return QDir(fileDialog.selectedFiles()[0]).canonicalPath(); -} - void Launcher::DataFilesPage::addSubdirectories(bool append) { int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); @@ -606,22 +621,30 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) if (selectedRow == -1) return; - const auto rootDir = selectDirectory(); - if (rootDir.isEmpty()) + QString rootPath = QFileDialog::getExistingDirectory( + this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly); + + if (rootPath.isEmpty()) return; - QStringList subdirs; - contentSubdirs(rootDir, subdirs); + const QDir rootDir(rootPath); + rootPath = rootDir.canonicalPath(); - if (subdirs.empty()) + QStringList subdirs; + contentSubdirs(rootPath, subdirs); + + // Always offer to append the root directory just in case + if (subdirs.isEmpty() || subdirs[0] != rootPath) + subdirs.prepend(rootPath); + else if (subdirs.size() == 1) { - // we didn't find anything that looks like a content directory, add directory selected by user - if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) - { - ui.directoryListWidget->addItem(rootDir); - mNewDataDirs.push_back(rootDir); - refreshDataFilesView(); - } + // We didn't find anything else that looks like a content directory + // Automatically add the directory selected by user + if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) + return; + ui.directoryListWidget->addItem(rootPath); + mNewDataDirs.push_back(rootPath); + refreshDataFilesView(); return; } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 033c91f9c7..dc3aeaef6f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -131,7 +131,6 @@ namespace Launcher void reloadCells(QStringList selectedFiles); void refreshDataFilesView(); void updateNavMeshProgress(int minDataSize); - QString selectDirectory(); /** * Returns the file paths of all selected content files diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 9a10bf6d9c..18fb57805f 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -96,32 +96,29 @@ bool Launcher::GraphicsPage::loadSettings() // Visuals - int vsync = Settings::Manager::getInt("vsync mode", "Video"); - if (vsync < 0 || vsync > 2) - vsync = 0; + const int vsync = Settings::video().mVsyncMode; vSyncComboBox->setCurrentIndex(vsync); - size_t windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); - if (windowMode > static_cast(Settings::WindowMode::Windowed)) - windowMode = 0; - windowModeComboBox->setCurrentIndex(windowMode); - slotFullScreenChanged(windowMode); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; - if (Settings::Manager::getBool("window border", "Video")) + windowModeComboBox->setCurrentIndex(static_cast(windowMode)); + handleWindowModeChange(windowMode); + + if (Settings::video().mWindowBorder) windowBorderCheckBox->setCheckState(Qt::Checked); // aaValue is the actual value (0, 1, 2, 4, 8, 16) - int aaValue = Settings::Manager::getInt("antialiasing", "Video"); + const int aaValue = Settings::video().mAntialiasing; // aaIndex is the index into the allowed values in the pull down. - int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); + const int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - int width = Settings::Manager::getInt("resolution x", "Video"); - int height = Settings::Manager::getInt("resolution y", "Video"); + const int width = Settings::video().mResolutionX; + const int height = Settings::video().mResolutionY; QString resolution = QString::number(width) + QString(" x ") + QString::number(height); - screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video")); + screenComboBox->setCurrentIndex(Settings::video().mScreen); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -137,7 +134,7 @@ bool Launcher::GraphicsPage::loadSettings() customHeightSpinBox->setValue(height); } - float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video"); + const float fpsLimit = Settings::video().mFramerateLimit; if (fpsLimit != 0) { framerateLimitCheckBox->setCheckState(Qt::Checked); @@ -198,23 +195,10 @@ void Launcher::GraphicsPage::saveSettings() { // Visuals - // Ensure we only set the new settings if they changed. This is to avoid cluttering the - // user settings file (which by definition should only contain settings the user has touched) - int cVSync = vSyncComboBox->currentIndex(); - if (cVSync != Settings::Manager::getInt("vsync mode", "Video")) - Settings::Manager::setInt("vsync mode", "Video", cVSync); - - int cWindowMode = windowModeComboBox->currentIndex(); - if (cWindowMode != Settings::Manager::getInt("window mode", "Video")) - Settings::Manager::setInt("window mode", "Video", cWindowMode); - - bool cWindowBorder = windowBorderCheckBox->checkState(); - if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) - Settings::Manager::setBool("window border", "Video", cWindowBorder); - - int cAAValue = antiAliasingComboBox->currentText().toInt(); - if (cAAValue != Settings::Manager::getInt("antialiasing", "Video")) - Settings::Manager::setInt("antialiasing", "Video", cAAValue); + Settings::video().mVsyncMode.set(static_cast(vSyncComboBox->currentIndex())); + Settings::video().mWindowMode.set(static_cast(windowModeComboBox->currentIndex())); + Settings::video().mWindowBorder.set(windowBorderCheckBox->checkState() == Qt::Checked); + Settings::video().mAntialiasing.set(antiAliasingComboBox->currentText().toInt()); int cWidth = 0; int cHeight = 0; @@ -234,25 +218,17 @@ void Launcher::GraphicsPage::saveSettings() cHeight = customHeightSpinBox->value(); } - if (cWidth != Settings::Manager::getInt("resolution x", "Video")) - Settings::Manager::setInt("resolution x", "Video", cWidth); - - if (cHeight != Settings::Manager::getInt("resolution y", "Video")) - Settings::Manager::setInt("resolution y", "Video", cHeight); - - int cScreen = screenComboBox->currentIndex(); - if (cScreen != Settings::Manager::getInt("screen", "Video")) - Settings::Manager::setInt("screen", "Video", cScreen); + Settings::video().mResolutionX.set(cWidth); + Settings::video().mResolutionY.set(cHeight); + Settings::video().mScreen.set(screenComboBox->currentIndex()); if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { - float cFpsLimit = framerateLimitSpinBox->value(); - if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video")) - Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit); + Settings::video().mFramerateLimit.set(framerateLimitSpinBox->value()); } - else if (Settings::Manager::getFloat("framerate limit", "Video") != 0) + else if (Settings::video().mFramerateLimit != 0) { - Settings::Manager::setFloat("framerate limit", "Video", 0); + Settings::video().mFramerateLimit.set(0); } // Lighting @@ -392,8 +368,12 @@ void Launcher::GraphicsPage::screenChanged(int screen) void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { - if (mode == static_cast(Settings::WindowMode::Fullscreen) - || mode == static_cast(Settings::WindowMode::WindowedFullscreen)) + handleWindowModeChange(static_cast(mode)); +} + +void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) +{ + if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 92bdf35ac4..85f91d1ff1 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -3,7 +3,7 @@ #include "ui_graphicspage.h" -#include +#include namespace Files { @@ -40,6 +40,7 @@ namespace Launcher static QRect getMaximumResolution(); bool setupSDL(); + void handleWindowModeChange(Settings::WindowMode state); }; } #endif diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 023d6b729a..bba3bbe5e1 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,13 +1,5 @@ #include "maindialog.hpp" -#include -#include -#include -#include -#include -#include -#include - #include #include #include @@ -15,10 +7,15 @@ #include #include +#include +#include #include #include #include +#include #include +#include +#include #include "datafilespage.hpp" #include "graphicspage.hpp" diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7393562cfb..c8c1ada5a9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -313,6 +313,8 @@ bool OMW::Engine::frame(float frametime) mLuaManager->reportStats(frameNumber, *stats); } + mStereoManager->updateSettings(Settings::camera().mNearClip, Settings::camera().mViewingDistance); + mViewer->eventTraversal(); mViewer->updateTraversal(); @@ -452,14 +454,13 @@ void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame) void OMW::Engine::createWindow() { - int screen = Settings::Manager::getInt("screen", "Video"); - int width = Settings::Manager::getInt("resolution x", "Video"); - int height = Settings::Manager::getInt("resolution y", "Video"); - Settings::WindowMode windowMode - = static_cast(Settings::Manager::getInt("window mode", "Video")); - bool windowBorder = Settings::Manager::getBool("window border", "Video"); - int vsync = Settings::Manager::getInt("vsync mode", "Video"); - unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video")); + const int screen = Settings::video().mScreen; + const int width = Settings::video().mResolutionX; + const int height = Settings::video().mResolutionY; + const Settings::WindowMode windowMode = Settings::video().mWindowMode; + const bool windowBorder = Settings::video().mWindowBorder; + const SDLUtil::VSyncMode vsync = Settings::video().mVsyncMode; + unsigned antialiasing = static_cast(Settings::video().mAntialiasing); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); @@ -482,8 +483,7 @@ void OMW::Engine::createWindow() if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; - SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0"); + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, Settings::video().mMinimizeOnFocusLoss ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); @@ -513,7 +513,7 @@ void OMW::Engine::createWindow() Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing / 2; antialiasing /= 2; - Settings::Manager::setInt("antialiasing", "Video", antialiasing); + Settings::video().mAntialiasing.set(antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } @@ -560,7 +560,7 @@ void OMW::Engine::createWindow() SDL_DestroyWindow(mWindow); mWindow = nullptr; antialiasing /= 2; - Settings::Manager::setInt("antialiasing", "Video", antialiasing); + Settings::video().mAntialiasing.set(antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } @@ -593,7 +593,63 @@ void OMW::Engine::createWindow() realizeOperations->add(mSelectColorFormatOperation); if (Stereo::getStereo()) - realizeOperations->add(new Stereo::InitializeStereoOperation()); + { + Stereo::Settings settings; + + settings.mMultiview = Settings::stereo().mMultiview; + settings.mAllowDisplayListsForMultiview = Settings::stereo().mAllowDisplayListsForMultiview; + settings.mSharedShadowMaps = Settings::stereo().mSharedShadowMaps; + + if (Settings::stereo().mUseCustomView) + { + const osg::Vec3 leftEyeOffset(Settings::stereoView().mLeftEyeOffsetX, + Settings::stereoView().mLeftEyeOffsetY, Settings::stereoView().mLeftEyeOffsetZ); + + const osg::Quat leftEyeOrientation(Settings::stereoView().mLeftEyeOrientationX, + Settings::stereoView().mLeftEyeOrientationY, Settings::stereoView().mLeftEyeOrientationZ, + Settings::stereoView().mLeftEyeOrientationW); + + const osg::Vec3 rightEyeOffset(Settings::stereoView().mRightEyeOffsetX, + Settings::stereoView().mRightEyeOffsetY, Settings::stereoView().mRightEyeOffsetZ); + + const osg::Quat rightEyeOrientation(Settings::stereoView().mRightEyeOrientationX, + Settings::stereoView().mRightEyeOrientationY, Settings::stereoView().mRightEyeOrientationZ, + Settings::stereoView().mRightEyeOrientationW); + + settings.mCustomView = Stereo::CustomView{ + .mLeft = Stereo::View{ + .pose = Stereo::Pose{ + .position = leftEyeOffset, + .orientation = leftEyeOrientation, + }, + .fov = Stereo::FieldOfView{ + .angleLeft = Settings::stereoView().mLeftEyeFovLeft, + .angleRight = Settings::stereoView().mLeftEyeFovRight, + .angleUp = Settings::stereoView().mLeftEyeFovUp, + .angleDown = Settings::stereoView().mLeftEyeFovDown, + }, + }, + .mRight = Stereo::View{ + .pose = Stereo::Pose{ + .position = rightEyeOffset, + .orientation = rightEyeOrientation, + }, + .fov = Stereo::FieldOfView{ + .angleLeft = Settings::stereoView().mRightEyeFovLeft, + .angleRight = Settings::stereoView().mRightEyeFovRight, + .angleUp = Settings::stereoView().mRightEyeFovUp, + .angleDown = Settings::stereoView().mRightEyeFovDown, + }, + }, + }; + } + + if (Settings::stereo().mUseCustomEyeResolution) + settings.mEyeResolution + = osg::Vec2i(Settings::stereoView().mEyeResolutionX, Settings::stereoView().mEyeResolutionY); + + realizeOperations->add(new Stereo::InitializeStereoOperation(settings)); + } mViewer->realize(); mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); @@ -632,9 +688,9 @@ void OMW::Engine::prepareEngine() mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mEnvironment.setStateManager(*mStateManager); - bool stereoEnabled - = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); - mStereoManager = std::make_unique(mViewer, stereoEnabled); + const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo(); + mStereoManager = std::make_unique( + mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance); osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); @@ -866,7 +922,7 @@ void OMW::Engine::go() // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); - mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); + mEnvironment.setFrameRateLimit(Settings::video().mFramerateLimit); prepareEngine(); diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index 1668799eba..5017b1b272 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ namespace MWClass ESM4Named::registerSelf(); ESM4Light::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Npc::registerSelf(); ESM4Named::registerSelf(); ESM4Static::registerSelf(); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4950a8c40b..eebcb99512 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -464,18 +464,20 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); + const bool isInCombat = aiSequence.isInCombat(); if (stats.isDead()) { // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + if (Settings::game().mCanLootDuringDeathAnimation && !isInCombat) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) + else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback @@ -570,7 +572,8 @@ namespace MWClass if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) return true; - return !customData.mCreatureStats.getAiSequence().isInCombat(); + const MWMechanics::AiSequence& aiSeq = customData.mCreatureStats.getAiSequence(); + return !aiSeq.isInCombat() || aiSeq.isFleeing(); } MWGui::ToolTipInfo Creature::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 5c05d81692..9c02af963d 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -138,17 +138,17 @@ namespace MWClass { } - bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } - - MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override - { - return ESM4Impl::getToolTipInfo(ptr.get()->mBase->mFullName, count); - } - std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return ptr.get()->mBase->mFullName; } + + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override + { + return ESM4Impl::getToolTipInfo(getName(ptr), count); + } + + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return !getName(ptr).empty(); } }; } diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 92fff6f873..ba74eadc7a 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -128,6 +128,11 @@ namespace MWGui mLines.swap(lines); + std::stable_sort(mLines.begin(), mLines.end(), + [](const MWGui::ItemChargeView::Line& a, const MWGui::ItemChargeView::Line& b) { + return Misc::StringUtils::ciLess(a.mText->getCaption().asUTF8(), b.mText->getCaption().asUTF8()); + }); + layoutWidgets(); } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 088024ac63..6d79b5f9d7 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -89,7 +89,7 @@ namespace { if (!Settings::map().mAllowZooming) return Constants::CellGridRadius; - if (!Settings::Manager::getBool("distant terrain", "Terrain")) + if (!Settings::terrain().mDistantTerrain) return Constants::CellGridRadius; const int viewingDistanceInCells = Settings::camera().mViewingDistance / Constants::CellSizeInUnits; return std::clamp( diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index c5393fbfb7..3be0bb1c06 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -47,6 +47,8 @@ namespace MWGui MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; + std::vector> items; + for (MWWorld::ContainerStoreIterator iter(store.begin(categories)); iter != store.end(); ++iter) { if (iter->getClass().hasItemHealth(*iter)) @@ -76,22 +78,31 @@ namespace MWGui name += " - " + MyGUI::utility::toString(price) + MWBase::Environment::get().getESMStore()->get().find("sgp")->mValue.getString(); - MyGUI::Button* button = mList->createWidget(price <= playerGold - ? "SandTextButton" - : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - 0, currentY, 0, lineHeight, MyGUI::Align::Default); - - currentY += lineHeight; - - button->setUserString("Price", MyGUI::utility::toString(price)); - button->setUserData(MWWorld::Ptr(*iter)); - button->setCaptionWithReplacing(name); - button->setSize(mList->getWidth(), lineHeight); - button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); - button->setUserString("ToolTipType", "ItemPtr"); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + items.emplace_back(name, price, *iter); } } + + std::stable_sort(items.begin(), items.end(), + [](const auto& a, const auto& b) { return Misc::StringUtils::ciLess(std::get<0>(a), std::get<0>(b)); }); + + for (const auto& [name, price, ptr] : items) + { + MyGUI::Button* button = mList->createWidget(price <= playerGold + ? "SandTextButton" + : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip + 0, currentY, 0, lineHeight, MyGUI::Align::Default); + + currentY += lineHeight; + + button->setUserString("Price", MyGUI::utility::toString(price)); + button->setUserData(MWWorld::Ptr(ptr)); + button->setCaptionWithReplacing(name); + button->setSize(mList->getWidth(), lineHeight); + button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); + button->setUserString("ToolTipType", "ItemPtr"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the // scrollbar is hidden mList->setVisibleVScroll(false); diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index e54704d6d4..ab5bdf791d 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -421,7 +422,12 @@ namespace MWGui auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + std::vector techniques; for (const auto& [name, _] : processor->getTechniqueMap()) + techniques.push_back(name); + std::sort(techniques.begin(), techniques.end(), Misc::StringUtils::ciLess); + + for (const std::string& name : techniques) { auto technique = processor->loadTechnique(name); diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index aabd3d46ab..63b51d7d2c 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -49,7 +49,7 @@ namespace MWGui = new SortFilterItemModel(std::make_unique(MWMechanics::getPlayer())); model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); mRepairBox->setModel(model); - + mRepairBox->update(); // Reset scrollbars mRepairBox->resetScrollbars(); } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1a41f9bb55..7cefa7bda8 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -258,7 +258,7 @@ namespace MWGui , mKeyboardMode(true) , mCurrentPage(-1) { - bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool terrain = Settings::terrain().mDistantTerrain; const std::string_view widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; MyGUI::Widget* unusedSlider; getWidget(unusedSlider, widgetName); @@ -354,7 +354,7 @@ namespace MWGui += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); // fill resolution list - int screen = Settings::Manager::getInt("screen", "Video"); + const int screen = Settings::video().mScreen; int numDisplayModes = SDL_GetNumDisplayModes(screen); std::vector> resolutions; for (int i = 0; i < numDisplayModes; i++) @@ -396,8 +396,7 @@ namespace MWGui updateMaxLightsComboBox(mMaxLights); - Settings::WindowMode windowMode - = static_cast(Settings::Manager::getInt("window mode", "Video")); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; mWindowBorderButton->setEnabled( windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); @@ -491,8 +490,8 @@ namespace MWGui int resX, resY; parseResolution(resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + Settings::video().mResolutionX.set(resX); + Settings::video().mResolutionY.set(resY); apply(); } @@ -506,8 +505,8 @@ namespace MWGui { mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); - int currentX = Settings::Manager::getInt("resolution x", "Video"); - int currentY = Settings::Manager::getInt("resolution y", "Video"); + const int currentX = Settings::video().mResolutionX; + const int currentY = Settings::video().mResolutionY; for (size_t i = 0; i < mResolutionList->getItemCount(); ++i) { @@ -591,23 +590,22 @@ namespace MWGui "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); } - void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* _sender, size_t pos) + void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - int index = static_cast(_sender->getIndexSelected()); - Settings::Manager::setInt("vsync mode", "Video", index); + Settings::video().mVsyncMode.set(static_cast(sender->getIndexSelected())); apply(); } - void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) + void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - int index = static_cast(_sender->getIndexSelected()); - if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + const Settings::WindowMode windowMode = static_cast(sender->getIndexSelected()); + if (windowMode == Settings::WindowMode::WindowedFullscreen) { mResolutionList->setEnabled(false); mWindowModeHint->setVisible(true); @@ -618,12 +616,12 @@ namespace MWGui mWindowModeHint->setVisible(false); } - if (index == static_cast(Settings::WindowMode::Windowed)) + if (windowMode == Settings::WindowMode::Windowed) mWindowBorderButton->setEnabled(true); else mWindowBorderButton->setEnabled(false); - Settings::Manager::setInt("window mode", "Video", index); + Settings::video().mWindowMode.set(windowMode); apply(); } @@ -849,14 +847,12 @@ namespace MWGui void SettingsWindow::updateWindowModeSettings() { - size_t index = static_cast(Settings::Manager::getInt("window mode", "Video")); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; + const std::size_t windowModeIndex = static_cast(windowMode); - if (index > static_cast(Settings::WindowMode::Windowed)) - index = MyGUI::ITEM_NONE; + mWindowModeList->setIndexSelected(windowModeIndex); - mWindowModeList->setIndexSelected(index); - - if (index != static_cast(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE) + if (windowMode != Settings::WindowMode::Windowed && windowModeIndex != MyGUI::ITEM_NONE) { // check if this resolution is supported in fullscreen if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) @@ -864,8 +860,8 @@ namespace MWGui const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); int resX, resY; parseResolution(resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + Settings::video().mResolutionX.set(resX); + Settings::video().mResolutionY.set(resY); } bool supported = false; @@ -882,8 +878,7 @@ namespace MWGui fallbackY = resY; } - if (resX == Settings::Manager::getInt("resolution x", "Video") - && resY == Settings::Manager::getInt("resolution y", "Video")) + if (resX == Settings::video().mResolutionX && resY == Settings::video().mResolutionY) supported = true; } @@ -891,26 +886,21 @@ namespace MWGui { if (fallbackX != 0 && fallbackY != 0) { - Settings::Manager::setInt("resolution x", "Video", fallbackX); - Settings::Manager::setInt("resolution y", "Video", fallbackY); + Settings::video().mResolutionX.set(fallbackX); + Settings::video().mResolutionY.set(fallbackY); } } mWindowBorderButton->setEnabled(false); } - if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + if (windowMode == Settings::WindowMode::WindowedFullscreen) mResolutionList->setEnabled(false); } void SettingsWindow::updateVSyncModeSettings() { - int index = static_cast(Settings::Manager::getInt("vsync mode", "Video")); - - if (index < 0 || index > 2) - index = 0; - - mVSyncModeList->setIndexSelected(index); + mVSyncModeList->setIndexSelected(static_cast(Settings::video().mVsyncMode)); } void SettingsWindow::layoutControlsBox() diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 43b37623d5..1d41bab34f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -294,8 +294,7 @@ namespace MWGui += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); mVideoWrapper = std::make_unique(window, viewer); - mVideoWrapper->setGammaContrast( - Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast); if (useShaders) mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager()); @@ -1157,25 +1156,22 @@ namespace MWGui changeRes = true; else if (setting.first == "Video" && setting.second == "vsync mode") - mVideoWrapper->setSyncToVBlank(Settings::Manager::getInt("vsync mode", "Video")); + mVideoWrapper->setSyncToVBlank(Settings::video().mVsyncMode); else if (setting.first == "Video" && (setting.second == "gamma" || setting.second == "contrast")) - mVideoWrapper->setGammaContrast( - Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast); } if (changeRes) { - mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), - Settings::Manager::getInt("resolution y", "Video"), - static_cast(Settings::Manager::getInt("window mode", "Video")), - Settings::Manager::getBool("window border", "Video")); + mVideoWrapper->setVideoMode(Settings::video().mResolutionX, Settings::video().mResolutionY, + Settings::video().mWindowMode, Settings::video().mWindowBorder); } } void WindowManager::windowResized(int x, int y) { - Settings::Manager::setInt("resolution x", "Video", x); - Settings::Manager::setInt("resolution y", "Video", y); + Settings::video().mResolutionX.set(x); + Settings::video().mResolutionY.set(y); // We only want to process changes to window-size related settings. Settings::CategorySettingVector filter = { { "Video", "resolution x" }, { "Video", "resolution y" } }; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index eb82c160f9..5f9a3fde85 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -170,10 +170,9 @@ namespace MWInput void ActionManager::screenshot() { - const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - bool regularScreenshot = settingStr.empty() || settingStr == "regular"; + const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType; - if (regularScreenshot) + if (settings.mType == Settings::ScreenshotType::Regular) { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 32e48a008e..298006030a 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -42,8 +42,7 @@ namespace MWInput float angle = 0; - SDL_DisplayOrientation currentOrientation - = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); + SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::video().mScreen); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index bbdba00ee2..e3470eb853 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -94,8 +94,8 @@ namespace MWLua api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; double aspect = (height == 0.0) ? 1.0 : width / height; double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); osg::Matrixf invertedViewMatrix; @@ -106,8 +106,8 @@ namespace MWLua }; api["worldToViewportVector"] = [camera](osg::Vec3f pos) { - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; osg::Matrix windowMatrix = osg::Matrix::translate(1.0, 1.0, 1.0) * osg::Matrix::scale(0.5 * width, 0.5 * height, 0.5); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 46a72c0848..7c3b31c7b6 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -240,6 +241,9 @@ namespace MWLua case ESM::REC_MISC4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_MSTT4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_ALCH4: cell.mStore->template forEachType(visitor); break; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index fc686dcbb3..63a2838250 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -246,6 +246,13 @@ namespace MWLua reloadAllScriptsImpl(); mReloadAllScriptsRequested = false; } + + if (mDelayedUiModeChangedArg) + { + if (playerScripts) + playerScripts->uiModeChanged(*mDelayedUiModeChangedArg, true); + mDelayedUiModeChangedArg = std::nullopt; + } } void LuaManager::applyDelayedActions() @@ -275,6 +282,7 @@ namespace MWLua mGlobalScripts.removeAllScripts(); mGlobalScriptsStarted = false; mNewGameStarted = false; + mDelayedUiModeChangedArg = std::nullopt; if (!mPlayer.isEmpty()) { mPlayer.getCellRef().unsetRefNum(); @@ -325,9 +333,15 @@ namespace MWLua { if (mPlayer.isEmpty()) return; + ObjectId argId = arg.isEmpty() ? ObjectId() : getId(arg); + if (mApplyingDelayedActions) + { + mDelayedUiModeChangedArg = argId; + return; + } PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) - playerScripts->uiModeChanged(arg, mApplyingDelayedActions); + playerScripts->uiModeChanged(argId, false); } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 9f4c0096b5..a725761dbd 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -204,6 +204,7 @@ namespace MWLua std::optional mTeleportPlayerAction; std::vector mUIMessages; std::vector> mInGameConsoleMessages; + std::optional mDelayedUiModeChangedArg; LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index e8cad4968c..2d3aa9bc78 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -66,9 +66,9 @@ namespace MWLua } // `arg` is either forwarded from MWGui::pushGuiMode or empty - void uiModeChanged(const MWWorld::Ptr& arg, bool byLuaAction) + void uiModeChanged(ObjectId arg, bool byLuaAction) { - if (arg.isEmpty()) + if (arg.isZeroOrUnset()) callEngineHandlers(mUiModeChanged, byLuaAction); else callEngineHandlers(mUiModeChanged, byLuaAction, LObject(arg)); diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index ab15385f08..cef0753817 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -138,6 +138,18 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); return input->getControlSwitch(key); }; + player["isTeleportingEnabled"] = [](const Object& player) -> bool { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + return MWBase::Environment::get().getWorld()->isTeleportingEnabled(); + }; + player["setTeleportingEnabled"] = [](const Object& player, bool state) { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + if (dynamic_cast(&player) && !dynamic_cast(&player)) + throw std::runtime_error("Only player and global scripts can toggle teleportation."); + MWBase::Environment::get().getWorld()->enableTeleporting(state); + }; player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) throw std::runtime_error("The argument must be a player."); diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index eeb7061cc3..bd8b592f7a 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -47,6 +47,7 @@ namespace MWLua constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; constexpr std::string_view ESM4Light = "ESM4Light"; constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; + constexpr std::string_view ESM4MovableStatic = "ESM4MovableStatic"; constexpr std::string_view ESM4Potion = "ESM4Potion"; constexpr std::string_view ESM4Static = "ESM4Static"; constexpr std::string_view ESM4Terminal = "ESM4Terminal"; @@ -91,6 +92,7 @@ namespace MWLua { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, + { ESM::REC_MSTT4, ObjectTypeName::ESM4MovableStatic }, { ESM::REC_ALCH4, ObjectTypeName::ESM4Potion }, { ESM::REC_STAT4, ObjectTypeName::ESM4Static }, { ESM::REC_TERM4, ObjectTypeName::ESM4Terminal }, @@ -230,6 +232,7 @@ namespace MWLua addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); + addType(ObjectTypeName::ESM4MovableStatic, { ESM::REC_MSTT4 }); addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 }); addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 }); addESM4TerminalBindings(addType(ObjectTypeName::ESM4Terminal, { ESM::REC_TERM4 }), context); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 79f5fac9a1..d42f7b0637 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -239,10 +239,7 @@ namespace MWLua return luaManager->uiResourceManager()->registerTexture(data); }; - api["screenSize"] = []() { - return osg::Vec2f( - Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); - }; + api["screenSize"] = []() { return osg::Vec2f(Settings::video().mResolutionX, Settings::video().mResolutionY); }; api["_getAllUiModes"] = [](sol::this_state lua) { sol::table res(lua, sol::create); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 743c5d5ab5..3e7b075e62 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1998,12 +1998,12 @@ namespace MWMechanics } bool Actors::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) const + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); + return iter->second->getCharacterController().playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 98c64397ab..15a39136a6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -113,7 +113,7 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr& ptr) const; bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) const; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; void persistAnimationStates() const; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4c6ea42d36..5285fb31dd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -708,7 +708,7 @@ namespace MWMechanics mFleeDest = ESM::Pathgrid::Point(0, 0, 0); } - bool AiCombatStorage::isFleeing() + bool AiCombatStorage::isFleeing() const { return mFleeState != FleeState_None; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 92d380dbd8..494f038d6e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -70,7 +70,7 @@ namespace MWMechanics void startFleeing(); void stopFleeing(); - bool isFleeing(); + bool isFleeing() const; }; /// \brief Causes the actor to fight another actor diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b58927c993..de626ace95 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -135,6 +135,15 @@ namespace MWMechanics return mNumPursuitPackages > 0; } + bool AiSequence::isFleeing() const + { + if (!isInCombat()) + return false; + + const AiCombatStorage* storage = mAiState.getPtr(); + return storage && storage->isFleeing(); + } + bool AiSequence::isEngagedWithActor() const { if (!isInCombat()) @@ -272,7 +281,9 @@ namespace MWMechanics } else { - float rating = MWMechanics::getBestActionRating(actor, target); + float rating = 0.f; + if (MWMechanics::canFight(actor, target)) + rating = MWMechanics::getBestActionRating(actor, target); const ESM::Position& targetPos = target.getRefData().getPosition(); diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index ab3cc11e2c..92c1724ea6 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -117,6 +117,9 @@ namespace MWMechanics /// Is there any pursuit package. bool isInPursuit() const; + /// Is the actor fleeing? + bool isFleeing() const; + /// Removes all packages using the specified id. void removePackagesById(AiPackageTypeId id); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index d79469a9a0..f2ce17fd9c 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -38,6 +38,13 @@ namespace MWMechanics return *result; } + /// \brief returns pointer to stored object in the desired type + template + Derived* getPtr() const + { + return dynamic_cast(mStorage.get()); + } + template void copy(DerivedClassStorage& destination) const { diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 30756ade35..30aad2e89a 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -229,7 +229,7 @@ namespace MWMechanics } if (mPathFinder.isPathConstructed()) - storage.setState(AiWanderStorage::Wander_Walking); + storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid); } if (!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) @@ -499,7 +499,7 @@ namespace MWMechanics if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { if (mPathFinder.isPathConstructed()) - storage.setState(AiWanderStorage::Wander_Walking); + storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid); else storage.setState(AiWanderStorage::Wander_ChooseAction); } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 48abac8b06..068d91ab42 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -538,7 +538,7 @@ namespace MWMechanics if (mAnimation->isPlaying("containerclose")) return false; - mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containeropen", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); if (mAnimation->isPlaying("containeropen")) return false; @@ -559,7 +559,7 @@ namespace MWMechanics if (animPlaying) startPoint = 1.f - complete; - mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containerclose", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0); } } @@ -827,8 +827,8 @@ namespace MWMechanics void CharacterController::refreshCurrentAnims( CharacterState idle, CharacterState movement, JumpingState jump, bool force) { - // If the current animation is persistent, do not touch it - if (isPersistentAnimPlaying()) + // If the current animation is scripted, do not touch it + if (isScriptedAnimPlaying()) return; refreshHitRecoilAnims(); @@ -882,7 +882,7 @@ namespace MWMechanics mDeathState = chooseRandomDeathState(); // Do not interrupt scripted animation by death - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; playDeath(startpoint, mDeathState); @@ -1481,8 +1481,8 @@ namespace MWMechanics sndMgr->stopSound3D(mPtr, wolfRun); } - // Combat for actors with persistent animations obviously will be buggy - if (isPersistentAnimPlaying()) + // Combat for actors with scripted animations obviously will be buggy + if (isScriptedAnimPlaying()) return forcestateupdate; float complete = 0.f; @@ -1852,17 +1852,32 @@ namespace MWMechanics void CharacterController::updateAnimQueue() { - if (mAnimQueue.size() > 1) + if (mAnimQueue.empty()) + return; + + if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + // Remove the finished animation, unless it's a scripted animation that was interrupted by e.g. a rebuild of + // the animation object. + if (mAnimQueue.size() > 1 || !mAnimQueue.front().mScripted || mAnimQueue.front().mLoopCount == 0) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); - - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } + + if (!mAnimQueue.empty()) + { + // Move on to the remaining items of the queue + bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); + mAnimation->play(mAnimQueue.front().mGroup, + mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, + mAnimQueue.front().mLoopCount, loopfallback); + } + } + else + { + mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); } if (!mAnimQueue.empty()) @@ -2344,7 +2359,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled()) + if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, vec); } @@ -2371,8 +2386,7 @@ namespace MWMechanics } } - bool isPersist = isPersistentAnimPlaying(); - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (duration > 0.0f) moved /= duration; else @@ -2413,7 +2427,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2428,7 +2442,7 @@ namespace MWMechanics state.mScriptedAnims.clear(); for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) { - if (!iter->mPersist) + if (!iter->mScripted) continue; ESM::AnimationState::ScriptedAnimation anim; @@ -2464,7 +2478,7 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; - entry.mPersist = true; + entry.mScripted = true; mAnimQueue.push_back(entry); } @@ -2483,18 +2497,18 @@ namespace MWMechanics mIdleState = CharState_SpecialIdle; bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(anim.mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", + mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", complete, anim.mLoopCount, loopfallback); } } - bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool persist) + bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-persistent ones - if (isPersistentAnimPlaying() && !persist) + // We should not interrupt scripted animations with non-scripted ones + if (isScriptedAnimPlaying() && !scripted) return true; // If this animation is a looped animation (has a "loop start" key) that is already playing @@ -2523,17 +2537,17 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = groupname; entry.mLoopCount = count - 1; - entry.mPersist = persist; + entry.mScripted = scripted; if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { - clearAnimQueue(persist); + clearAnimQueue(scripted); clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = entry.mGroup.starts_with("idle"); - mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, + mAnimation->play(groupname, scripted && groupname != "idle" ? Priority_Scripted : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f, count - 1, loopfallback); } @@ -2544,7 +2558,7 @@ namespace MWMechanics // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing if (groupname == "idle") - entry.mPersist = false; + entry.mScripted = false; mAnimQueue.push_back(entry); @@ -2556,12 +2570,12 @@ namespace MWMechanics mSkipAnim = true; } - bool CharacterController::isPersistentAnimPlaying() const + bool CharacterController::isScriptedAnimPlaying() const { if (!mAnimQueue.empty()) { const AnimationQueueEntry& first = mAnimQueue.front(); - return first.mPersist && isAnimPlaying(first.mGroup); + return first.mScripted && isAnimPlaying(first.mGroup); } return false; @@ -2584,13 +2598,13 @@ namespace MWMechanics return movementAnimationControlled; } - void CharacterController::clearAnimQueue(bool clearPersistAnims) + void CharacterController::clearAnimQueue(bool clearScriptedAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearScriptedAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); - if (clearPersistAnims) + if (clearScriptedAnims) { mAnimQueue.clear(); return; @@ -2598,7 +2612,7 @@ namespace MWMechanics for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { - if (!it->mPersist) + if (!it->mScripted) it = mAnimQueue.erase(it); else ++it; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 316a1cff0e..c3d45fe0fb 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -40,7 +40,7 @@ namespace MWMechanics Priority_Torch, Priority_Storm, Priority_Death, - Priority_Persistent, + Priority_Scripted, Num_Priorities }; @@ -135,7 +135,7 @@ namespace MWMechanics { std::string mGroup; size_t mLoopCount; - bool mPersist; + bool mScripted; }; typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; @@ -207,7 +207,7 @@ namespace MWMechanics void refreshMovementAnims(CharacterState movement, bool force = false); void refreshIdleAnims(CharacterState idle, bool force = false); - void clearAnimQueue(bool clearPersistAnims = false); + void clearAnimQueue(bool clearScriptedAnims = false); bool updateWeaponState(); void updateIdleStormState(bool inwater) const; @@ -215,7 +215,7 @@ namespace MWMechanics std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); - bool isPersistentAnimPlaying() const; + bool isScriptedAnimPlaying() const; bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -270,7 +270,7 @@ namespace MWMechanics void persistAnimationState() const; void unpersistAnimationState(); - bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); + bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 071ac164f3..f95df16855 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -760,12 +760,12 @@ namespace MWMechanics } bool MechanicsManager::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { if (ptr.getClass().isActor()) - return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); + return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); else - return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); + return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 36bb18e022..997636522e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -142,7 +142,7 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) override; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; void persistAnimationStates() override; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index ab981dd459..5bdfc91ac7 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -99,12 +99,12 @@ namespace MWMechanics } bool Objects::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->playGroup(groupName, mode, number, persist); + return iter->second->playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 8b5962109c..296f454e4f 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -46,7 +46,7 @@ namespace MWMechanics void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false); + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index e0584afcd4..dd83db286f 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -118,13 +118,17 @@ namespace MWMechanics } int value = 50.f; - if (actor.getClass().isNpc()) - { - ESM::RefId skill = item.getClass().getEquipmentSkill(item); - if (!skill.empty()) - value = actor.getClass().getSkill(actor, skill); - } - else + ESM::RefId skill = item.getClass().getEquipmentSkill(item); + if (!skill.empty()) + value = actor.getClass().getSkill(actor, skill); + // Prefer hand-to-hand if our skill is 0 (presumably due to magic) + if (value <= 0.f) + return 0.f; + // Note that a creature with a dagger and 0 Stealth will forgo the weapon despite using Combat for hit chance. + // The same creature will use a sword provided its Combat stat isn't 0. We're using the "skill" value here to + // decide whether to use the weapon at all, but adjusting the final rating based on actual hit chance - i.e. the + // Combat stat. + if (!actor.getClass().isNpc()) { MWWorld::LiveCellRef* ref = actor.get(); value = ref->mBase->mData.mCombat; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b49f382f66..0741e24a69 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1145,8 +1145,7 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) - && stateiter->second.mPlaying) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; break; @@ -1158,7 +1157,7 @@ namespace MWRender while (stateiter != mStates.end()) { AnimState& state = stateiter->second; - if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) + if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted))) { ++stateiter; continue; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index fbf5bf112a..86371dc0bf 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -154,7 +154,7 @@ namespace MWRender public: CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) - : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, + : RTTNode(sizeX, sizeY, Settings::video().mAntialiasing, false, 0, StereoAwareness::Unaware_MultiViewShaders, shouldAddMSAAIntermediateTarget()) , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) { diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 385ce46fff..99ebb94647 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -457,14 +457,14 @@ namespace MWRender : GenericResourceManager(nullptr, Settings::cells().mCacheExpiryDelay) , Terrain::QuadTreeWorld::ChunkManager(worldspace) , mSceneManager(sceneManager) + , mActiveGrid(Settings::terrain().mObjectPagingActiveGrid) + , mDebugBatches(Settings::terrain().mDebugChunks) + , mMergeFactor(Settings::terrain().mObjectPagingMergeFactor) + , mMinSize(Settings::terrain().mObjectPagingMinSize) + , mMinSizeMergeFactor(Settings::terrain().mObjectPagingMinSizeMergeFactor) + , mMinSizeCostMultiplier(Settings::terrain().mObjectPagingMinSizeCostMultiplier) , mRefTrackerLocked(false) { - mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); - mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); - mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); - mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); - mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); - mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); } std::map ObjectPaging::collectESM3References( diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 9162657e30..a6945de299 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -112,7 +112,7 @@ namespace MWRender : osg::Group() , mEnableLiveReload(false) , mRootNode(rootNode) - , mSamples(Settings::Manager::getInt("antialiasing", "Video")) + , mSamples(Settings::video().mAntialiasing) , mDirty(false) , mDirtyFrameId(0) , mRendering(rendering) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..090e6be312 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1230,8 +1230,8 @@ namespace MWRender if (mViewDistance < mNearClip) throw std::runtime_error("Viewing distance is less than near clip"); - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; double aspect = (height == 0.0) ? 1.0 : width / height; float fov = mFieldOfView; @@ -1316,23 +1316,21 @@ namespace MWRender return existingChunkMgr->second; RenderingManager::WorldspaceChunkMgr newChunkMgr; - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + const float lodFactor = Settings::terrain().mLodFactor; const bool groundcover = Settings::groundcover().mEnabled; - bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool distantTerrain = Settings::terrain().mDistantTerrain; if (distantTerrain || groundcover) { - const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); - int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); - compMapPower = std::max(-3, compMapPower); - float compMapLevel = pow(2, compMapPower); - const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); - float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); - maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); - bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); + const int compMapResolution = Settings::terrain().mCompositeMapResolution; + const int compMapPower = Settings::terrain().mCompositeMapLevel; + const float compMapLevel = std::pow(2, compMapPower); + const int vertexLodMod = Settings::terrain().mVertexLodMod; + const float maxCompGeometrySize = Settings::terrain().mMaxCompositeGeometrySize; + const bool debugChunks = Settings::terrain().mDebugChunks; auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); - if (Settings::Manager::getBool("object paging", "Terrain")) + if (Settings::terrain().mObjectPaging) { newChunkMgr.mObjectPaging = std::make_unique(mResourceSystem->getSceneManager(), worldspace); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 336a321cf0..b4bdda0a28 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,7 +21,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwgui/loadingscreen.hpp" #include "postprocessor.hpp" #include "util.hpp" @@ -29,7 +29,7 @@ namespace MWRender { - enum Screenshot360Type + enum class Screenshot360Type { Spherical, Cylindrical, @@ -161,59 +161,46 @@ namespace MWRender bool ScreenshotManager::screenshot360(osg::Image* image) { - int screenshotW = mViewer->getCamera()->getViewport()->width(); - int screenshotH = mViewer->getCamera()->getViewport()->height(); - Screenshot360Type screenshotMapping = Spherical; + const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType; - const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - std::vector settingArgs; - Misc::StringUtils::split(settingStr, settingArgs); + Screenshot360Type screenshotMapping = Screenshot360Type::Spherical; - if (settingArgs.size() > 0) + switch (settings.mType) { - std::string_view typeStrings[4] = { "spherical", "cylindrical", "planet", "cubemap" }; - bool found = false; - - for (int i = 0; i < 4; ++i) - { - if (settingArgs[0] == typeStrings[i]) - { - screenshotMapping = static_cast(i); - found = true; - break; - } - } - - if (!found) - { - Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; + case Settings::ScreenshotType::Regular: + Log(Debug::Warning) << "Wrong screenshot 360 type: regular."; return false; - } + case Settings::ScreenshotType::Cylindrical: + screenshotMapping = Screenshot360Type::Cylindrical; + break; + case Settings::ScreenshotType::Spherical: + screenshotMapping = Screenshot360Type::Spherical; + break; + case Settings::ScreenshotType::Planet: + screenshotMapping = Screenshot360Type::Planet; + break; + case Settings::ScreenshotType::Cubemap: + screenshotMapping = Screenshot360Type::RawCubemap; + break; } + int screenshotW = mViewer->getCamera()->getViewport()->width(); + + if (settings.mWidth.has_value()) + screenshotW = *settings.mWidth; + + int screenshotH = mViewer->getCamera()->getViewport()->height(); + + if (settings.mHeight.has_value()) + screenshotH = *settings.mHeight; + // planet mapping needs higher resolution - int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; - - if (settingArgs.size() > 1) - { - screenshotW = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); - } - - if (settingArgs.size() > 2) - { - screenshotH = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); - } - - if (settingArgs.size() > 3) - { - cubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); - } - - bool rawCubemap = screenshotMapping == RawCubemap; + const int cubeSize = screenshotMapping == Screenshot360Type::Planet ? screenshotW : screenshotW / 2; + const bool rawCubemap = screenshotMapping == Screenshot360Type::RawCubemap; if (rawCubemap) screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - else if (screenshotMapping == Planet) + else if (screenshotMapping == Screenshot360Type::Planet) screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; @@ -276,7 +263,7 @@ namespace MWRender stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); - stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); + stateset->addUniform(new osg::Uniform("mapping", static_cast(screenshotMapping))); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); screenshotCamera->addChild(quad); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 234b022f5d..cd03784e8c 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -70,7 +70,7 @@ namespace MWRender bool shouldAddMSAAIntermediateTarget() { - return Settings::shaders().mAntialiasAlphaTest && Settings::Manager::getInt("antialiasing", "Video") > 1; + return Settings::shaders().mAntialiasAlphaTest && Settings::video().mAntialiasing > 1; } } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index edee3963e7..4a0d302cd8 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -31,6 +31,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/levelledlist.hpp" +#include "interpretercontext.hpp" #include "ref.hpp" namespace @@ -94,6 +95,12 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + runtime.getContext().report("Failed to add item '" + item.getRefIdString() + "': unknown ID"); + return; + } + if (count < 0) count = static_cast(count); @@ -210,6 +217,12 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + runtime.getContext().report("Failed to remove item '" + item.getRefIdString() + "': unknown ID"); + return; + } + if (count < 0) count = static_cast(count); diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 1f0a9a37cf..6511fbdb01 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "ref.hpp" @@ -89,6 +90,13 @@ namespace MWScript ESM::RefId topic = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->get().search(topic)) + { + runtime.getContext().report( + "Failed to add topic '" + topic.getRefIdString() + "': topic record not found"); + return; + } + MWBase::Environment::get().getDialogueManager()->addTopic(topic); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 43da00afe3..687b512106 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,14 @@ namespace MWScript MWWorld::Ptr target = R()(runtime, false); ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + + if (!MWBase::Environment::get().getESMStore()->get().search(name)) + { + runtime.getContext().report( + "Failed to start global script '" + name.getRefIdString() + "': script record not found"); + return; + } + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript(name, target); } }; @@ -206,6 +215,14 @@ namespace MWScript { const ESM::RefId& name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + + if (!MWBase::Environment::get().getESMStore()->get().search(name)) + { + runtime.getContext().report( + "Failed to stop global script '" + name.getRefIdString() + "': script record not found"); + return; + } + MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript(name); } }; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index bad2181a06..d3c9d73c7c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -160,31 +161,32 @@ namespace template void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { - if (!collection.mList.empty()) + // references + for (const MWWorld::LiveCellRef& liveCellRef : collection.mList) { - // references - for (typename MWWorld::CellRefList::List::const_iterator iter(collection.mList.begin()); - iter != collection.mList.end(); ++iter) + if (ESM::isESM4Rec(T::sRecordId)) { - if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) - { - // Reference that came from a content file and has not been changed -> ignore - continue; - } - if (iter->mData.getCount() == 0 && !iter->mRef.hasContentFile()) - { - // Deleted reference that did not come from a content file -> ignore - continue; - } - using StateType = typename RecordToState::StateType; - StateType state; - iter->save(state); - - // recordId currently unused - writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); - - state.save(writer); + // TODO: Implement loading/saving of REFR4 and ACHR4 with ESM3 reader/writer. + continue; } + if (!liveCellRef.mData.hasChanged() && !liveCellRef.mRef.hasChanged() && liveCellRef.mRef.hasContentFile()) + { + // Reference that came from a content file and has not been changed -> ignore + continue; + } + if (liveCellRef.mData.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) + { + // Deleted reference that did not come from a content file -> ignore + continue; + } + using StateType = typename RecordToState::StateType; + StateType state; + liveCellRef.save(state); + + // recordId currently unused + writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); + + state.save(writer); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 13409ab169..debd80a97b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -73,6 +73,7 @@ namespace ESM4 struct Flora; struct Ingredient; struct MiscItem; + struct MovableStatic; struct Terminal; struct Tree; struct Weapon; @@ -95,8 +96,9 @@ namespace MWWorld CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList>; + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList>; /// \brief Mutable state of a cell class CellStore diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f9b53cf21f..12fbc0d4df 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -84,7 +84,8 @@ namespace } std::vector getNPCsToReplace(const MWWorld::Store& factions, - const MWWorld::Store& classes, const std::unordered_map& npcs) + const MWWorld::Store& classes, const MWWorld::Store& scripts, + const std::unordered_map& npcs) { // Cache first class from store - we will use it if current class is not found const ESM::RefId& defaultCls = getDefaultClass(classes); @@ -122,6 +123,14 @@ namespace changed = true; } + if (!npc.mScript.empty() && !scripts.search(npc.mScript)) + { + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script " + << npc.mScript << ", ignoring it."; + npc.mScript = ESM::RefId(); + changed = true; + } + if (changed) npcsToReplace.push_back(npc); } @@ -138,9 +147,9 @@ namespace { if (!item.mScript.empty() && !scripts.search(item.mScript)) { + Log(Debug::Verbose) << MapT::mapped_type::getRecordType() << ' ' << id << " (" << item.mName + << ") has nonexistent script " << item.mScript << ", ignoring it."; item.mScript = ESM::RefId(); - Log(Debug::Verbose) << "Item " << id << " (" << item.mName << ") has nonexistent script " - << item.mScript << ", ignoring it."; } } } @@ -292,6 +301,7 @@ namespace MWWorld case ESM::REC_LVLC4: case ESM::REC_LVLN4: case ESM::REC_MISC4: + case ESM::REC_MSTT4: case ESM::REC_NPC_4: case ESM::REC_STAT4: case ESM::REC_TERM4: @@ -517,8 +527,8 @@ namespace MWWorld void ESMStore::validate() { auto& npcs = getWritable(); - std::vector npcsToReplace - = getNPCsToReplace(getWritable(), getWritable(), npcs.mStatic); + std::vector npcsToReplace = getNPCsToReplace( + getWritable(), getWritable(), getWritable(), npcs.mStatic); for (const ESM::NPC& npc : npcsToReplace) { @@ -526,6 +536,8 @@ namespace MWWorld npcs.insertStatic(npc); } + removeMissingScripts(getWritable(), getWritable().mStatic); + // Validate spell effects for invalid arguments std::vector spellsToReplace; auto& spells = getWritable(); @@ -605,8 +617,8 @@ namespace MWWorld auto& npcs = getWritable(); auto& scripts = getWritable(); - std::vector npcsToReplace - = getNPCsToReplace(getWritable(), getWritable(), npcs.mDynamic); + std::vector npcsToReplace = getNPCsToReplace( + getWritable(), getWritable(), getWritable(), npcs.mDynamic); for (const ESM::NPC& npc : npcsToReplace) npcs.insert(npc); @@ -614,6 +626,7 @@ namespace MWWorld removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); + removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); removeMissingObjects(getWritable()); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 821ca6f488..ceba4a26ef 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -92,11 +92,13 @@ namespace ESM4 struct HeadPart; struct Ingredient; struct Land; + struct LandTexture; struct LevelledCreature; struct LevelledItem; struct LevelledNpc; struct Light; struct MiscItem; + struct MovableStatic; struct Npc; struct Outfit; struct Potion; @@ -139,10 +141,10 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store>; + Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 22f6857a24..e42675c66e 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1363,11 +1363,13 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 36bd74d217..b2fc46f358 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -87,6 +87,8 @@ add_component_dir (settings settingvalue shadermanager values + screenshotsettings + windowmode ) add_component_dir (bsa @@ -336,7 +338,15 @@ add_component_dir (fontloader ) add_component_dir (sdlutil - gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager sdlmappings + events + gl4es_init + imagetosurface + sdlcursormanager + sdlgraphicswindow + sdlinputwrapper + sdlmappings + sdlvideowrapper + vsyncmode ) add_component_dir (version diff --git a/components/esm/records.hpp b/components/esm/records.hpp index ccd03a05b9..4473e0290f 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -62,10 +62,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a5f1032c69..9945a8c40e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2708,7 +2708,8 @@ namespace NifOsg stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); } if (shaderprop->softEffect()) - SceneUtil::setupSoftEffect(*node, shaderprop->mFalloffDepth, true); + SceneUtil::setupSoftEffect( + *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); break; } default: diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index d8d3e871dc..720e032a61 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -15,7 +15,7 @@ namespace SceneUtil { - void setupSoftEffect(osg::Node& node, float size, bool falloff) + void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth) { static const osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); @@ -23,6 +23,7 @@ namespace SceneUtil stateset->addUniform(new osg::Uniform("particleSize", size)); stateset->addUniform(new osg::Uniform("particleFade", falloff)); + stateset->addUniform(new osg::Uniform("softFalloffDepth", falloffDepth)); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); @@ -35,6 +36,8 @@ namespace SceneUtil std::string source; + constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases + if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) { YAML::Node root = YAML::Load(source); @@ -47,8 +50,9 @@ namespace SceneUtil { auto size = it.second["size"].as(45.f); auto falloff = it.second["falloff"].as(false); + auto falloffDepth = it.second["falloffDepth"].as(defaultFalloffDepth); - setupSoftEffect(node, size, falloff); + setupSoftEffect(node, size, falloff, falloffDepth); } } @@ -56,7 +60,8 @@ namespace SceneUtil } else if (osgParticle::ParticleSystem* partsys = dynamic_cast(&node)) { - setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false); + setupSoftEffect( + node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false, defaultFalloffDepth); } traverse(node); diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index ddcb9b6c5c..9b1563b78a 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -15,7 +15,7 @@ namespace osg namespace SceneUtil { - void setupSoftEffect(osg::Node& node, float size, bool falloff); + void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); class ProcessExtraDataVisitor : public osg::NodeVisitor { diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 3c2efc3728..36947460df 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -14,22 +14,16 @@ namespace SDLUtil close(true); } - GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync) + GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode) : mWindow(nullptr) , mContext(nullptr) , mValid(false) , mRealized(false) , mOwnsWindow(false) + , mVSyncMode(vsyncMode) { _traits = traits; - if (vsync == 2) - mVSyncMode = VSyncMode::Adaptive; - else if (vsync == 1) - mVSyncMode = VSyncMode::Enabled; - else - mVSyncMode = VSyncMode::Disabled; - init(); if (GraphicsWindowSDL2::valid()) { diff --git a/components/sdlutil/sdlgraphicswindow.hpp b/components/sdlutil/sdlgraphicswindow.hpp index 3af6ef9276..238c872fb9 100644 --- a/components/sdlutil/sdlgraphicswindow.hpp +++ b/components/sdlutil/sdlgraphicswindow.hpp @@ -5,14 +5,10 @@ #include +#include "vsyncmode.hpp" + namespace SDLUtil { - enum VSyncMode - { - Disabled = 0, - Enabled = 1, - Adaptive = 2 - }; class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow { @@ -29,7 +25,7 @@ namespace SDLUtil virtual ~GraphicsWindowSDL2(); public: - GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync); + GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode); bool isSameKindAs(const Object* object) const override { diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 07cab33ad3..cc9706732e 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -1,7 +1,7 @@ #include "sdlinputwrapper.hpp" #include -#include +#include #include @@ -187,8 +187,10 @@ namespace SDLUtil { case SDL_DISPLAYEVENT_ORIENTATION: if (mSensorListener - && evt.display.display == (unsigned int)Settings::Manager::getInt("screen", "Video")) + && evt.display.display == static_cast(Settings::video().mScreen)) + { mSensorListener->displayOrientationChanged(); + } break; default: break; diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp index 3b612214b8..d93c16aace 100644 --- a/components/sdlutil/sdlvideowrapper.cpp +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -30,14 +30,8 @@ namespace SDLUtil SDL_SetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); } - void VideoWrapper::setSyncToVBlank(int mode) + void VideoWrapper::setSyncToVBlank(VSyncMode vsyncMode) { - VSyncMode vsyncMode = VSyncMode::Disabled; - if (mode == 1) - vsyncMode = VSyncMode::Enabled; - else if (mode == 2) - vsyncMode = VSyncMode::Adaptive; - osgViewer::Viewer::Windows windows; mViewer->getWindows(windows); mViewer->stopThreading(); @@ -47,7 +41,7 @@ namespace SDLUtil if (GraphicsWindowSDL2* sdl2win = dynamic_cast(win)) sdl2win->setSyncToVBlank(vsyncMode); else - win->setSyncToVBlank(static_cast(mode)); + win->setSyncToVBlank(vsyncMode != VSyncMode::Disabled); } mViewer->startThreading(); } diff --git a/components/sdlutil/sdlvideowrapper.hpp b/components/sdlutil/sdlvideowrapper.hpp index 9ed6ff1252..7977de40a7 100644 --- a/components/sdlutil/sdlvideowrapper.hpp +++ b/components/sdlutil/sdlvideowrapper.hpp @@ -5,6 +5,8 @@ #include +#include "vsyncmode.hpp" + struct SDL_Window; namespace osgViewer @@ -26,7 +28,7 @@ namespace SDLUtil VideoWrapper(SDL_Window* window, osg::ref_ptr viewer); ~VideoWrapper(); - void setSyncToVBlank(int mode); + void setSyncToVBlank(VSyncMode vsyncMode); void setGammaContrast(float gamma, float contrast); diff --git a/components/sdlutil/vsyncmode.hpp b/components/sdlutil/vsyncmode.hpp new file mode 100644 index 0000000000..5156addc40 --- /dev/null +++ b/components/sdlutil/vsyncmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SDLUTIL_VSYNCMODE_H +#define OPENMW_COMPONENTS_SDLUTIL_VSYNCMODE_H + +namespace SDLUtil +{ + enum VSyncMode + { + Disabled = 0, + Enabled = 1, + Adaptive = 2 + }; +} + +#endif diff --git a/components/settings/categories/stereoview.hpp b/components/settings/categories/stereoview.hpp index 1e9d35ace8..bcd0f57abc 100644 --- a/components/settings/categories/stereoview.hpp +++ b/components/settings/categories/stereoview.hpp @@ -31,14 +31,14 @@ namespace Settings makeClampSanitizerDouble(-1, 1) }; SettingValue mLeftEyeOrientationW{ mIndex, "Stereo View", "left eye orientation w", makeClampSanitizerDouble(-1, 1) }; - SettingValue mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; + SettingValue mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; SettingValue mRightEyeOffsetX{ mIndex, "Stereo View", "right eye offset x" }; SettingValue mRightEyeOffsetY{ mIndex, "Stereo View", "right eye offset y" }; SettingValue mRightEyeOffsetZ{ mIndex, "Stereo View", "right eye offset z" }; @@ -50,14 +50,14 @@ namespace Settings makeClampSanitizerDouble(-1, 1) }; SettingValue mRightEyeOrientationW{ mIndex, "Stereo View", "right eye orientation w", makeClampSanitizerDouble(-1, 1) }; - SettingValue mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; + SettingValue mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; }; } diff --git a/components/settings/categories/video.hpp b/components/settings/categories/video.hpp index fd6bb0018a..0e0f7a75bb 100644 --- a/components/settings/categories/video.hpp +++ b/components/settings/categories/video.hpp @@ -1,8 +1,12 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include +#include + +#include #include #include @@ -20,16 +24,16 @@ namespace Settings SettingValue mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) }; SettingValue mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) }; - SettingValue mWindowMode{ mIndex, "Video", "window mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mWindowMode{ mIndex, "Video", "window mode" }; SettingValue mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) }; SettingValue mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" }; SettingValue mWindowBorder{ mIndex, "Video", "window border" }; SettingValue mAntialiasing{ mIndex, "Video", "antialiasing", makeMaxSanitizerInt(0) }; - SettingValue mVsyncMode{ mIndex, "Video", "vsync mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mVsyncMode{ mIndex, "Video", "vsync mode" }; SettingValue mFramerateLimit{ mIndex, "Video", "framerate limit", makeMaxSanitizerFloat(0) }; SettingValue mContrast{ mIndex, "Video", "contrast", makeMaxStrictSanitizerFloat(0) }; SettingValue mGamma{ mIndex, "Video", "gamma", makeMaxStrictSanitizerFloat(0) }; - SettingValue mScreenshotType{ mIndex, "Video", "screenshot type" }; + SettingValue mScreenshotType{ mIndex, "Video", "screenshot type" }; }; } diff --git a/components/settings/screenshotsettings.hpp b/components/settings/screenshotsettings.hpp new file mode 100644 index 0000000000..6475ace005 --- /dev/null +++ b/components/settings/screenshotsettings.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H +#define OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H + +#include +#include + +namespace Settings +{ + enum class ScreenshotType + { + Regular, + Cylindrical, + Spherical, + Planet, + Cubemap, + }; + + struct ScreenshotSettings + { + ScreenshotType mType; + std::optional mWidth; + std::optional mHeight; + std::optional mCubeSize; + + auto operator<=>(const ScreenshotSettings& value) const = default; + }; +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 7b31bf6aad..c64e179efd 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -119,6 +119,23 @@ namespace Settings Log(Debug::Warning) << "Invalid HRTF mode value: " << static_cast(value) << ", fallback to auto (-1)"; return -1; } + + ScreenshotType parseScreenshotType(std::string_view value) + { + if (value == "regular") + return ScreenshotType::Regular; + if (value == "spherical") + return ScreenshotType::Spherical; + if (value == "cylindrical") + return ScreenshotType::Cylindrical; + if (value == "planet") + return ScreenshotType::Planet; + if (value == "cubemap") + return ScreenshotType::Cubemap; + + Log(Debug::Warning) << "Invalid screenshot type: " << value << ", fallback to regular"; + return ScreenshotType::Regular; + } } CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); @@ -501,6 +518,16 @@ namespace Settings setInt(setting, category, toInt(value)); } + void Manager::set(std::string_view setting, std::string_view category, WindowMode value) + { + setInt(setting, category, static_cast(value)); + } + + void Manager::set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value) + { + setInt(setting, category, static_cast(value)); + } + void Manager::recordInit(std::string_view setting, std::string_view category) { sInitialized.emplace(category, setting); @@ -547,4 +574,28 @@ namespace Settings Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'"; return SceneUtil::LightingMethod::PerObjectUniform; } + + ScreenshotSettings parseScreenshotSettings(std::string_view value) + { + std::vector settingArgs; + Misc::StringUtils::split(value, settingArgs); + + ScreenshotSettings result; + + if (settingArgs.size() > 0) + result.mType = parseScreenshotType(settingArgs[0]); + else + result.mType = ScreenshotType::Regular; + + if (settingArgs.size() > 1) + result.mWidth = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); + + if (settingArgs.size() > 2) + result.mHeight = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); + + if (settingArgs.size() > 3) + result.mCubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); + + return result; + } } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index a5b75fd445..c061755bc1 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -5,9 +5,12 @@ #include "gyroscopeaxis.hpp" #include "hrtfmode.hpp" #include "navmeshrendermode.hpp" +#include "screenshotsettings.hpp" +#include "windowmode.hpp" #include #include +#include #include #include @@ -27,13 +30,6 @@ namespace Files namespace Settings { - enum class WindowMode - { - Fullscreen = 0, - WindowedFullscreen, - Windowed - }; - /// /// \brief Settings management (can change during runtime) /// @@ -114,6 +110,8 @@ namespace Settings static void set(std::string_view setting, std::string_view category, const MyGUI::Colour& value); static void set(std::string_view setting, std::string_view category, SceneUtil::LightingMethod value); static void set(std::string_view setting, std::string_view category, HrtfMode value); + static void set(std::string_view setting, std::string_view category, WindowMode value); + static void set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value); private: static std::set> sInitialized; @@ -239,6 +237,32 @@ namespace Settings return HrtfMode::Enable; return HrtfMode::Disable; } + + template <> + inline WindowMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0 || 2 < value) + return WindowMode::Fullscreen; + return static_cast(value); + } + + template <> + inline SDLUtil::VSyncMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0 || 2 < value) + return SDLUtil::VSyncMode::Disabled; + return static_cast(value); + } + + ScreenshotSettings parseScreenshotSettings(std::string_view value); + + template <> + inline ScreenshotSettings Manager::getImpl(std::string_view setting, std::string_view category) + { + return parseScreenshotSettings(getString(setting, category)); + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 7b49c9bda2..2f239a4ebd 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -42,6 +42,9 @@ namespace Settings NavMeshRenderMode, LightingMethod, HrtfMode, + WindowMode, + VSyncMode, + ScreenshotSettings, }; template @@ -161,6 +164,24 @@ namespace Settings return SettingValueType::HrtfMode; } + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::WindowMode; + } + + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::VSyncMode; + } + + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::ScreenshotSettings; + } + inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) @@ -203,6 +224,12 @@ namespace Settings return "lighting method"; case SettingValueType::HrtfMode: return "hrtf mode"; + case SettingValueType::WindowMode: + return "window mode"; + case SettingValueType::VSyncMode: + return "vsync mode"; + case SettingValueType::ScreenshotSettings: + return "screenshot settings"; } return "unsupported"; } @@ -361,6 +388,17 @@ namespace Settings } return stream; } + else if constexpr (std::is_same_v) + { + stream << "ScreenshotSettings{ .mType = " << static_cast(value.mValue.mType); + if (value.mValue.mWidth.has_value()) + stream << ", .mWidth = " << *value.mValue.mWidth; + if (value.mValue.mHeight.has_value()) + stream << ", .mHeight = " << *value.mValue.mHeight; + if (value.mValue.mCubeSize.has_value()) + stream << ", .mCubeSize = " << *value.mValue.mCubeSize; + return stream << " }"; + } else return stream << value.mValue; } diff --git a/components/settings/windowmode.hpp b/components/settings/windowmode.hpp new file mode 100644 index 0000000000..96a81059f4 --- /dev/null +++ b/components/settings/windowmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_WINDOWMODE_H +#define OPENMW_COMPONENTS_SETTINGS_WINDOWMODE_H + +namespace Settings +{ + enum class WindowMode + { + Fullscreen = 0, + WindowedFullscreen = 1, + Windowed = 2, + }; +} + +#endif diff --git a/components/stereo/frustum.cpp b/components/stereo/frustum.cpp index cf8a7e8c30..a35beba7dd 100644 --- a/components/stereo/frustum.cpp +++ b/components/stereo/frustum.cpp @@ -85,7 +85,7 @@ namespace Stereo } } - StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) + StereoFrustumManager::StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera) : mCamera(camera) , mShadowTechnique(nullptr) , mShadowFrustumCallback(nullptr) @@ -95,7 +95,7 @@ namespace Stereo mMultiviewFrustumCallback = std::make_unique(this, camera); } - if (Settings::Manager::getBool("shared shadow maps", "Stereo")) + if (sharedShadowMaps) { mShadowFrustumCallback = new ShadowFrustumCallback(this); auto* renderer = static_cast(mCamera->getRenderer()); diff --git a/components/stereo/frustum.hpp b/components/stereo/frustum.hpp index c3abdd87d2..35e3adf95a 100644 --- a/components/stereo/frustum.hpp +++ b/components/stereo/frustum.hpp @@ -45,7 +45,7 @@ namespace Stereo class StereoFrustumManager { public: - StereoFrustumManager(osg::Camera* camera); + StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera); ~StereoFrustumManager(); void update(std::array projections); diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp index 047a52747b..a2f6cfd626 100644 --- a/components/stereo/multiview.cpp +++ b/components/stereo/multiview.cpp @@ -124,12 +124,12 @@ namespace Stereo } } - void setVertexBufferHint(bool enableMultiview) + void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview) { if (getStereo() && enableMultiview) { auto* ds = osg::DisplaySettings::instance().get(); - if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo") + if (!allowDisplayListsForMultiview && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE) { // Note that this only works if this code is executed before realize() is called on the viewer. diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp index fa69afc7a1..a9d84eae85 100644 --- a/components/stereo/multiview.hpp +++ b/components/stereo/multiview.hpp @@ -37,7 +37,7 @@ namespace Stereo void configureExtensions(unsigned int contextID, bool enableMultiview); //! Sets the appropriate vertex buffer hint on OSG's display settings if needed - void setVertexBufferHint(bool enableMultiview); + void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview); //! Creates a Texture2D as a texture view into a Texture2DArray osg::ref_ptr createTextureView_Texture2DFromTexture2DArray( diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index bf82d52078..a2eea0fda2 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -114,13 +114,15 @@ namespace Stereo return *sInstance; } - Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo) + Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far) : mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mUpdateCallback(new StereoUpdateCallback(this)) , mMasterProjectionMatrix(osg::Matrixd::identity()) , mEyeResolutionOverriden(false) , mEyeResolutionOverride(0, 0) + , mNear(near) + , mFar(far) , mFrustumManager(nullptr) , mUpdateViewCallback(nullptr) { @@ -132,13 +134,13 @@ namespace Stereo Manager::~Manager() {} - void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview) + void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps) { auto ci = gc->getState()->getContextID(); configureExtensions(ci, enableMultiview); mMainCamera->addUpdateCallback(mUpdateCallback); - mFrustumManager = std::make_unique(mViewer->getCamera()); + mFrustumManager = std::make_unique(sharedShadowMaps, mViewer->getCamera()); if (getMultiview()) setupOVRMultiView2Technique(); @@ -273,7 +275,7 @@ namespace Stereo void Manager::updateStereoFramebuffer() { // VR-TODO: in VR, still need to have this framebuffer attached before the postprocessor is created - // auto samples = Settings::Manager::getInt("antialiasing", "Video"); + // auto samples = /*do not use Settings here*/; // auto eyeRes = eyeResolution(); // if (mMultiviewFramebuffer) @@ -289,20 +291,17 @@ namespace Stereo void Manager::update() { - const double near_ = Settings::camera().mNearClip; - const double far_ = Settings::camera().mViewingDistance; - if (mUpdateViewCallback) { mUpdateViewCallback->updateView(mView[0], mView[1]); mViewOffsetMatrix[0] = mView[0].viewMatrix(true); mViewOffsetMatrix[1] = mView[1].viewMatrix(true); - mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); - mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); + mProjectionMatrix[0] = mView[0].perspectiveMatrix(mNear, mFar, false); + mProjectionMatrix[1] = mView[1].perspectiveMatrix(mNear, mFar, false); if (SceneUtil::AutoDepth::isReversed()) { - mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); - mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); + mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(mNear, mFar, true); + mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(mNear, mFar, true); } View masterView; @@ -310,7 +309,7 @@ namespace Stereo masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); - auto projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); + auto projectionMatrix = masterView.perspectiveMatrix(mNear, mFar, false); mMainCamera->setProjectionMatrix(projectionMatrix); } else @@ -394,60 +393,30 @@ namespace Stereo right = mRight; } - InitializeStereoOperation::InitializeStereoOperation() + InitializeStereoOperation::InitializeStereoOperation(const Settings& settings) : GraphicsOperation("InitializeStereoOperation", false) + , mMultiview(settings.mMultiview) + , mSharedShadowMaps(settings.mSharedShadowMaps) + , mCustomView(settings.mCustomView) + , mEyeResolution(settings.mEyeResolution) { // Ideally, this would have belonged to the operator(). But the vertex buffer // hint has to be set before realize is called on the osg viewer, and so has to // be done here instead. - Stereo::setVertexBufferHint(Settings::Manager::getBool("multiview", "Stereo")); + Stereo::setVertexBufferHint(settings.mMultiview, settings.mAllowDisplayListsForMultiview); } void InitializeStereoOperation::operator()(osg::GraphicsContext* graphicsContext) { auto& sm = Stereo::Manager::instance(); - if (Settings::Manager::getBool("use custom view", "Stereo")) - { - Stereo::View left; - Stereo::View right; + if (mCustomView.has_value()) + sm.setUpdateViewCallback( + std::make_shared(mCustomView->mLeft, mCustomView->mRight)); - left.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); - left.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); - left.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View"); - left.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View"); - left.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View"); - left.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View"); - left.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View"); - left.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View"); - left.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View"); - left.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View"); - left.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View"); + if (mEyeResolution.has_value()) + sm.overrideEyeResolution(*mEyeResolution); - right.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); - right.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View"); - right.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View"); - right.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View"); - right.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View"); - right.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View"); - right.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View"); - right.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View"); - right.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View"); - right.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View"); - right.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View"); - - auto customViewCallback = std::make_shared(left, right); - sm.setUpdateViewCallback(customViewCallback); - } - - if (Settings::Manager::getBool("use custom eye resolution", "Stereo")) - { - osg::Vec2i eyeResolution = osg::Vec2i(); - eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View"); - eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View"); - sm.overrideEyeResolution(eyeResolution); - } - - sm.initializeStereo(graphicsContext, Settings::Manager::getBool("multiview", "Stereo")); + sm.initializeStereo(graphicsContext, mMultiview, mSharedShadowMaps); } } diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 6f4e971718..8ed88da550 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -76,15 +76,23 @@ namespace Stereo //! @Param viewer the osg viewer whose stereo should be managed. //! @Param enableStereo whether or not stereo should be enabled. //! @Param enableMultiview whether or not to make use of the GL_OVR_Multiview extension, if supported. - Manager(osgViewer::Viewer* viewer, bool enableStereo); + //! @Param near defines distance to near camera clipping plane from view point. + //! @Param far defines distance to far camera clipping plane from view point. + explicit Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far); ~Manager(); //! Called during update traversal void update(); + void updateSettings(double near, double far) + { + mNear = near; + mFar = far; + } + //! Initializes all details of stereo if applicable. If the constructor was called with enableMultiview=true, //! and the GL_OVR_Multiview extension is supported, Stereo::getMultiview() will return true after this call. - void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview); + void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps); //! Callback that updates stereo configuration during the update pass void setUpdateViewCallback(std::shared_ptr cb); @@ -138,6 +146,8 @@ namespace Stereo std::shared_ptr mMultiviewFramebuffer; bool mEyeResolutionOverriden; osg::Vec2i mEyeResolutionOverride; + double mNear; + double mFar; std::array mView; std::array mViewOffsetMatrix; @@ -153,13 +163,34 @@ namespace Stereo osg::ref_ptr mIdentifierRight = new Identifier(); }; + struct CustomView + { + Stereo::View mLeft; + Stereo::View mRight; + }; + + struct Settings + { + bool mMultiview; + bool mAllowDisplayListsForMultiview; + bool mSharedShadowMaps; + std::optional mCustomView; + std::optional mEyeResolution; + }; + //! Performs stereo-specific initialization operations. class InitializeStereoOperation final : public osg::GraphicsOperation { public: - InitializeStereoOperation(); + explicit InitializeStereoOperation(const Settings& settings); void operator()(osg::GraphicsContext* graphicsContext) override; + + private: + bool mMultiview; + bool mSharedShadowMaps; + std::optional mCustomView; + std::optional mEyeResolution; }; } diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 340ed5ffed..5ea711953d 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -31,13 +31,15 @@ This setting can either be activated in the OpenMW launcher or changed in `setti Variables. -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| Name | Description | Type | Default | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| Name | Description | Type | Default | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloffDepth | The units at which geometry starts to fade. | float | 300 | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ Example usage. @@ -48,6 +50,7 @@ Example usage. "soft_effect" : { "size": 250, "falloff" : false, + "falloffDepth": 5, } } } diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 4cb5ba1842..801cf63d5b 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -194,3 +194,12 @@ Gamma is an exponent that makes colors brighter if greater than 1.0 and darker i This setting can be changed in the Detail tab of the Video panel of the Options menu. It has been reported to not work on some Linux systems, and therefore the in-game setting in the Options menu has been disabled on Linux systems. + +screenshot type +--------------- + +:Type: screenshot settings +:Default: regular + +Type of screenshot to take (regular, cylindrical, spherical, planet or cubemap), optionally followed by +screenshot width, height and cubemap resolution in pixels (e.g. spherical 1600 1000 1200). diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index c65a7323cb..69ce5fbaf2 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -919,13 +919,26 @@ -- @function [parent=#Player] getCrimeLevel -- @param openmw.core#GameObject player -- @return #number - + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished -- @param openmw.core#GameObject player -- @return #boolean +--- +-- Whether teleportation for this player is enabled. +-- @function [parent=#Player] isTeleportingEnabled +-- @param openmw.core#GameObject player +-- @param #boolean player +-- @return #boolean + +--- +-- Enables or disables teleportation for this player. +-- @function [parent=#Player] setTeleportingEnabled +-- @param openmw.core#GameObject player +-- @param #boolean state True to enable teleporting, false to disable. + --- -- Returns a list containing quests @{#PlayerQuest} for the specified player, indexed by quest ID. -- @function [parent=#Player] quests @@ -1868,6 +1881,9 @@ --- Functions for @{#ESM4Miscellaneous} objects -- @field [parent=#types] #ESM4Miscellaneous ESM4Miscellaneous +--- Functions for @{#ESM4MovableStatic} objects +-- @field [parent=#types] #ESM4MovableStatic ESM4MovableStatic + --- Functions for @{#ESM4Potion} objects -- @field [parent=#types] #ESM4Potion ESM4Potion diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ece773a677..bbc6b4d1c8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -640,7 +640,7 @@ contrast = 1.0 # Video gamma setting. (>0.0). No effect in Linux. gamma = 1.0 -# Type of screenshot to take (regular, cylindrical, spherical or planet), optionally followed by +# Type of screenshot to take (regular, cylindrical, spherical, planet or cubemap), optionally followed by # screenshot width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200) screenshot type = regular diff --git a/files/shaders/compatibility/bs/nolighting.frag b/files/shaders/compatibility/bs/nolighting.frag index c5393d4732..c9e3ca4e13 100644 --- a/files/shaders/compatibility/bs/nolighting.frag +++ b/files/shaders/compatibility/bs/nolighting.frag @@ -38,6 +38,7 @@ uniform float alphaRef; uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; +uniform float softFalloffDepth; #endif void main() @@ -71,7 +72,8 @@ void main() far, texture2D(opaqueDepthTex, screenCoords).x, particleSize, - particleFade + particleFade, + softFalloffDepth ); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 4ef463cb34..4caf6c97e2 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -98,6 +98,7 @@ varying vec3 passNormal; uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; +uniform float softFalloffDepth; #endif #if @particleOcclusion @@ -256,7 +257,8 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal); far, texture2D(opaqueDepthTex, screenCoords).x, particleSize, - particleFade + particleFade, + softFalloffDepth ); #endif diff --git a/files/shaders/lib/particle/soft.glsl b/files/shaders/lib/particle/soft.glsl index c415d8402b..99336bc039 100644 --- a/files/shaders/lib/particle/soft.glsl +++ b/files/shaders/lib/particle/soft.glsl @@ -19,7 +19,8 @@ float calcSoftParticleFade( float far, float depth, float size, - bool fade + bool fade, + float softFalloffDepth ) { float euclidianDepth = length(viewPos); @@ -32,13 +33,12 @@ float calcSoftParticleFade( float falloff = size * falloffMultiplier; float delta = particleDepth - sceneDepth; - const float nearMult = 300.0; float viewBias = 1.0; if (fade) { float VdotN = dot(viewDir, viewNormal); - viewBias = abs(VdotN) * quickstep(euclidianDepth / nearMult) * (1.0 - pow(1.0 + VdotN, 1.3)); + viewBias = abs(VdotN) * quickstep(euclidianDepth / softFalloffDepth) * (1.0 - pow(1.0 - abs(VdotN), 1.3)); } const float shift = 0.845;