1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-06 09:11:32 +00:00

Merge branch 'master' of gitlab.com:openmw/openmw into lua_class_data

This commit is contained in:
Zackhasacat 2023-10-31 22:23:45 -05:00
commit 371eeddf85
89 changed files with 899 additions and 493 deletions

View file

@ -140,6 +140,7 @@ Programmers
Lordrea Lordrea
Łukasz Gołębiewski (lukago) Łukasz Gołębiewski (lukago)
Lukasz Gromanowski (lgro) Lukasz Gromanowski (lgro)
Mads Sandvei (Foal)
Marc Bouvier (CramitDeFrog) Marc Bouvier (CramitDeFrog)
Marcin Hulist (Gohan) Marcin Hulist (Gohan)
Mark Siewert (mark76) Mark Siewert (mark76)

View file

@ -8,7 +8,9 @@
Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game 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 #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 #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 #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 #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses 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 #6807: Ultimate Galleon is not working properly
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands 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 #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 #6939: OpenMW-CS: ID columns are too short
Bug #6949: Sun Damage effect doesn't work in quasi exteriors Bug #6949: Sun Damage effect doesn't work in quasi exteriors
Bug #6964: Nerasa Dralor Won't Follow Bug #6964: Nerasa Dralor Won't Follow
@ -58,6 +61,7 @@
Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7134: Saves with an invalid last generated RefNum can be loaded
Bug #7163: Myar Aranath: Wheat breaks the GUI Bug #7163: Myar Aranath: Wheat breaks the GUI
Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty 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 #7229: Error marker loading failure is not handled
Bug #7243: Supporting loading external files from VFS from esm files 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 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 #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 #7459: Icons get stacked on the cursor when picking up multiple items simultaneously
Bug #7472: Crash when enchanting last projectiles 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 #7505: Distant terrain does not support sample size greater than cell size
Bug #7553: Faction reaction loading is incorrect Bug #7553: Faction reaction loading is incorrect
Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading 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 #7609: ForceGreeting should not open dialogue for werewolves
Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7611: Beast races' idle animations slide after turning or jumping in place
Bug #7630: Charm can be cast on creatures 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 #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION Feature #6149: Dehardcode Lua API_REVISION
@ -109,7 +120,9 @@
Feature #7546: Start the game on Fredas Feature #7546: Start the game on Fredas
Feature #7568: Uninterruptable scripted music Feature #7568: Uninterruptable scripted music
Feature #7618: Show the player character's health in the save details 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 #7634: Support NiParticleBomb
Feature #7652: Sort inactive post processing shaders list properly
Task #5896: Do not use deprecated MyGUI properties Task #5896: Do not use deprecated MyGUI properties
Task #7113: Move from std::atoi to std::from_char Task #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector Task #7117: Replace boost::scoped_array with std::vector

View file

@ -40,8 +40,35 @@ namespace
{ {
void contentSubdirs(const QString& path, QStringList& dirs) void contentSubdirs(const QString& path, QStringList& dirs)
{ {
QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.ba2", "*.omwscripts" }; static const QStringList fileFilter{
QStringList dirFilter{ "bookart", "icons", "meshes", "music", "sound", "textures" }; "*.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); QDir currentDir(path);
if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() 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); 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) void Launcher::DataFilesPage::addSubdirectories(bool append)
{ {
int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow();
@ -606,22 +621,30 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
if (selectedRow == -1) if (selectedRow == -1)
return; return;
const auto rootDir = selectDirectory(); QString rootPath = QFileDialog::getExistingDirectory(
if (rootDir.isEmpty()) this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly);
if (rootPath.isEmpty())
return; return;
QStringList subdirs; const QDir rootDir(rootPath);
contentSubdirs(rootDir, subdirs); 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 // We didn't find anything else that looks like a content directory
if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) // Automatically add the directory selected by user
{ if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty())
ui.directoryListWidget->addItem(rootDir); return;
mNewDataDirs.push_back(rootDir); ui.directoryListWidget->addItem(rootPath);
refreshDataFilesView(); mNewDataDirs.push_back(rootPath);
} refreshDataFilesView();
return; return;
} }

View file

@ -131,7 +131,6 @@ namespace Launcher
void reloadCells(QStringList selectedFiles); void reloadCells(QStringList selectedFiles);
void refreshDataFilesView(); void refreshDataFilesView();
void updateNavMeshProgress(int minDataSize); void updateNavMeshProgress(int minDataSize);
QString selectDirectory();
/** /**
* Returns the file paths of all selected content files * Returns the file paths of all selected content files

View file

@ -96,32 +96,29 @@ bool Launcher::GraphicsPage::loadSettings()
// Visuals // Visuals
int vsync = Settings::Manager::getInt("vsync mode", "Video"); const int vsync = Settings::video().mVsyncMode;
if (vsync < 0 || vsync > 2)
vsync = 0;
vSyncComboBox->setCurrentIndex(vsync); vSyncComboBox->setCurrentIndex(vsync);
size_t windowMode = static_cast<size_t>(Settings::Manager::getInt("window mode", "Video")); const Settings::WindowMode windowMode = Settings::video().mWindowMode;
if (windowMode > static_cast<size_t>(Settings::WindowMode::Windowed))
windowMode = 0;
windowModeComboBox->setCurrentIndex(windowMode);
slotFullScreenChanged(windowMode);
if (Settings::Manager::getBool("window border", "Video")) windowModeComboBox->setCurrentIndex(static_cast<int>(windowMode));
handleWindowModeChange(windowMode);
if (Settings::video().mWindowBorder)
windowBorderCheckBox->setCheckState(Qt::Checked); windowBorderCheckBox->setCheckState(Qt::Checked);
// aaValue is the actual value (0, 1, 2, 4, 8, 16) // 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. // 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) if (aaIndex != -1)
antiAliasingComboBox->setCurrentIndex(aaIndex); antiAliasingComboBox->setCurrentIndex(aaIndex);
int width = Settings::Manager::getInt("resolution x", "Video"); const int width = Settings::video().mResolutionX;
int height = Settings::Manager::getInt("resolution y", "Video"); const int height = Settings::video().mResolutionY;
QString resolution = QString::number(width) + QString(" x ") + QString::number(height); 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); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
@ -137,7 +134,7 @@ bool Launcher::GraphicsPage::loadSettings()
customHeightSpinBox->setValue(height); customHeightSpinBox->setValue(height);
} }
float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video"); const float fpsLimit = Settings::video().mFramerateLimit;
if (fpsLimit != 0) if (fpsLimit != 0)
{ {
framerateLimitCheckBox->setCheckState(Qt::Checked); framerateLimitCheckBox->setCheckState(Qt::Checked);
@ -198,23 +195,10 @@ void Launcher::GraphicsPage::saveSettings()
{ {
// Visuals // Visuals
// Ensure we only set the new settings if they changed. This is to avoid cluttering the Settings::video().mVsyncMode.set(static_cast<SDLUtil::VSyncMode>(vSyncComboBox->currentIndex()));
// user settings file (which by definition should only contain settings the user has touched) Settings::video().mWindowMode.set(static_cast<Settings::WindowMode>(windowModeComboBox->currentIndex()));
int cVSync = vSyncComboBox->currentIndex(); Settings::video().mWindowBorder.set(windowBorderCheckBox->checkState() == Qt::Checked);
if (cVSync != Settings::Manager::getInt("vsync mode", "Video")) Settings::video().mAntialiasing.set(antiAliasingComboBox->currentText().toInt());
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);
int cWidth = 0; int cWidth = 0;
int cHeight = 0; int cHeight = 0;
@ -234,25 +218,17 @@ void Launcher::GraphicsPage::saveSettings()
cHeight = customHeightSpinBox->value(); cHeight = customHeightSpinBox->value();
} }
if (cWidth != Settings::Manager::getInt("resolution x", "Video")) Settings::video().mResolutionX.set(cWidth);
Settings::Manager::setInt("resolution x", "Video", cWidth); Settings::video().mResolutionY.set(cHeight);
Settings::video().mScreen.set(screenComboBox->currentIndex());
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);
if (framerateLimitCheckBox->checkState() != Qt::Unchecked) if (framerateLimitCheckBox->checkState() != Qt::Unchecked)
{ {
float cFpsLimit = framerateLimitSpinBox->value(); Settings::video().mFramerateLimit.set(framerateLimitSpinBox->value());
if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video"))
Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit);
} }
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 // Lighting
@ -392,8 +368,12 @@ void Launcher::GraphicsPage::screenChanged(int screen)
void Launcher::GraphicsPage::slotFullScreenChanged(int mode) void Launcher::GraphicsPage::slotFullScreenChanged(int mode)
{ {
if (mode == static_cast<int>(Settings::WindowMode::Fullscreen) handleWindowModeChange(static_cast<Settings::WindowMode>(mode));
|| mode == static_cast<int>(Settings::WindowMode::WindowedFullscreen)) }
void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
{
if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen)
{ {
standardRadioButton->toggle(); standardRadioButton->toggle();
customRadioButton->setEnabled(false); customRadioButton->setEnabled(false);

View file

@ -3,7 +3,7 @@
#include "ui_graphicspage.h" #include "ui_graphicspage.h"
#include <components/settings/settings.hpp> #include <components/settings/windowmode.hpp>
namespace Files namespace Files
{ {
@ -40,6 +40,7 @@ namespace Launcher
static QRect getMaximumResolution(); static QRect getMaximumResolution();
bool setupSDL(); bool setupSDL();
void handleWindowModeChange(Settings::WindowMode state);
}; };
} }
#endif #endif

View file

@ -1,13 +1,5 @@
#include "maindialog.hpp" #include "maindialog.hpp"
#include <components/debug/debuglog.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/conversion.hpp>
#include <components/files/qtconversion.hpp>
#include <components/misc/helpviewer.hpp>
#include <components/misc/utf8qtextstream.hpp>
#include <components/version/version.hpp>
#include <QCloseEvent> #include <QCloseEvent>
#include <QDir> #include <QDir>
#include <QMessageBox> #include <QMessageBox>
@ -15,10 +7,15 @@
#include <QTime> #include <QTime>
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
#include <components/debug/debuglog.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/files/qtconfigpath.hpp> #include <components/files/qtconfigpath.hpp>
#include <components/files/qtconversion.hpp> #include <components/files/qtconversion.hpp>
#include <components/misc/helpviewer.hpp>
#include <components/misc/utf8qtextstream.hpp> #include <components/misc/utf8qtextstream.hpp>
#include <components/settings/settings.hpp>
#include <components/version/version.hpp>
#include "datafilespage.hpp" #include "datafilespage.hpp"
#include "graphicspage.hpp" #include "graphicspage.hpp"

View file

@ -313,6 +313,8 @@ bool OMW::Engine::frame(float frametime)
mLuaManager->reportStats(frameNumber, *stats); mLuaManager->reportStats(frameNumber, *stats);
} }
mStereoManager->updateSettings(Settings::camera().mNearClip, Settings::camera().mViewingDistance);
mViewer->eventTraversal(); mViewer->eventTraversal();
mViewer->updateTraversal(); mViewer->updateTraversal();
@ -452,14 +454,13 @@ void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame)
void OMW::Engine::createWindow() void OMW::Engine::createWindow()
{ {
int screen = Settings::Manager::getInt("screen", "Video"); const int screen = Settings::video().mScreen;
int width = Settings::Manager::getInt("resolution x", "Video"); const int width = Settings::video().mResolutionX;
int height = Settings::Manager::getInt("resolution y", "Video"); const int height = Settings::video().mResolutionY;
Settings::WindowMode windowMode const Settings::WindowMode windowMode = Settings::video().mWindowMode;
= static_cast<Settings::WindowMode>(Settings::Manager::getInt("window mode", "Video")); const bool windowBorder = Settings::video().mWindowBorder;
bool windowBorder = Settings::Manager::getBool("window border", "Video"); const SDLUtil::VSyncMode vsync = Settings::video().mVsyncMode;
int vsync = Settings::Manager::getInt("vsync mode", "Video"); unsigned antialiasing = static_cast<unsigned>(Settings::video().mAntialiasing);
unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video"));
int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); 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) if (!windowBorder)
flags |= SDL_WINDOW_BORDERLESS; flags |= SDL_WINDOW_BORDERLESS;
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, Settings::video().mMinimizeOnFocusLoss ? "1" : "0");
Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0");
checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8));
checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_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 " Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying "
<< antialiasing / 2; << antialiasing / 2;
antialiasing /= 2; antialiasing /= 2;
Settings::Manager::setInt("antialiasing", "Video", antialiasing); Settings::video().mAntialiasing.set(antialiasing);
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
continue; continue;
} }
@ -560,7 +560,7 @@ void OMW::Engine::createWindow()
SDL_DestroyWindow(mWindow); SDL_DestroyWindow(mWindow);
mWindow = nullptr; mWindow = nullptr;
antialiasing /= 2; antialiasing /= 2;
Settings::Manager::setInt("antialiasing", "Video", antialiasing); Settings::video().mAntialiasing.set(antialiasing);
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
continue; continue;
} }
@ -593,7 +593,63 @@ void OMW::Engine::createWindow()
realizeOperations->add(mSelectColorFormatOperation); realizeOperations->add(mSelectColorFormatOperation);
if (Stereo::getStereo()) 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(); mViewer->realize();
mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits();
@ -632,9 +688,9 @@ void OMW::Engine::prepareEngine()
mStateManager = std::make_unique<MWState::StateManager>(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mStateManager = std::make_unique<MWState::StateManager>(mCfgMgr.getUserDataPath() / "saves", mContentFiles);
mEnvironment.setStateManager(*mStateManager); mEnvironment.setStateManager(*mStateManager);
bool stereoEnabled const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo();
= Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); mStereoManager = std::make_unique<Stereo::Manager>(
mStereoManager = std::make_unique<Stereo::Manager>(mViewer, stereoEnabled); mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance);
osg::ref_ptr<osg::Group> rootNode(new osg::Group); osg::ref_ptr<osg::Group> rootNode(new osg::Group);
mViewer->setSceneData(rootNode); mViewer->setSceneData(rootNode);
@ -866,7 +922,7 @@ void OMW::Engine::go()
// Do not try to outsmart the OS thread scheduler (see bug #4785). // Do not try to outsmart the OS thread scheduler (see bug #4785).
mViewer->setUseConfigureAffinity(false); mViewer->setUseConfigureAffinity(false);
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); mEnvironment.setFrameRateLimit(Settings::video().mFramerateLimit);
prepareEngine(); prepareEngine();

View file

@ -14,6 +14,7 @@
#include <components/esm4/loadingr.hpp> #include <components/esm4/loadingr.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadmisc.hpp> #include <components/esm4/loadmisc.hpp>
#include <components/esm4/loadmstt.hpp>
#include <components/esm4/loadnpc.hpp> #include <components/esm4/loadnpc.hpp>
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>
#include <components/esm4/loadterm.hpp> #include <components/esm4/loadterm.hpp>
@ -85,6 +86,7 @@ namespace MWClass
ESM4Named<ESM4::Ingredient>::registerSelf(); ESM4Named<ESM4::Ingredient>::registerSelf();
ESM4Light::registerSelf(); ESM4Light::registerSelf();
ESM4Named<ESM4::MiscItem>::registerSelf(); ESM4Named<ESM4::MiscItem>::registerSelf();
ESM4Named<ESM4::MovableStatic>::registerSelf();
ESM4Npc::registerSelf(); ESM4Npc::registerSelf();
ESM4Named<ESM4::Potion>::registerSelf(); ESM4Named<ESM4::Potion>::registerSelf();
ESM4Static::registerSelf(); ESM4Static::registerSelf();

View file

@ -464,18 +464,20 @@ namespace MWClass
} }
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
const MWMechanics::AiSequence& aiSequence = stats.getAiSequence();
const bool isInCombat = aiSequence.isInCombat();
if (stats.isDead()) if (stats.isDead())
{ {
// by default user can loot friendly actors during death animation // 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<MWWorld::ActionOpen>(ptr); return std::make_unique<MWWorld::ActionOpen>(ptr);
// otherwise wait until death animation // otherwise wait until death animation
if (stats.isDeathAnimationFinished()) if (stats.isDeathAnimationFinished())
return std::make_unique<MWWorld::ActionOpen>(ptr); return std::make_unique<MWWorld::ActionOpen>(ptr);
} }
else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown())
return std::make_unique<MWWorld::ActionTalk>(ptr); return std::make_unique<MWWorld::ActionTalk>(ptr);
// Tribunal and some mod companions oddly enough must use open action as fallback // 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()) if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished())
return true; 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 MWGui::ToolTipInfo Creature::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const

View file

@ -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<Record>()->mBase->mFullName, count);
}
std::string_view getName(const MWWorld::ConstPtr& ptr) const override std::string_view getName(const MWWorld::ConstPtr& ptr) const override
{ {
return ptr.get<Record>()->mBase->mFullName; return ptr.get<Record>()->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(); }
}; };
} }

View file

@ -128,6 +128,11 @@ namespace MWGui
mLines.swap(lines); 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(); layoutWidgets();
} }

View file

@ -89,7 +89,7 @@ namespace
{ {
if (!Settings::map().mAllowZooming) if (!Settings::map().mAllowZooming)
return Constants::CellGridRadius; return Constants::CellGridRadius;
if (!Settings::Manager::getBool("distant terrain", "Terrain")) if (!Settings::terrain().mDistantTerrain)
return Constants::CellGridRadius; return Constants::CellGridRadius;
const int viewingDistanceInCells = Settings::camera().mViewingDistance / Constants::CellSizeInUnits; const int viewingDistanceInCells = Settings::camera().mViewingDistance / Constants::CellSizeInUnits;
return std::clamp( return std::clamp(

View file

@ -47,6 +47,8 @@ namespace MWGui
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor;
std::vector<std::tuple<std::string, int, MWWorld::Ptr>> items;
for (MWWorld::ContainerStoreIterator iter(store.begin(categories)); iter != store.end(); ++iter) for (MWWorld::ContainerStoreIterator iter(store.begin(categories)); iter != store.end(); ++iter)
{ {
if (iter->getClass().hasItemHealth(*iter)) if (iter->getClass().hasItemHealth(*iter))
@ -76,22 +78,31 @@ namespace MWGui
name += " - " + MyGUI::utility::toString(price) name += " - " + MyGUI::utility::toString(price)
+ MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>().find("sgp")->mValue.getString(); + MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>().find("sgp")->mValue.getString();
MyGUI::Button* button = mList->createWidget<MyGUI::Button>(price <= playerGold items.emplace_back(name, price, *iter);
? "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);
} }
} }
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<MyGUI::Button>(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 // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the
// scrollbar is hidden // scrollbar is hidden
mList->setVisibleVScroll(false); mList->setVisibleVScroll(false);

View file

@ -20,6 +20,7 @@
#include <components/fx/technique.hpp> #include <components/fx/technique.hpp>
#include <components/fx/widgets.hpp> #include <components/fx/widgets.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/utf8stream.hpp> #include <components/misc/utf8stream.hpp>
#include <components/widgets/box.hpp> #include <components/widgets/box.hpp>
@ -421,7 +422,12 @@ namespace MWGui
auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor();
std::vector<std::string> techniques;
for (const auto& [name, _] : processor->getTechniqueMap()) 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); auto technique = processor->loadTechnique(name);

View file

@ -49,7 +49,7 @@ namespace MWGui
= new SortFilterItemModel(std::make_unique<InventoryItemModel>(MWMechanics::getPlayer())); = new SortFilterItemModel(std::make_unique<InventoryItemModel>(MWMechanics::getPlayer()));
model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); model->setFilter(SortFilterItemModel::Filter_OnlyRepairable);
mRepairBox->setModel(model); mRepairBox->setModel(model);
mRepairBox->update();
// Reset scrollbars // Reset scrollbars
mRepairBox->resetScrollbars(); mRepairBox->resetScrollbars();
} }

View file

@ -258,7 +258,7 @@ namespace MWGui
, mKeyboardMode(true) , mKeyboardMode(true)
, mCurrentPage(-1) , mCurrentPage(-1)
{ {
bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); const bool terrain = Settings::terrain().mDistantTerrain;
const std::string_view widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; const std::string_view widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider";
MyGUI::Widget* unusedSlider; MyGUI::Widget* unusedSlider;
getWidget(unusedSlider, widgetName); getWidget(unusedSlider, widgetName);
@ -354,7 +354,7 @@ namespace MWGui
+= MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings);
// fill resolution list // fill resolution list
int screen = Settings::Manager::getInt("screen", "Video"); const int screen = Settings::video().mScreen;
int numDisplayModes = SDL_GetNumDisplayModes(screen); int numDisplayModes = SDL_GetNumDisplayModes(screen);
std::vector<std::pair<int, int>> resolutions; std::vector<std::pair<int, int>> resolutions;
for (int i = 0; i < numDisplayModes; i++) for (int i = 0; i < numDisplayModes; i++)
@ -396,8 +396,7 @@ namespace MWGui
updateMaxLightsComboBox(mMaxLights); updateMaxLightsComboBox(mMaxLights);
Settings::WindowMode windowMode const Settings::WindowMode windowMode = Settings::video().mWindowMode;
= static_cast<Settings::WindowMode>(Settings::Manager::getInt("window mode", "Video"));
mWindowBorderButton->setEnabled( mWindowBorderButton->setEnabled(
windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen);
@ -491,8 +490,8 @@ namespace MWGui
int resX, resY; int resX, resY;
parseResolution(resX, resY, resStr); parseResolution(resX, resY, resStr);
Settings::Manager::setInt("resolution x", "Video", resX); Settings::video().mResolutionX.set(resX);
Settings::Manager::setInt("resolution y", "Video", resY); Settings::video().mResolutionY.set(resY);
apply(); apply();
} }
@ -506,8 +505,8 @@ namespace MWGui
{ {
mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); mResolutionList->setIndexSelected(MyGUI::ITEM_NONE);
int currentX = Settings::Manager::getInt("resolution x", "Video"); const int currentX = Settings::video().mResolutionX;
int currentY = Settings::Manager::getInt("resolution y", "Video"); const int currentY = Settings::video().mResolutionY;
for (size_t i = 0; i < mResolutionList->getItemCount(); ++i) for (size_t i = 0; i < mResolutionList->getItemCount(); ++i)
{ {
@ -591,23 +590,22 @@ namespace MWGui
"#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); "#{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) if (pos == MyGUI::ITEM_NONE)
return; return;
int index = static_cast<int>(_sender->getIndexSelected()); Settings::video().mVsyncMode.set(static_cast<SDLUtil::VSyncMode>(sender->getIndexSelected()));
Settings::Manager::setInt("vsync mode", "Video", index);
apply(); apply();
} }
void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* sender, size_t pos)
{ {
if (pos == MyGUI::ITEM_NONE) if (pos == MyGUI::ITEM_NONE)
return; return;
int index = static_cast<int>(_sender->getIndexSelected()); const Settings::WindowMode windowMode = static_cast<Settings::WindowMode>(sender->getIndexSelected());
if (index == static_cast<size_t>(Settings::WindowMode::WindowedFullscreen)) if (windowMode == Settings::WindowMode::WindowedFullscreen)
{ {
mResolutionList->setEnabled(false); mResolutionList->setEnabled(false);
mWindowModeHint->setVisible(true); mWindowModeHint->setVisible(true);
@ -618,12 +616,12 @@ namespace MWGui
mWindowModeHint->setVisible(false); mWindowModeHint->setVisible(false);
} }
if (index == static_cast<size_t>(Settings::WindowMode::Windowed)) if (windowMode == Settings::WindowMode::Windowed)
mWindowBorderButton->setEnabled(true); mWindowBorderButton->setEnabled(true);
else else
mWindowBorderButton->setEnabled(false); mWindowBorderButton->setEnabled(false);
Settings::Manager::setInt("window mode", "Video", index); Settings::video().mWindowMode.set(windowMode);
apply(); apply();
} }
@ -849,14 +847,12 @@ namespace MWGui
void SettingsWindow::updateWindowModeSettings() void SettingsWindow::updateWindowModeSettings()
{ {
size_t index = static_cast<size_t>(Settings::Manager::getInt("window mode", "Video")); const Settings::WindowMode windowMode = Settings::video().mWindowMode;
const std::size_t windowModeIndex = static_cast<std::size_t>(windowMode);
if (index > static_cast<size_t>(Settings::WindowMode::Windowed)) mWindowModeList->setIndexSelected(windowModeIndex);
index = MyGUI::ITEM_NONE;
mWindowModeList->setIndexSelected(index); if (windowMode != Settings::WindowMode::Windowed && windowModeIndex != MyGUI::ITEM_NONE)
if (index != static_cast<size_t>(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE)
{ {
// check if this resolution is supported in fullscreen // check if this resolution is supported in fullscreen
if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE)
@ -864,8 +860,8 @@ namespace MWGui
const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected());
int resX, resY; int resX, resY;
parseResolution(resX, resY, resStr); parseResolution(resX, resY, resStr);
Settings::Manager::setInt("resolution x", "Video", resX); Settings::video().mResolutionX.set(resX);
Settings::Manager::setInt("resolution y", "Video", resY); Settings::video().mResolutionY.set(resY);
} }
bool supported = false; bool supported = false;
@ -882,8 +878,7 @@ namespace MWGui
fallbackY = resY; fallbackY = resY;
} }
if (resX == Settings::Manager::getInt("resolution x", "Video") if (resX == Settings::video().mResolutionX && resY == Settings::video().mResolutionY)
&& resY == Settings::Manager::getInt("resolution y", "Video"))
supported = true; supported = true;
} }
@ -891,26 +886,21 @@ namespace MWGui
{ {
if (fallbackX != 0 && fallbackY != 0) if (fallbackX != 0 && fallbackY != 0)
{ {
Settings::Manager::setInt("resolution x", "Video", fallbackX); Settings::video().mResolutionX.set(fallbackX);
Settings::Manager::setInt("resolution y", "Video", fallbackY); Settings::video().mResolutionY.set(fallbackY);
} }
} }
mWindowBorderButton->setEnabled(false); mWindowBorderButton->setEnabled(false);
} }
if (index == static_cast<size_t>(Settings::WindowMode::WindowedFullscreen)) if (windowMode == Settings::WindowMode::WindowedFullscreen)
mResolutionList->setEnabled(false); mResolutionList->setEnabled(false);
} }
void SettingsWindow::updateVSyncModeSettings() void SettingsWindow::updateVSyncModeSettings()
{ {
int index = static_cast<size_t>(Settings::Manager::getInt("vsync mode", "Video")); mVSyncModeList->setIndexSelected(static_cast<size_t>(Settings::video().mVsyncMode));
if (index < 0 || index > 2)
index = 0;
mVSyncModeList->setIndexSelected(index);
} }
void SettingsWindow::layoutControlsBox() void SettingsWindow::layoutControlsBox()

View file

@ -294,8 +294,7 @@ namespace MWGui
+= MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested);
mVideoWrapper = std::make_unique<SDLUtil::VideoWrapper>(window, viewer); mVideoWrapper = std::make_unique<SDLUtil::VideoWrapper>(window, viewer);
mVideoWrapper->setGammaContrast( mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast);
Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video"));
if (useShaders) if (useShaders)
mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager()); mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager());
@ -1157,25 +1156,22 @@ namespace MWGui
changeRes = true; changeRes = true;
else if (setting.first == "Video" && setting.second == "vsync mode") 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")) else if (setting.first == "Video" && (setting.second == "gamma" || setting.second == "contrast"))
mVideoWrapper->setGammaContrast( mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast);
Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video"));
} }
if (changeRes) if (changeRes)
{ {
mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), mVideoWrapper->setVideoMode(Settings::video().mResolutionX, Settings::video().mResolutionY,
Settings::Manager::getInt("resolution y", "Video"), Settings::video().mWindowMode, Settings::video().mWindowBorder);
static_cast<Settings::WindowMode>(Settings::Manager::getInt("window mode", "Video")),
Settings::Manager::getBool("window border", "Video"));
} }
} }
void WindowManager::windowResized(int x, int y) void WindowManager::windowResized(int x, int y)
{ {
Settings::Manager::setInt("resolution x", "Video", x); Settings::video().mResolutionX.set(x);
Settings::Manager::setInt("resolution y", "Video", y); Settings::video().mResolutionY.set(y);
// We only want to process changes to window-size related settings. // We only want to process changes to window-size related settings.
Settings::CategorySettingVector filter = { { "Video", "resolution x" }, { "Video", "resolution y" } }; Settings::CategorySettingVector filter = { { "Video", "resolution x" }, { "Video", "resolution y" } };

View file

@ -4,7 +4,7 @@
#include <SDL_keyboard.h> #include <SDL_keyboard.h>
#include <components/settings/settings.hpp> #include <components/settings/values.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
@ -170,10 +170,9 @@ namespace MWInput
void ActionManager::screenshot() void ActionManager::screenshot()
{ {
const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType;
bool regularScreenshot = settingStr.empty() || settingStr == "regular";
if (regularScreenshot) if (settings.mType == Settings::ScreenshotType::Regular)
{ {
mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->setFramesToCapture(1);
mScreenCaptureHandler->captureNextFrame(*mViewer); mScreenCaptureHandler->captureNextFrame(*mViewer);

View file

@ -42,8 +42,7 @@ namespace MWInput
float angle = 0; float angle = 0;
SDL_DisplayOrientation currentOrientation SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::video().mScreen);
= SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video"));
switch (currentOrientation) switch (currentOrientation)
{ {
case SDL_ORIENTATION_UNKNOWN: case SDL_ORIENTATION_UNKNOWN:

View file

@ -94,8 +94,8 @@ namespace MWLua
api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; };
api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f {
double width = Settings::Manager::getInt("resolution x", "Video"); const double width = Settings::video().mResolutionX;
double height = Settings::Manager::getInt("resolution y", "Video"); const double height = Settings::video().mResolutionY;
double aspect = (height == 0.0) ? 1.0 : width / height; double aspect = (height == 0.0) ? 1.0 : width / height;
double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2);
osg::Matrixf invertedViewMatrix; osg::Matrixf invertedViewMatrix;
@ -106,8 +106,8 @@ namespace MWLua
}; };
api["worldToViewportVector"] = [camera](osg::Vec3f pos) { api["worldToViewportVector"] = [camera](osg::Vec3f pos) {
double width = Settings::Manager::getInt("resolution x", "Video"); const double width = Settings::video().mResolutionX;
double height = Settings::Manager::getInt("resolution y", "Video"); const double height = Settings::video().mResolutionY;
osg::Matrix windowMatrix osg::Matrix windowMatrix
= osg::Matrix::translate(1.0, 1.0, 1.0) * osg::Matrix::scale(0.5 * width, 0.5 * height, 0.5); = osg::Matrix::translate(1.0, 1.0, 1.0) * osg::Matrix::scale(0.5 * width, 0.5 * height, 0.5);

View file

@ -36,6 +36,7 @@
#include <components/esm4/loadingr.hpp> #include <components/esm4/loadingr.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadmisc.hpp> #include <components/esm4/loadmisc.hpp>
#include <components/esm4/loadmstt.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>
#include <components/esm4/loadtree.hpp> #include <components/esm4/loadtree.hpp>
@ -240,6 +241,9 @@ namespace MWLua
case ESM::REC_MISC4: case ESM::REC_MISC4:
cell.mStore->template forEachType<ESM4::MiscItem>(visitor); cell.mStore->template forEachType<ESM4::MiscItem>(visitor);
break; break;
case ESM::REC_MSTT4:
cell.mStore->template forEachType<ESM4::MovableStatic>(visitor);
break;
case ESM::REC_ALCH4: case ESM::REC_ALCH4:
cell.mStore->template forEachType<ESM4::Potion>(visitor); cell.mStore->template forEachType<ESM4::Potion>(visitor);
break; break;

View file

@ -246,6 +246,13 @@ namespace MWLua
reloadAllScriptsImpl(); reloadAllScriptsImpl();
mReloadAllScriptsRequested = false; mReloadAllScriptsRequested = false;
} }
if (mDelayedUiModeChangedArg)
{
if (playerScripts)
playerScripts->uiModeChanged(*mDelayedUiModeChangedArg, true);
mDelayedUiModeChangedArg = std::nullopt;
}
} }
void LuaManager::applyDelayedActions() void LuaManager::applyDelayedActions()
@ -275,6 +282,7 @@ namespace MWLua
mGlobalScripts.removeAllScripts(); mGlobalScripts.removeAllScripts();
mGlobalScriptsStarted = false; mGlobalScriptsStarted = false;
mNewGameStarted = false; mNewGameStarted = false;
mDelayedUiModeChangedArg = std::nullopt;
if (!mPlayer.isEmpty()) if (!mPlayer.isEmpty())
{ {
mPlayer.getCellRef().unsetRefNum(); mPlayer.getCellRef().unsetRefNum();
@ -325,9 +333,15 @@ namespace MWLua
{ {
if (mPlayer.isEmpty()) if (mPlayer.isEmpty())
return; return;
ObjectId argId = arg.isEmpty() ? ObjectId() : getId(arg);
if (mApplyingDelayedActions)
{
mDelayedUiModeChangedArg = argId;
return;
}
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts()); PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (playerScripts) if (playerScripts)
playerScripts->uiModeChanged(arg, mApplyingDelayedActions); playerScripts->uiModeChanged(argId, false);
} }
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)

View file

@ -204,6 +204,7 @@ namespace MWLua
std::optional<DelayedAction> mTeleportPlayerAction; std::optional<DelayedAction> mTeleportPlayerAction;
std::vector<std::string> mUIMessages; std::vector<std::string> mUIMessages;
std::vector<std::pair<std::string, Misc::Color>> mInGameConsoleMessages; std::vector<std::pair<std::string, Misc::Color>> mInGameConsoleMessages;
std::optional<ObjectId> mDelayedUiModeChangedArg;
LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() };
LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() };

View file

@ -66,9 +66,9 @@ namespace MWLua
} }
// `arg` is either forwarded from MWGui::pushGuiMode or empty // `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); callEngineHandlers(mUiModeChanged, byLuaAction);
else else
callEngineHandlers(mUiModeChanged, byLuaAction, LObject(arg)); callEngineHandlers(mUiModeChanged, byLuaAction, LObject(arg));

View file

@ -138,6 +138,18 @@ namespace MWLua
throw std::runtime_error("The argument must be a player."); throw std::runtime_error("The argument must be a player.");
return input->getControlSwitch(key); 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<const LObject*>(&player) && !dynamic_cast<const SelfObject*>(&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) { player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) {
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
throw std::runtime_error("The argument must be a player."); throw std::runtime_error("The argument must be a player.");

View file

@ -47,6 +47,7 @@ namespace MWLua
constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; constexpr std::string_view ESM4Ingredient = "ESM4Ingredient";
constexpr std::string_view ESM4Light = "ESM4Light"; constexpr std::string_view ESM4Light = "ESM4Light";
constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous";
constexpr std::string_view ESM4MovableStatic = "ESM4MovableStatic";
constexpr std::string_view ESM4Potion = "ESM4Potion"; constexpr std::string_view ESM4Potion = "ESM4Potion";
constexpr std::string_view ESM4Static = "ESM4Static"; constexpr std::string_view ESM4Static = "ESM4Static";
constexpr std::string_view ESM4Terminal = "ESM4Terminal"; constexpr std::string_view ESM4Terminal = "ESM4Terminal";
@ -91,6 +92,7 @@ namespace MWLua
{ ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient },
{ ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light },
{ ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem },
{ ESM::REC_MSTT4, ObjectTypeName::ESM4MovableStatic },
{ ESM::REC_ALCH4, ObjectTypeName::ESM4Potion }, { ESM::REC_ALCH4, ObjectTypeName::ESM4Potion },
{ ESM::REC_STAT4, ObjectTypeName::ESM4Static }, { ESM::REC_STAT4, ObjectTypeName::ESM4Static },
{ ESM::REC_TERM4, ObjectTypeName::ESM4Terminal }, { ESM::REC_TERM4, ObjectTypeName::ESM4Terminal },
@ -230,6 +232,7 @@ namespace MWLua
addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 });
addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 });
addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 });
addType(ObjectTypeName::ESM4MovableStatic, { ESM::REC_MSTT4 });
addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 }); addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 });
addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 }); addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 });
addESM4TerminalBindings(addType(ObjectTypeName::ESM4Terminal, { ESM::REC_TERM4 }), context); addESM4TerminalBindings(addType(ObjectTypeName::ESM4Terminal, { ESM::REC_TERM4 }), context);

View file

@ -239,10 +239,7 @@ namespace MWLua
return luaManager->uiResourceManager()->registerTexture(data); return luaManager->uiResourceManager()->registerTexture(data);
}; };
api["screenSize"] = []() { api["screenSize"] = []() { return osg::Vec2f(Settings::video().mResolutionX, Settings::video().mResolutionY); };
return osg::Vec2f(
Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video"));
};
api["_getAllUiModes"] = [](sol::this_state lua) { api["_getAllUiModes"] = [](sol::this_state lua) {
sol::table res(lua, sol::create); sol::table res(lua, sol::create);

View file

@ -1998,12 +1998,12 @@ namespace MWMechanics
} }
bool Actors::playAnimationGroup( 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); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())
{ {
return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); return iter->second->getCharacterController().playGroup(groupName, mode, number, scripted);
} }
else else
{ {

View file

@ -113,7 +113,7 @@ namespace MWMechanics
void forceStateUpdate(const MWWorld::Ptr& ptr) const; void forceStateUpdate(const MWWorld::Ptr& ptr) const;
bool playAnimationGroup( 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; void skipAnimation(const MWWorld::Ptr& ptr) const;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const;
void persistAnimationStates() const; void persistAnimationStates() const;

View file

@ -708,7 +708,7 @@ namespace MWMechanics
mFleeDest = ESM::Pathgrid::Point(0, 0, 0); mFleeDest = ESM::Pathgrid::Point(0, 0, 0);
} }
bool AiCombatStorage::isFleeing() bool AiCombatStorage::isFleeing() const
{ {
return mFleeState != FleeState_None; return mFleeState != FleeState_None;
} }

View file

@ -70,7 +70,7 @@ namespace MWMechanics
void startFleeing(); void startFleeing();
void stopFleeing(); void stopFleeing();
bool isFleeing(); bool isFleeing() const;
}; };
/// \brief Causes the actor to fight another actor /// \brief Causes the actor to fight another actor

View file

@ -135,6 +135,15 @@ namespace MWMechanics
return mNumPursuitPackages > 0; return mNumPursuitPackages > 0;
} }
bool AiSequence::isFleeing() const
{
if (!isInCombat())
return false;
const AiCombatStorage* storage = mAiState.getPtr<AiCombatStorage>();
return storage && storage->isFleeing();
}
bool AiSequence::isEngagedWithActor() const bool AiSequence::isEngagedWithActor() const
{ {
if (!isInCombat()) if (!isInCombat())
@ -272,7 +281,9 @@ namespace MWMechanics
} }
else 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(); const ESM::Position& targetPos = target.getRefData().getPosition();

View file

@ -117,6 +117,9 @@ namespace MWMechanics
/// Is there any pursuit package. /// Is there any pursuit package.
bool isInPursuit() const; bool isInPursuit() const;
/// Is the actor fleeing?
bool isFleeing() const;
/// Removes all packages using the specified id. /// Removes all packages using the specified id.
void removePackagesById(AiPackageTypeId id); void removePackagesById(AiPackageTypeId id);

View file

@ -38,6 +38,13 @@ namespace MWMechanics
return *result; return *result;
} }
/// \brief returns pointer to stored object in the desired type
template <class Derived>
Derived* getPtr() const
{
return dynamic_cast<Derived*>(mStorage.get());
}
template <class Derived> template <class Derived>
void copy(DerivedClassStorage& destination) const void copy(DerivedClassStorage& destination) const
{ {

View file

@ -229,7 +229,7 @@ namespace MWMechanics
} }
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
storage.setState(AiWanderStorage::Wander_Walking); storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
} }
if (!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) if (!cStats.getMovementFlag(CreatureStats::Flag_ForceJump)
@ -499,7 +499,7 @@ namespace MWMechanics
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
{ {
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
storage.setState(AiWanderStorage::Wander_Walking); storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
else else
storage.setState(AiWanderStorage::Wander_ChooseAction); storage.setState(AiWanderStorage::Wander_ChooseAction);
} }

View file

@ -538,7 +538,7 @@ namespace MWMechanics
if (mAnimation->isPlaying("containerclose")) if (mAnimation->isPlaying("containerclose"))
return false; 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); "start", "stop", 0.f, 0);
if (mAnimation->isPlaying("containeropen")) if (mAnimation->isPlaying("containeropen"))
return false; return false;
@ -559,7 +559,7 @@ namespace MWMechanics
if (animPlaying) if (animPlaying)
startPoint = 1.f - complete; 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); "start", "stop", startPoint, 0);
} }
} }
@ -827,8 +827,8 @@ namespace MWMechanics
void CharacterController::refreshCurrentAnims( void CharacterController::refreshCurrentAnims(
CharacterState idle, CharacterState movement, JumpingState jump, bool force) CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{ {
// If the current animation is persistent, do not touch it // If the current animation is scripted, do not touch it
if (isPersistentAnimPlaying()) if (isScriptedAnimPlaying())
return; return;
refreshHitRecoilAnims(); refreshHitRecoilAnims();
@ -882,7 +882,7 @@ namespace MWMechanics
mDeathState = chooseRandomDeathState(); mDeathState = chooseRandomDeathState();
// Do not interrupt scripted animation by death // Do not interrupt scripted animation by death
if (isPersistentAnimPlaying()) if (isScriptedAnimPlaying())
return; return;
playDeath(startpoint, mDeathState); playDeath(startpoint, mDeathState);
@ -1481,8 +1481,8 @@ namespace MWMechanics
sndMgr->stopSound3D(mPtr, wolfRun); sndMgr->stopSound3D(mPtr, wolfRun);
} }
// Combat for actors with persistent animations obviously will be buggy // Combat for actors with scripted animations obviously will be buggy
if (isPersistentAnimPlaying()) if (isScriptedAnimPlaying())
return forcestateupdate; return forcestateupdate;
float complete = 0.f; float complete = 0.f;
@ -1852,17 +1852,32 @@ namespace MWMechanics
void CharacterController::updateAnimQueue() 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); mAnimation->disable(mAnimQueue.front().mGroup);
mAnimQueue.pop_front(); 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()) if (!mAnimQueue.empty())
@ -2344,7 +2359,7 @@ namespace MWMechanics
} }
} }
if (!isMovementAnimationControlled()) if (!isMovementAnimationControlled() && !isScriptedAnimPlaying())
world->queueMovement(mPtr, vec); world->queueMovement(mPtr, vec);
} }
@ -2371,8 +2386,7 @@ namespace MWMechanics
} }
} }
bool isPersist = isPersistentAnimPlaying(); osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration);
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration);
if (duration > 0.0f) if (duration > 0.0f)
moved /= duration; moved /= duration;
else else
@ -2413,7 +2427,7 @@ namespace MWMechanics
} }
// Update movement // Update movement
if (isMovementAnimationControlled() && mPtr.getClass().isActor()) if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying())
world->queueMovement(mPtr, moved); world->queueMovement(mPtr, moved);
mSkipAnim = false; mSkipAnim = false;
@ -2428,7 +2442,7 @@ namespace MWMechanics
state.mScriptedAnims.clear(); state.mScriptedAnims.clear();
for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter)
{ {
if (!iter->mPersist) if (!iter->mScripted)
continue; continue;
ESM::AnimationState::ScriptedAnimation anim; ESM::AnimationState::ScriptedAnimation anim;
@ -2464,7 +2478,7 @@ namespace MWMechanics
AnimationQueueEntry entry; AnimationQueueEntry entry;
entry.mGroup = iter->mGroup; entry.mGroup = iter->mGroup;
entry.mLoopCount = iter->mLoopCount; entry.mLoopCount = iter->mLoopCount;
entry.mPersist = true; entry.mScripted = true;
mAnimQueue.push_back(entry); mAnimQueue.push_back(entry);
} }
@ -2483,18 +2497,18 @@ namespace MWMechanics
mIdleState = CharState_SpecialIdle; mIdleState = CharState_SpecialIdle;
bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); 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); "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)) if (!mAnimation || !mAnimation->hasAnimation(groupname))
return false; return false;
// We should not interrupt persistent animations by non-persistent ones // We should not interrupt scripted animations with non-scripted ones
if (isPersistentAnimPlaying() && !persist) if (isScriptedAnimPlaying() && !scripted)
return true; return true;
// If this animation is a looped animation (has a "loop start" key) that is already playing // If this animation is a looped animation (has a "loop start" key) that is already playing
@ -2523,17 +2537,17 @@ namespace MWMechanics
AnimationQueueEntry entry; AnimationQueueEntry entry;
entry.mGroup = groupname; entry.mGroup = groupname;
entry.mLoopCount = count - 1; entry.mLoopCount = count - 1;
entry.mPersist = persist; entry.mScripted = scripted;
if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
{ {
clearAnimQueue(persist); clearAnimQueue(scripted);
clearStateAnimation(mCurrentIdle); clearStateAnimation(mCurrentIdle);
mIdleState = CharState_SpecialIdle; mIdleState = CharState_SpecialIdle;
bool loopfallback = entry.mGroup.starts_with("idle"); 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, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f,
count - 1, loopfallback); count - 1, loopfallback);
} }
@ -2544,7 +2558,7 @@ namespace MWMechanics
// "PlayGroup idle" is a special case, used to remove to stop scripted animations playing // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing
if (groupname == "idle") if (groupname == "idle")
entry.mPersist = false; entry.mScripted = false;
mAnimQueue.push_back(entry); mAnimQueue.push_back(entry);
@ -2556,12 +2570,12 @@ namespace MWMechanics
mSkipAnim = true; mSkipAnim = true;
} }
bool CharacterController::isPersistentAnimPlaying() const bool CharacterController::isScriptedAnimPlaying() const
{ {
if (!mAnimQueue.empty()) if (!mAnimQueue.empty())
{ {
const AnimationQueueEntry& first = mAnimQueue.front(); const AnimationQueueEntry& first = mAnimQueue.front();
return first.mPersist && isAnimPlaying(first.mGroup); return first.mScripted && isAnimPlaying(first.mGroup);
} }
return false; return false;
@ -2584,13 +2598,13 @@ namespace MWMechanics
return movementAnimationControlled; return movementAnimationControlled;
} }
void CharacterController::clearAnimQueue(bool clearPersistAnims) void CharacterController::clearAnimQueue(bool clearScriptedAnims)
{ {
// Do not interrupt scripted animations, if we want to keep them // 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); mAnimation->disable(mAnimQueue.front().mGroup);
if (clearPersistAnims) if (clearScriptedAnims)
{ {
mAnimQueue.clear(); mAnimQueue.clear();
return; return;
@ -2598,7 +2612,7 @@ namespace MWMechanics
for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();)
{ {
if (!it->mPersist) if (!it->mScripted)
it = mAnimQueue.erase(it); it = mAnimQueue.erase(it);
else else
++it; ++it;

View file

@ -40,7 +40,7 @@ namespace MWMechanics
Priority_Torch, Priority_Torch,
Priority_Storm, Priority_Storm,
Priority_Death, Priority_Death,
Priority_Persistent, Priority_Scripted,
Num_Priorities Num_Priorities
}; };
@ -135,7 +135,7 @@ namespace MWMechanics
{ {
std::string mGroup; std::string mGroup;
size_t mLoopCount; size_t mLoopCount;
bool mPersist; bool mScripted;
}; };
typedef std::deque<AnimationQueueEntry> AnimationQueue; typedef std::deque<AnimationQueueEntry> AnimationQueue;
AnimationQueue mAnimQueue; AnimationQueue mAnimQueue;
@ -207,7 +207,7 @@ namespace MWMechanics
void refreshMovementAnims(CharacterState movement, bool force = false); void refreshMovementAnims(CharacterState movement, bool force = false);
void refreshIdleAnims(CharacterState idle, bool force = false); void refreshIdleAnims(CharacterState idle, bool force = false);
void clearAnimQueue(bool clearPersistAnims = false); void clearAnimQueue(bool clearScriptedAnims = false);
bool updateWeaponState(); bool updateWeaponState();
void updateIdleStormState(bool inwater) const; void updateIdleStormState(bool inwater) const;
@ -215,7 +215,7 @@ namespace MWMechanics
std::string chooseRandomAttackAnimation() const; std::string chooseRandomAttackAnimation() const;
static bool isRandomAttackAnimation(std::string_view group); static bool isRandomAttackAnimation(std::string_view group);
bool isPersistentAnimPlaying() const; bool isScriptedAnimPlaying() const;
bool isMovementAnimationControlled() const; bool isMovementAnimationControlled() const;
void updateAnimQueue(); void updateAnimQueue();
@ -270,7 +270,7 @@ namespace MWMechanics
void persistAnimationState() const; void persistAnimationState() const;
void unpersistAnimationState(); 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(); void skipAnim();
bool isAnimPlaying(std::string_view groupName) const; bool isAnimPlaying(std::string_view groupName) const;

View file

@ -760,12 +760,12 @@ namespace MWMechanics
} }
bool MechanicsManager::playAnimationGroup( 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()) if (ptr.getClass().isActor())
return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted);
else else
return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted);
} }
void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr)
{ {

View file

@ -142,7 +142,7 @@ namespace MWMechanics
/// Attempt to play an animation group /// Attempt to play an animation group
/// @return Success or error /// @return Success or error
bool playAnimationGroup( 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; void skipAnimation(const MWWorld::Ptr& ptr) override;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override;
void persistAnimationStates() override; void persistAnimationStates() override;

View file

@ -99,12 +99,12 @@ namespace MWMechanics
} }
bool Objects::playAnimationGroup( 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); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())
{ {
return iter->second->playGroup(groupName, mode, number, persist); return iter->second->playGroup(groupName, mode, number, scripted);
} }
else else
{ {

View file

@ -46,7 +46,7 @@ namespace MWMechanics
void onClose(const MWWorld::Ptr& ptr); void onClose(const MWWorld::Ptr& ptr);
bool playAnimationGroup( 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 skipAnimation(const MWWorld::Ptr& ptr);
void persistAnimationStates(); void persistAnimationStates();

View file

@ -118,13 +118,17 @@ namespace MWMechanics
} }
int value = 50.f; int value = 50.f;
if (actor.getClass().isNpc()) ESM::RefId skill = item.getClass().getEquipmentSkill(item);
{ if (!skill.empty())
ESM::RefId skill = item.getClass().getEquipmentSkill(item); value = actor.getClass().getSkill(actor, skill);
if (!skill.empty()) // Prefer hand-to-hand if our skill is 0 (presumably due to magic)
value = actor.getClass().getSkill(actor, skill); if (value <= 0.f)
} return 0.f;
else // 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<ESM::Creature>* ref = actor.get<ESM::Creature>(); MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
value = ref->mBase->mData.mCombat; value = ref->mBase->mData.mCombat;

View file

@ -1145,8 +1145,7 @@ namespace MWRender
bool hasScriptedAnims = false; bool hasScriptedAnims = false;
for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++)
{ {
if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying)
&& stateiter->second.mPlaying)
{ {
hasScriptedAnims = true; hasScriptedAnims = true;
break; break;
@ -1158,7 +1157,7 @@ namespace MWRender
while (stateiter != mStates.end()) while (stateiter != mStates.end())
{ {
AnimState& state = stateiter->second; AnimState& state = stateiter->second;
if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted)))
{ {
++stateiter; ++stateiter;
continue; continue;

View file

@ -154,7 +154,7 @@ namespace MWRender
public: public:
CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) 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()) StereoAwareness::Unaware_MultiViewShaders, shouldAddMSAAIntermediateTarget())
, mAspectRatio(static_cast<float>(sizeX) / static_cast<float>(sizeY)) , mAspectRatio(static_cast<float>(sizeX) / static_cast<float>(sizeY))
{ {

View file

@ -457,14 +457,14 @@ namespace MWRender
: GenericResourceManager<ChunkId>(nullptr, Settings::cells().mCacheExpiryDelay) : GenericResourceManager<ChunkId>(nullptr, Settings::cells().mCacheExpiryDelay)
, Terrain::QuadTreeWorld::ChunkManager(worldspace) , Terrain::QuadTreeWorld::ChunkManager(worldspace)
, mSceneManager(sceneManager) , 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) , 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<ESM::RefNum, ESM::CellRef> ObjectPaging::collectESM3References( std::map<ESM::RefNum, ESM::CellRef> ObjectPaging::collectESM3References(

View file

@ -112,7 +112,7 @@ namespace MWRender
: osg::Group() : osg::Group()
, mEnableLiveReload(false) , mEnableLiveReload(false)
, mRootNode(rootNode) , mRootNode(rootNode)
, mSamples(Settings::Manager::getInt("antialiasing", "Video")) , mSamples(Settings::video().mAntialiasing)
, mDirty(false) , mDirty(false)
, mDirtyFrameId(0) , mDirtyFrameId(0)
, mRendering(rendering) , mRendering(rendering)

View file

@ -1230,8 +1230,8 @@ namespace MWRender
if (mViewDistance < mNearClip) if (mViewDistance < mNearClip)
throw std::runtime_error("Viewing distance is less than near clip"); throw std::runtime_error("Viewing distance is less than near clip");
double width = Settings::Manager::getInt("resolution x", "Video"); const double width = Settings::video().mResolutionX;
double height = Settings::Manager::getInt("resolution y", "Video"); const double height = Settings::video().mResolutionY;
double aspect = (height == 0.0) ? 1.0 : width / height; double aspect = (height == 0.0) ? 1.0 : width / height;
float fov = mFieldOfView; float fov = mFieldOfView;
@ -1316,23 +1316,21 @@ namespace MWRender
return existingChunkMgr->second; return existingChunkMgr->second;
RenderingManager::WorldspaceChunkMgr newChunkMgr; RenderingManager::WorldspaceChunkMgr newChunkMgr;
const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); const float lodFactor = Settings::terrain().mLodFactor;
const bool groundcover = Settings::groundcover().mEnabled; const bool groundcover = Settings::groundcover().mEnabled;
bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); const bool distantTerrain = Settings::terrain().mDistantTerrain;
if (distantTerrain || groundcover) if (distantTerrain || groundcover)
{ {
const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); const int compMapResolution = Settings::terrain().mCompositeMapResolution;
int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); const int compMapPower = Settings::terrain().mCompositeMapLevel;
compMapPower = std::max(-3, compMapPower); const float compMapLevel = std::pow(2, compMapPower);
float compMapLevel = pow(2, compMapPower); const int vertexLodMod = Settings::terrain().mVertexLodMod;
const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); const float maxCompGeometrySize = Settings::terrain().mMaxCompositeGeometrySize;
float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); const bool debugChunks = Settings::terrain().mDebugChunks;
maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f);
bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain");
auto quadTreeWorld = std::make_unique<Terrain::QuadTreeWorld>(mSceneRoot, mRootNode, mResourceSystem, auto quadTreeWorld = std::make_unique<Terrain::QuadTreeWorld>(mSceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel,
lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace);
if (Settings::Manager::getBool("object paging", "Terrain")) if (Settings::terrain().mObjectPaging)
{ {
newChunkMgr.mObjectPaging newChunkMgr.mObjectPaging
= std::make_unique<ObjectPaging>(mResourceSystem->getSceneManager(), worldspace); = std::make_unique<ObjectPaging>(mResourceSystem->getSceneManager(), worldspace);

View file

@ -8,6 +8,7 @@
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/TextureCubeMap> #include <osg/TextureCubeMap>
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/strings/algorithm.hpp> #include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -20,7 +21,6 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwgui/loadingscreen.hpp"
#include "postprocessor.hpp" #include "postprocessor.hpp"
#include "util.hpp" #include "util.hpp"
@ -29,7 +29,7 @@
namespace MWRender namespace MWRender
{ {
enum Screenshot360Type enum class Screenshot360Type
{ {
Spherical, Spherical,
Cylindrical, Cylindrical,
@ -161,59 +161,46 @@ namespace MWRender
bool ScreenshotManager::screenshot360(osg::Image* image) bool ScreenshotManager::screenshot360(osg::Image* image)
{ {
int screenshotW = mViewer->getCamera()->getViewport()->width(); const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType;
int screenshotH = mViewer->getCamera()->getViewport()->height();
Screenshot360Type screenshotMapping = Spherical;
const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); Screenshot360Type screenshotMapping = Screenshot360Type::Spherical;
std::vector<std::string_view> settingArgs;
Misc::StringUtils::split(settingStr, settingArgs);
if (settingArgs.size() > 0) switch (settings.mType)
{ {
std::string_view typeStrings[4] = { "spherical", "cylindrical", "planet", "cubemap" }; case Settings::ScreenshotType::Regular:
bool found = false; Log(Debug::Warning) << "Wrong screenshot 360 type: regular.";
for (int i = 0; i < 4; ++i)
{
if (settingArgs[0] == typeStrings[i])
{
screenshotMapping = static_cast<Screenshot360Type>(i);
found = true;
break;
}
}
if (!found)
{
Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << ".";
return false; 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 // planet mapping needs higher resolution
int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; const int cubeSize = screenshotMapping == Screenshot360Type::Planet ? screenshotW : screenshotW / 2;
const bool rawCubemap = screenshotMapping == Screenshot360Type::RawCubemap;
if (settingArgs.size() > 1)
{
screenshotW = std::min(10000, Misc::StringUtils::toNumeric<int>(settingArgs[1], 0));
}
if (settingArgs.size() > 2)
{
screenshotH = std::min(10000, Misc::StringUtils::toNumeric<int>(settingArgs[2], 0));
}
if (settingArgs.size() > 3)
{
cubeSize = std::min(5000, Misc::StringUtils::toNumeric<int>(settingArgs[3], 0));
}
bool rawCubemap = screenshotMapping == RawCubemap;
if (rawCubemap) if (rawCubemap)
screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row 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 screenshotH = screenshotW; // use square resolution for planet mapping
std::vector<osg::ref_ptr<osg::Image>> images; std::vector<osg::ref_ptr<osg::Image>> images;
@ -276,7 +263,7 @@ namespace MWRender
stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("cubeMap", 0)); stateset->addUniform(new osg::Uniform("cubeMap", 0));
stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); stateset->addUniform(new osg::Uniform("mapping", static_cast<int>(screenshotMapping)));
stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON);
screenshotCamera->addChild(quad); screenshotCamera->addChild(quad);

View file

@ -70,7 +70,7 @@ namespace MWRender
bool shouldAddMSAAIntermediateTarget() bool shouldAddMSAAIntermediateTarget()
{ {
return Settings::shaders().mAntialiasAlphaTest && Settings::Manager::getInt("antialiasing", "Video") > 1; return Settings::shaders().mAntialiasAlphaTest && Settings::video().mAntialiasing > 1;
} }
} }

View file

@ -31,6 +31,7 @@
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/levelledlist.hpp"
#include "interpretercontext.hpp"
#include "ref.hpp" #include "ref.hpp"
namespace namespace
@ -94,6 +95,12 @@ namespace MWScript
Interpreter::Type_Integer count = runtime[0].mInteger; Interpreter::Type_Integer count = runtime[0].mInteger;
runtime.pop(); runtime.pop();
if (!MWBase::Environment::get().getESMStore()->find(item))
{
runtime.getContext().report("Failed to add item '" + item.getRefIdString() + "': unknown ID");
return;
}
if (count < 0) if (count < 0)
count = static_cast<uint16_t>(count); count = static_cast<uint16_t>(count);
@ -210,6 +217,12 @@ namespace MWScript
Interpreter::Type_Integer count = runtime[0].mInteger; Interpreter::Type_Integer count = runtime[0].mInteger;
runtime.pop(); runtime.pop();
if (!MWBase::Environment::get().getESMStore()->find(item))
{
runtime.getContext().report("Failed to remove item '" + item.getRefIdString() + "': unknown ID");
return;
}
if (count < 0) if (count < 0)
count = static_cast<uint16_t>(count); count = static_cast<uint16_t>(count);

View file

@ -16,6 +16,7 @@
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "ref.hpp" #include "ref.hpp"
@ -89,6 +90,13 @@ namespace MWScript
ESM::RefId topic = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); ESM::RefId topic = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger));
runtime.pop(); runtime.pop();
if (!MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>().search(topic))
{
runtime.getContext().report(
"Failed to add topic '" + topic.getRefIdString() + "': topic record not found");
return;
}
MWBase::Environment::get().getDialogueManager()->addTopic(topic); MWBase::Environment::get().getDialogueManager()->addTopic(topic);
} }
}; };

View file

@ -41,6 +41,7 @@
#include <components/esm3/loadmisc.hpp> #include <components/esm3/loadmisc.hpp>
#include <components/esm3/loadprob.hpp> #include <components/esm3/loadprob.hpp>
#include <components/esm3/loadrepa.hpp> #include <components/esm3/loadrepa.hpp>
#include <components/esm3/loadscpt.hpp>
#include <components/esm3/loadstat.hpp> #include <components/esm3/loadstat.hpp>
#include <components/esm3/loadweap.hpp> #include <components/esm3/loadweap.hpp>
@ -184,6 +185,14 @@ namespace MWScript
MWWorld::Ptr target = R()(runtime, false); MWWorld::Ptr target = R()(runtime, false);
ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger));
runtime.pop(); runtime.pop();
if (!MWBase::Environment::get().getESMStore()->get<ESM::Script>().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); 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)); const ESM::RefId& name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger));
runtime.pop(); runtime.pop();
if (!MWBase::Environment::get().getESMStore()->get<ESM::Script>().search(name))
{
runtime.getContext().report(
"Failed to stop global script '" + name.getRefIdString() + "': script record not found");
return;
}
MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript(name); MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript(name);
} }
}; };

View file

@ -57,6 +57,7 @@
#include <components/esm4/loadingr.hpp> #include <components/esm4/loadingr.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadmisc.hpp> #include <components/esm4/loadmisc.hpp>
#include <components/esm4/loadmstt.hpp>
#include <components/esm4/loadnpc.hpp> #include <components/esm4/loadnpc.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>
@ -160,31 +161,32 @@ namespace
template <typename T> template <typename T>
void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList<T>& collection) void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList<T>& collection)
{ {
if (!collection.mList.empty()) // references
for (const MWWorld::LiveCellRef<T>& liveCellRef : collection.mList)
{ {
// references if (ESM::isESM4Rec(T::sRecordId))
for (typename MWWorld::CellRefList<T>::List::const_iterator iter(collection.mList.begin());
iter != collection.mList.end(); ++iter)
{ {
if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) // TODO: Implement loading/saving of REFR4 and ACHR4 with ESM3 reader/writer.
{ continue;
// 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<T>::StateType;
StateType state;
iter->save(state);
// recordId currently unused
writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId);
state.save(writer);
} }
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<T>::StateType;
StateType state;
liveCellRef.save(state);
// recordId currently unused
writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId);
state.save(writer);
} }
} }

View file

@ -73,6 +73,7 @@ namespace ESM4
struct Flora; struct Flora;
struct Ingredient; struct Ingredient;
struct MiscItem; struct MiscItem;
struct MovableStatic;
struct Terminal; struct Terminal;
struct Tree; struct Tree;
struct Weapon; struct Weapon;
@ -95,8 +96,9 @@ namespace MWWorld
CellRefList<ESM4::Static>, CellRefList<ESM4::Light>, CellRefList<ESM4::Activator>, CellRefList<ESM4::Potion>, CellRefList<ESM4::Static>, CellRefList<ESM4::Light>, CellRefList<ESM4::Activator>, CellRefList<ESM4::Potion>,
CellRefList<ESM4::Ammunition>, CellRefList<ESM4::Armor>, CellRefList<ESM4::Book>, CellRefList<ESM4::Clothing>, CellRefList<ESM4::Ammunition>, CellRefList<ESM4::Armor>, CellRefList<ESM4::Book>, CellRefList<ESM4::Clothing>,
CellRefList<ESM4::Container>, CellRefList<ESM4::Door>, CellRefList<ESM4::Flora>, CellRefList<ESM4::Ingredient>, CellRefList<ESM4::Container>, CellRefList<ESM4::Door>, CellRefList<ESM4::Flora>, CellRefList<ESM4::Ingredient>,
CellRefList<ESM4::Terminal>, CellRefList<ESM4::Tree>, CellRefList<ESM4::MiscItem>, CellRefList<ESM4::Weapon>, CellRefList<ESM4::Terminal>, CellRefList<ESM4::Tree>, CellRefList<ESM4::MiscItem>,
CellRefList<ESM4::Furniture>, CellRefList<ESM4::Creature>, CellRefList<ESM4::Npc>>; CellRefList<ESM4::MovableStatic>, CellRefList<ESM4::Weapon>, CellRefList<ESM4::Furniture>,
CellRefList<ESM4::Creature>, CellRefList<ESM4::Npc>>;
/// \brief Mutable state of a cell /// \brief Mutable state of a cell
class CellStore class CellStore

View file

@ -84,7 +84,8 @@ namespace
} }
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions, std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions,
const MWWorld::Store<ESM::Class>& classes, const std::unordered_map<ESM::RefId, ESM::NPC>& npcs) const MWWorld::Store<ESM::Class>& classes, const MWWorld::Store<ESM::Script>& scripts,
const std::unordered_map<ESM::RefId, ESM::NPC>& npcs)
{ {
// Cache first class from store - we will use it if current class is not found // Cache first class from store - we will use it if current class is not found
const ESM::RefId& defaultCls = getDefaultClass(classes); const ESM::RefId& defaultCls = getDefaultClass(classes);
@ -122,6 +123,14 @@ namespace
changed = true; 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) if (changed)
npcsToReplace.push_back(npc); npcsToReplace.push_back(npc);
} }
@ -138,9 +147,9 @@ namespace
{ {
if (!item.mScript.empty() && !scripts.search(item.mScript)) 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(); 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_LVLC4:
case ESM::REC_LVLN4: case ESM::REC_LVLN4:
case ESM::REC_MISC4: case ESM::REC_MISC4:
case ESM::REC_MSTT4:
case ESM::REC_NPC_4: case ESM::REC_NPC_4:
case ESM::REC_STAT4: case ESM::REC_STAT4:
case ESM::REC_TERM4: case ESM::REC_TERM4:
@ -517,8 +527,8 @@ namespace MWWorld
void ESMStore::validate() void ESMStore::validate()
{ {
auto& npcs = getWritable<ESM::NPC>(); auto& npcs = getWritable<ESM::NPC>();
std::vector<ESM::NPC> npcsToReplace std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(
= getNPCsToReplace(getWritable<ESM::Faction>(), getWritable<ESM::Class>(), npcs.mStatic); getWritable<ESM::Faction>(), getWritable<ESM::Class>(), getWritable<ESM::Script>(), npcs.mStatic);
for (const ESM::NPC& npc : npcsToReplace) for (const ESM::NPC& npc : npcsToReplace)
{ {
@ -526,6 +536,8 @@ namespace MWWorld
npcs.insertStatic(npc); npcs.insertStatic(npc);
} }
removeMissingScripts(getWritable<ESM::Script>(), getWritable<ESM::Creature>().mStatic);
// Validate spell effects for invalid arguments // Validate spell effects for invalid arguments
std::vector<ESM::Spell> spellsToReplace; std::vector<ESM::Spell> spellsToReplace;
auto& spells = getWritable<ESM::Spell>(); auto& spells = getWritable<ESM::Spell>();
@ -605,8 +617,8 @@ namespace MWWorld
auto& npcs = getWritable<ESM::NPC>(); auto& npcs = getWritable<ESM::NPC>();
auto& scripts = getWritable<ESM::Script>(); auto& scripts = getWritable<ESM::Script>();
std::vector<ESM::NPC> npcsToReplace std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(
= getNPCsToReplace(getWritable<ESM::Faction>(), getWritable<ESM::Class>(), npcs.mDynamic); getWritable<ESM::Faction>(), getWritable<ESM::Class>(), getWritable<ESM::Script>(), npcs.mDynamic);
for (const ESM::NPC& npc : npcsToReplace) for (const ESM::NPC& npc : npcsToReplace)
npcs.insert(npc); npcs.insert(npc);
@ -614,6 +626,7 @@ namespace MWWorld
removeMissingScripts(scripts, getWritable<ESM::Armor>().mDynamic); removeMissingScripts(scripts, getWritable<ESM::Armor>().mDynamic);
removeMissingScripts(scripts, getWritable<ESM::Book>().mDynamic); removeMissingScripts(scripts, getWritable<ESM::Book>().mDynamic);
removeMissingScripts(scripts, getWritable<ESM::Clothing>().mDynamic); removeMissingScripts(scripts, getWritable<ESM::Clothing>().mDynamic);
removeMissingScripts(scripts, getWritable<ESM::Creature>().mDynamic);
removeMissingScripts(scripts, getWritable<ESM::Weapon>().mDynamic); removeMissingScripts(scripts, getWritable<ESM::Weapon>().mDynamic);
removeMissingObjects(getWritable<ESM::CreatureLevList>()); removeMissingObjects(getWritable<ESM::CreatureLevList>());

View file

@ -92,11 +92,13 @@ namespace ESM4
struct HeadPart; struct HeadPart;
struct Ingredient; struct Ingredient;
struct Land; struct Land;
struct LandTexture;
struct LevelledCreature; struct LevelledCreature;
struct LevelledItem; struct LevelledItem;
struct LevelledNpc; struct LevelledNpc;
struct Light; struct Light;
struct MiscItem; struct MiscItem;
struct MovableStatic;
struct Npc; struct Npc;
struct Outfit; struct Outfit;
struct Potion; struct Potion;
@ -139,10 +141,10 @@ namespace MWWorld
Store<ESM4::Armor>, Store<ESM4::ArmorAddon>, Store<ESM4::Book>, Store<ESM4::Cell>, Store<ESM4::Clothing>, Store<ESM4::Armor>, Store<ESM4::ArmorAddon>, Store<ESM4::Book>, Store<ESM4::Cell>, Store<ESM4::Clothing>,
Store<ESM4::Container>, Store<ESM4::Creature>, Store<ESM4::Door>, Store<ESM4::Furniture>, Store<ESM4::Container>, Store<ESM4::Creature>, Store<ESM4::Door>, Store<ESM4::Furniture>,
Store<ESM4::Flora>, Store<ESM4::Hair>, Store<ESM4::HeadPart>, Store<ESM4::Ingredient>, Store<ESM4::Land>, Store<ESM4::Flora>, Store<ESM4::Hair>, Store<ESM4::HeadPart>, Store<ESM4::Ingredient>, Store<ESM4::Land>,
Store<ESM4::LevelledCreature>, Store<ESM4::LevelledItem>, Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::LandTexture>, Store<ESM4::LevelledCreature>, Store<ESM4::LevelledItem>,
Store<ESM4::MiscItem>, Store<ESM4::Npc>, Store<ESM4::Outfit>, Store<ESM4::Potion>, Store<ESM4::Race>, Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::MiscItem>, Store<ESM4::MovableStatic>,
Store<ESM4::Reference>, Store<ESM4::Static>, Store<ESM4::Terminal>, Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::Npc>, Store<ESM4::Outfit>, Store<ESM4::Potion>, Store<ESM4::Race>, Store<ESM4::Reference>,
Store<ESM4::World>>; Store<ESM4::Static>, Store<ESM4::Terminal>, Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::World>>;
private: private:
template <typename T> template <typename T>

View file

@ -1363,11 +1363,13 @@ template class MWWorld::TypedDynamicStore<ESM4::Hair>;
template class MWWorld::TypedDynamicStore<ESM4::HeadPart>; template class MWWorld::TypedDynamicStore<ESM4::HeadPart>;
template class MWWorld::TypedDynamicStore<ESM4::Ingredient>; template class MWWorld::TypedDynamicStore<ESM4::Ingredient>;
template class MWWorld::TypedDynamicStore<ESM4::Land>; template class MWWorld::TypedDynamicStore<ESM4::Land>;
template class MWWorld::TypedDynamicStore<ESM4::LandTexture>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledCreature>; template class MWWorld::TypedDynamicStore<ESM4::LevelledCreature>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledItem>; template class MWWorld::TypedDynamicStore<ESM4::LevelledItem>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledNpc>; template class MWWorld::TypedDynamicStore<ESM4::LevelledNpc>;
template class MWWorld::TypedDynamicStore<ESM4::Light>; template class MWWorld::TypedDynamicStore<ESM4::Light>;
template class MWWorld::TypedDynamicStore<ESM4::MiscItem>; template class MWWorld::TypedDynamicStore<ESM4::MiscItem>;
template class MWWorld::TypedDynamicStore<ESM4::MovableStatic>;
template class MWWorld::TypedDynamicStore<ESM4::Npc>; template class MWWorld::TypedDynamicStore<ESM4::Npc>;
template class MWWorld::TypedDynamicStore<ESM4::Outfit>; template class MWWorld::TypedDynamicStore<ESM4::Outfit>;
template class MWWorld::TypedDynamicStore<ESM4::Potion>; template class MWWorld::TypedDynamicStore<ESM4::Potion>;

View file

@ -87,6 +87,8 @@ add_component_dir (settings
settingvalue settingvalue
shadermanager shadermanager
values values
screenshotsettings
windowmode
) )
add_component_dir (bsa add_component_dir (bsa
@ -336,7 +338,15 @@ add_component_dir (fontloader
) )
add_component_dir (sdlutil 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 add_component_dir (version

View file

@ -62,10 +62,12 @@
#include <components/esm4/loadingr.hpp> #include <components/esm4/loadingr.hpp>
#include <components/esm4/loadland.hpp> #include <components/esm4/loadland.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadltex.hpp>
#include <components/esm4/loadlvlc.hpp> #include <components/esm4/loadlvlc.hpp>
#include <components/esm4/loadlvli.hpp> #include <components/esm4/loadlvli.hpp>
#include <components/esm4/loadlvln.hpp> #include <components/esm4/loadlvln.hpp>
#include <components/esm4/loadmisc.hpp> #include <components/esm4/loadmisc.hpp>
#include <components/esm4/loadmstt.hpp>
#include <components/esm4/loadnpc.hpp> #include <components/esm4/loadnpc.hpp>
#include <components/esm4/loadotft.hpp> #include <components/esm4/loadotft.hpp>
#include <components/esm4/loadrace.hpp> #include <components/esm4/loadrace.hpp>

View file

@ -2708,7 +2708,8 @@ namespace NifOsg
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
} }
if (shaderprop->softEffect()) if (shaderprop->softEffect())
SceneUtil::setupSoftEffect(*node, shaderprop->mFalloffDepth, true); SceneUtil::setupSoftEffect(
*node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth);
break; break;
} }
default: default:

View file

@ -15,7 +15,7 @@
namespace SceneUtil 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<SceneUtil::AutoDepth> depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); static const osg::ref_ptr<SceneUtil::AutoDepth> 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("particleSize", size));
stateset->addUniform(new osg::Uniform("particleFade", falloff)); stateset->addUniform(new osg::Uniform("particleFade", falloff));
stateset->addUniform(new osg::Uniform("softFalloffDepth", falloffDepth));
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true);
@ -35,6 +36,8 @@ namespace SceneUtil
std::string source; 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()) if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty())
{ {
YAML::Node root = YAML::Load(source); YAML::Node root = YAML::Load(source);
@ -47,8 +50,9 @@ namespace SceneUtil
{ {
auto size = it.second["size"].as<float>(45.f); auto size = it.second["size"].as<float>(45.f);
auto falloff = it.second["falloff"].as<bool>(false); auto falloff = it.second["falloff"].as<bool>(false);
auto falloffDepth = it.second["falloffDepth"].as<float>(defaultFalloffDepth);
setupSoftEffect(node, size, falloff); setupSoftEffect(node, size, falloff, falloffDepth);
} }
} }
@ -56,7 +60,8 @@ namespace SceneUtil
} }
else if (osgParticle::ParticleSystem* partsys = dynamic_cast<osgParticle::ParticleSystem*>(&node)) else if (osgParticle::ParticleSystem* partsys = dynamic_cast<osgParticle::ParticleSystem*>(&node))
{ {
setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false); setupSoftEffect(
node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false, defaultFalloffDepth);
} }
traverse(node); traverse(node);

View file

@ -15,7 +15,7 @@ namespace osg
namespace SceneUtil 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 class ProcessExtraDataVisitor : public osg::NodeVisitor
{ {

View file

@ -14,22 +14,16 @@ namespace SDLUtil
close(true); close(true);
} }
GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync) GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode)
: mWindow(nullptr) : mWindow(nullptr)
, mContext(nullptr) , mContext(nullptr)
, mValid(false) , mValid(false)
, mRealized(false) , mRealized(false)
, mOwnsWindow(false) , mOwnsWindow(false)
, mVSyncMode(vsyncMode)
{ {
_traits = traits; _traits = traits;
if (vsync == 2)
mVSyncMode = VSyncMode::Adaptive;
else if (vsync == 1)
mVSyncMode = VSyncMode::Enabled;
else
mVSyncMode = VSyncMode::Disabled;
init(); init();
if (GraphicsWindowSDL2::valid()) if (GraphicsWindowSDL2::valid())
{ {

View file

@ -5,14 +5,10 @@
#include <osgViewer/GraphicsWindow> #include <osgViewer/GraphicsWindow>
#include "vsyncmode.hpp"
namespace SDLUtil namespace SDLUtil
{ {
enum VSyncMode
{
Disabled = 0,
Enabled = 1,
Adaptive = 2
};
class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow
{ {
@ -29,7 +25,7 @@ namespace SDLUtil
virtual ~GraphicsWindowSDL2(); virtual ~GraphicsWindowSDL2();
public: public:
GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync); GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode);
bool isSameKindAs(const Object* object) const override bool isSameKindAs(const Object* object) const override
{ {

View file

@ -1,7 +1,7 @@
#include "sdlinputwrapper.hpp" #include "sdlinputwrapper.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/settings/settings.hpp> #include <components/settings/values.hpp>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
@ -187,8 +187,10 @@ namespace SDLUtil
{ {
case SDL_DISPLAYEVENT_ORIENTATION: case SDL_DISPLAYEVENT_ORIENTATION:
if (mSensorListener if (mSensorListener
&& evt.display.display == (unsigned int)Settings::Manager::getInt("screen", "Video")) && evt.display.display == static_cast<Uint32>(Settings::video().mScreen))
{
mSensorListener->displayOrientationChanged(); mSensorListener->displayOrientationChanged();
}
break; break;
default: default:
break; break;

View file

@ -30,14 +30,8 @@ namespace SDLUtil
SDL_SetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); 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; osgViewer::Viewer::Windows windows;
mViewer->getWindows(windows); mViewer->getWindows(windows);
mViewer->stopThreading(); mViewer->stopThreading();
@ -47,7 +41,7 @@ namespace SDLUtil
if (GraphicsWindowSDL2* sdl2win = dynamic_cast<GraphicsWindowSDL2*>(win)) if (GraphicsWindowSDL2* sdl2win = dynamic_cast<GraphicsWindowSDL2*>(win))
sdl2win->setSyncToVBlank(vsyncMode); sdl2win->setSyncToVBlank(vsyncMode);
else else
win->setSyncToVBlank(static_cast<bool>(mode)); win->setSyncToVBlank(vsyncMode != VSyncMode::Disabled);
} }
mViewer->startThreading(); mViewer->startThreading();
} }

View file

@ -5,6 +5,8 @@
#include <SDL_types.h> #include <SDL_types.h>
#include "vsyncmode.hpp"
struct SDL_Window; struct SDL_Window;
namespace osgViewer namespace osgViewer
@ -26,7 +28,7 @@ namespace SDLUtil
VideoWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> viewer); VideoWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> viewer);
~VideoWrapper(); ~VideoWrapper();
void setSyncToVBlank(int mode); void setSyncToVBlank(VSyncMode vsyncMode);
void setGammaContrast(float gamma, float contrast); void setGammaContrast(float gamma, float contrast);

View file

@ -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

View file

@ -31,14 +31,14 @@ namespace Settings
makeClampSanitizerDouble(-1, 1) }; makeClampSanitizerDouble(-1, 1) };
SettingValue<double> mLeftEyeOrientationW{ mIndex, "Stereo View", "left eye orientation w", SettingValue<double> mLeftEyeOrientationW{ mIndex, "Stereo View", "left eye orientation w",
makeClampSanitizerDouble(-1, 1) }; makeClampSanitizerDouble(-1, 1) };
SettingValue<double> mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", SettingValue<float> mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", SettingValue<float> mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", SettingValue<float> mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", SettingValue<float> mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mRightEyeOffsetX{ mIndex, "Stereo View", "right eye offset x" }; SettingValue<double> mRightEyeOffsetX{ mIndex, "Stereo View", "right eye offset x" };
SettingValue<double> mRightEyeOffsetY{ mIndex, "Stereo View", "right eye offset y" }; SettingValue<double> mRightEyeOffsetY{ mIndex, "Stereo View", "right eye offset y" };
SettingValue<double> mRightEyeOffsetZ{ mIndex, "Stereo View", "right eye offset z" }; SettingValue<double> mRightEyeOffsetZ{ mIndex, "Stereo View", "right eye offset z" };
@ -50,14 +50,14 @@ namespace Settings
makeClampSanitizerDouble(-1, 1) }; makeClampSanitizerDouble(-1, 1) };
SettingValue<double> mRightEyeOrientationW{ mIndex, "Stereo View", "right eye orientation w", SettingValue<double> mRightEyeOrientationW{ mIndex, "Stereo View", "right eye orientation w",
makeClampSanitizerDouble(-1, 1) }; makeClampSanitizerDouble(-1, 1) };
SettingValue<double> mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", SettingValue<float> mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", SettingValue<float> mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", SettingValue<float> mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
SettingValue<double> mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", SettingValue<float> mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down",
makeClampSanitizerDouble(-osg::PI, osg::PI) }; makeClampSanitizerFloat(-osg::PIf, osg::PIf) };
}; };
} }

View file

@ -1,8 +1,12 @@
#ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H
#define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H
#include "components/settings/sanitizerimpl.hpp" #include <components/settings/sanitizerimpl.hpp>
#include "components/settings/settingvalue.hpp" #include <components/settings/screenshotsettings.hpp>
#include <components/settings/settingvalue.hpp>
#include <components/settings/windowmode.hpp>
#include <components/sdlutil/vsyncmode.hpp>
#include <osg/Math> #include <osg/Math>
#include <osg/Vec2f> #include <osg/Vec2f>
@ -20,16 +24,16 @@ namespace Settings
SettingValue<int> mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) }; SettingValue<int> mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) };
SettingValue<int> mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) }; SettingValue<int> mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) };
SettingValue<int> mWindowMode{ mIndex, "Video", "window mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; SettingValue<WindowMode> mWindowMode{ mIndex, "Video", "window mode" };
SettingValue<int> mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) }; SettingValue<int> mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) };
SettingValue<bool> mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" }; SettingValue<bool> mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" };
SettingValue<bool> mWindowBorder{ mIndex, "Video", "window border" }; SettingValue<bool> mWindowBorder{ mIndex, "Video", "window border" };
SettingValue<int> mAntialiasing{ mIndex, "Video", "antialiasing", makeMaxSanitizerInt(0) }; SettingValue<int> mAntialiasing{ mIndex, "Video", "antialiasing", makeMaxSanitizerInt(0) };
SettingValue<int> mVsyncMode{ mIndex, "Video", "vsync mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; SettingValue<SDLUtil::VSyncMode> mVsyncMode{ mIndex, "Video", "vsync mode" };
SettingValue<float> mFramerateLimit{ mIndex, "Video", "framerate limit", makeMaxSanitizerFloat(0) }; SettingValue<float> mFramerateLimit{ mIndex, "Video", "framerate limit", makeMaxSanitizerFloat(0) };
SettingValue<float> mContrast{ mIndex, "Video", "contrast", makeMaxStrictSanitizerFloat(0) }; SettingValue<float> mContrast{ mIndex, "Video", "contrast", makeMaxStrictSanitizerFloat(0) };
SettingValue<float> mGamma{ mIndex, "Video", "gamma", makeMaxStrictSanitizerFloat(0) }; SettingValue<float> mGamma{ mIndex, "Video", "gamma", makeMaxStrictSanitizerFloat(0) };
SettingValue<std::string> mScreenshotType{ mIndex, "Video", "screenshot type" }; SettingValue<ScreenshotSettings> mScreenshotType{ mIndex, "Video", "screenshot type" };
}; };
} }

View file

@ -0,0 +1,29 @@
#ifndef OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H
#define OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H
#include <optional>
#include <ostream>
namespace Settings
{
enum class ScreenshotType
{
Regular,
Cylindrical,
Spherical,
Planet,
Cubemap,
};
struct ScreenshotSettings
{
ScreenshotType mType;
std::optional<int> mWidth;
std::optional<int> mHeight;
std::optional<int> mCubeSize;
auto operator<=>(const ScreenshotSettings& value) const = default;
};
}
#endif

View file

@ -119,6 +119,23 @@ namespace Settings
Log(Debug::Warning) << "Invalid HRTF mode value: " << static_cast<int>(value) << ", fallback to auto (-1)"; Log(Debug::Warning) << "Invalid HRTF mode value: " << static_cast<int>(value) << ", fallback to auto (-1)";
return -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(); CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap();
@ -501,6 +518,16 @@ namespace Settings
setInt(setting, category, toInt(value)); setInt(setting, category, toInt(value));
} }
void Manager::set(std::string_view setting, std::string_view category, WindowMode value)
{
setInt(setting, category, static_cast<int>(value));
}
void Manager::set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value)
{
setInt(setting, category, static_cast<int>(value));
}
void Manager::recordInit(std::string_view setting, std::string_view category) void Manager::recordInit(std::string_view setting, std::string_view category)
{ {
sInitialized.emplace(category, setting); sInitialized.emplace(category, setting);
@ -547,4 +574,28 @@ namespace Settings
Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'"; Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'";
return SceneUtil::LightingMethod::PerObjectUniform; return SceneUtil::LightingMethod::PerObjectUniform;
} }
ScreenshotSettings parseScreenshotSettings(std::string_view value)
{
std::vector<std::string_view> 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<int>(settingArgs[1], 0));
if (settingArgs.size() > 2)
result.mHeight = std::min(10000, Misc::StringUtils::toNumeric<int>(settingArgs[2], 0));
if (settingArgs.size() > 3)
result.mCubeSize = std::min(5000, Misc::StringUtils::toNumeric<int>(settingArgs[3], 0));
return result;
}
} }

View file

@ -5,9 +5,12 @@
#include "gyroscopeaxis.hpp" #include "gyroscopeaxis.hpp"
#include "hrtfmode.hpp" #include "hrtfmode.hpp"
#include "navmeshrendermode.hpp" #include "navmeshrendermode.hpp"
#include "screenshotsettings.hpp"
#include "windowmode.hpp"
#include <components/detournavigator/collisionshapetype.hpp> #include <components/detournavigator/collisionshapetype.hpp>
#include <components/sceneutil/lightingmethod.hpp> #include <components/sceneutil/lightingmethod.hpp>
#include <components/sdlutil/vsyncmode.hpp>
#include <filesystem> #include <filesystem>
#include <set> #include <set>
@ -27,13 +30,6 @@ namespace Files
namespace Settings namespace Settings
{ {
enum class WindowMode
{
Fullscreen = 0,
WindowedFullscreen,
Windowed
};
/// ///
/// \brief Settings management (can change during runtime) /// \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, 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, 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, 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: private:
static std::set<std::pair<std::string_view, std::string_view>> sInitialized; static std::set<std::pair<std::string_view, std::string_view>> sInitialized;
@ -239,6 +237,32 @@ namespace Settings
return HrtfMode::Enable; return HrtfMode::Enable;
return HrtfMode::Disable; return HrtfMode::Disable;
} }
template <>
inline WindowMode Manager::getImpl<WindowMode>(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<WindowMode>(value);
}
template <>
inline SDLUtil::VSyncMode Manager::getImpl<SDLUtil::VSyncMode>(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<SDLUtil::VSyncMode>(value);
}
ScreenshotSettings parseScreenshotSettings(std::string_view value);
template <>
inline ScreenshotSettings Manager::getImpl<ScreenshotSettings>(std::string_view setting, std::string_view category)
{
return parseScreenshotSettings(getString(setting, category));
}
} }
#endif // COMPONENTS_SETTINGS_H #endif // COMPONENTS_SETTINGS_H

View file

@ -42,6 +42,9 @@ namespace Settings
NavMeshRenderMode, NavMeshRenderMode,
LightingMethod, LightingMethod,
HrtfMode, HrtfMode,
WindowMode,
VSyncMode,
ScreenshotSettings,
}; };
template <class T> template <class T>
@ -161,6 +164,24 @@ namespace Settings
return SettingValueType::HrtfMode; return SettingValueType::HrtfMode;
} }
template <>
inline constexpr SettingValueType getSettingValueType<WindowMode>()
{
return SettingValueType::WindowMode;
}
template <>
inline constexpr SettingValueType getSettingValueType<SDLUtil::VSyncMode>()
{
return SettingValueType::VSyncMode;
}
template <>
inline constexpr SettingValueType getSettingValueType<ScreenshotSettings>()
{
return SettingValueType::ScreenshotSettings;
}
inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) inline constexpr std::string_view getSettingValueTypeName(SettingValueType type)
{ {
switch (type) switch (type)
@ -203,6 +224,12 @@ namespace Settings
return "lighting method"; return "lighting method";
case SettingValueType::HrtfMode: case SettingValueType::HrtfMode:
return "hrtf mode"; return "hrtf mode";
case SettingValueType::WindowMode:
return "window mode";
case SettingValueType::VSyncMode:
return "vsync mode";
case SettingValueType::ScreenshotSettings:
return "screenshot settings";
} }
return "unsupported"; return "unsupported";
} }
@ -361,6 +388,17 @@ namespace Settings
} }
return stream; return stream;
} }
else if constexpr (std::is_same_v<T, ScreenshotSettings>)
{
stream << "ScreenshotSettings{ .mType = " << static_cast<int>(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 else
return stream << value.mValue; return stream << value.mValue;
} }

View file

@ -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

View file

@ -85,7 +85,7 @@ namespace Stereo
} }
} }
StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) StereoFrustumManager::StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera)
: mCamera(camera) : mCamera(camera)
, mShadowTechnique(nullptr) , mShadowTechnique(nullptr)
, mShadowFrustumCallback(nullptr) , mShadowFrustumCallback(nullptr)
@ -95,7 +95,7 @@ namespace Stereo
mMultiviewFrustumCallback = std::make_unique<MultiviewFrustumCallback>(this, camera); mMultiviewFrustumCallback = std::make_unique<MultiviewFrustumCallback>(this, camera);
} }
if (Settings::Manager::getBool("shared shadow maps", "Stereo")) if (sharedShadowMaps)
{ {
mShadowFrustumCallback = new ShadowFrustumCallback(this); mShadowFrustumCallback = new ShadowFrustumCallback(this);
auto* renderer = static_cast<osgViewer::Renderer*>(mCamera->getRenderer()); auto* renderer = static_cast<osgViewer::Renderer*>(mCamera->getRenderer());

View file

@ -45,7 +45,7 @@ namespace Stereo
class StereoFrustumManager class StereoFrustumManager
{ {
public: public:
StereoFrustumManager(osg::Camera* camera); StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera);
~StereoFrustumManager(); ~StereoFrustumManager();
void update(std::array<osg::Matrix, 2> projections); void update(std::array<osg::Matrix, 2> projections);

View file

@ -124,12 +124,12 @@ namespace Stereo
} }
} }
void setVertexBufferHint(bool enableMultiview) void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview)
{ {
if (getStereo() && enableMultiview) if (getStereo() && enableMultiview)
{ {
auto* ds = osg::DisplaySettings::instance().get(); 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) && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE)
{ {
// Note that this only works if this code is executed before realize() is called on the viewer. // Note that this only works if this code is executed before realize() is called on the viewer.

View file

@ -37,7 +37,7 @@ namespace Stereo
void configureExtensions(unsigned int contextID, bool enableMultiview); void configureExtensions(unsigned int contextID, bool enableMultiview);
//! Sets the appropriate vertex buffer hint on OSG's display settings if needed //! 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 //! Creates a Texture2D as a texture view into a Texture2DArray
osg::ref_ptr<osg::Texture2D> createTextureView_Texture2DFromTexture2DArray( osg::ref_ptr<osg::Texture2D> createTextureView_Texture2DFromTexture2DArray(

View file

@ -114,13 +114,15 @@ namespace Stereo
return *sInstance; return *sInstance;
} }
Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo) Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far)
: mViewer(viewer) : mViewer(viewer)
, mMainCamera(mViewer->getCamera()) , mMainCamera(mViewer->getCamera())
, mUpdateCallback(new StereoUpdateCallback(this)) , mUpdateCallback(new StereoUpdateCallback(this))
, mMasterProjectionMatrix(osg::Matrixd::identity()) , mMasterProjectionMatrix(osg::Matrixd::identity())
, mEyeResolutionOverriden(false) , mEyeResolutionOverriden(false)
, mEyeResolutionOverride(0, 0) , mEyeResolutionOverride(0, 0)
, mNear(near)
, mFar(far)
, mFrustumManager(nullptr) , mFrustumManager(nullptr)
, mUpdateViewCallback(nullptr) , mUpdateViewCallback(nullptr)
{ {
@ -132,13 +134,13 @@ namespace Stereo
Manager::~Manager() {} 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(); auto ci = gc->getState()->getContextID();
configureExtensions(ci, enableMultiview); configureExtensions(ci, enableMultiview);
mMainCamera->addUpdateCallback(mUpdateCallback); mMainCamera->addUpdateCallback(mUpdateCallback);
mFrustumManager = std::make_unique<StereoFrustumManager>(mViewer->getCamera()); mFrustumManager = std::make_unique<StereoFrustumManager>(sharedShadowMaps, mViewer->getCamera());
if (getMultiview()) if (getMultiview())
setupOVRMultiView2Technique(); setupOVRMultiView2Technique();
@ -273,7 +275,7 @@ namespace Stereo
void Manager::updateStereoFramebuffer() void Manager::updateStereoFramebuffer()
{ {
// VR-TODO: in VR, still need to have this framebuffer attached before the postprocessor is created // 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(); // auto eyeRes = eyeResolution();
// if (mMultiviewFramebuffer) // if (mMultiviewFramebuffer)
@ -289,20 +291,17 @@ namespace Stereo
void Manager::update() void Manager::update()
{ {
const double near_ = Settings::camera().mNearClip;
const double far_ = Settings::camera().mViewingDistance;
if (mUpdateViewCallback) if (mUpdateViewCallback)
{ {
mUpdateViewCallback->updateView(mView[0], mView[1]); mUpdateViewCallback->updateView(mView[0], mView[1]);
mViewOffsetMatrix[0] = mView[0].viewMatrix(true); mViewOffsetMatrix[0] = mView[0].viewMatrix(true);
mViewOffsetMatrix[1] = mView[1].viewMatrix(true); mViewOffsetMatrix[1] = mView[1].viewMatrix(true);
mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); mProjectionMatrix[0] = mView[0].perspectiveMatrix(mNear, mFar, false);
mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); mProjectionMatrix[1] = mView[1].perspectiveMatrix(mNear, mFar, false);
if (SceneUtil::AutoDepth::isReversed()) if (SceneUtil::AutoDepth::isReversed())
{ {
mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(mNear, mFar, true);
mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(mNear, mFar, true);
} }
View masterView; View masterView;
@ -310,7 +309,7 @@ namespace Stereo
masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); 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.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft);
masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); 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); mMainCamera->setProjectionMatrix(projectionMatrix);
} }
else else
@ -394,60 +393,30 @@ namespace Stereo
right = mRight; right = mRight;
} }
InitializeStereoOperation::InitializeStereoOperation() InitializeStereoOperation::InitializeStereoOperation(const Settings& settings)
: GraphicsOperation("InitializeStereoOperation", false) : 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 // 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 // hint has to be set before realize is called on the osg viewer, and so has to
// be done here instead. // be done here instead.
Stereo::setVertexBufferHint(Settings::Manager::getBool("multiview", "Stereo")); Stereo::setVertexBufferHint(settings.mMultiview, settings.mAllowDisplayListsForMultiview);
} }
void InitializeStereoOperation::operator()(osg::GraphicsContext* graphicsContext) void InitializeStereoOperation::operator()(osg::GraphicsContext* graphicsContext)
{ {
auto& sm = Stereo::Manager::instance(); auto& sm = Stereo::Manager::instance();
if (Settings::Manager::getBool("use custom view", "Stereo")) if (mCustomView.has_value())
{ sm.setUpdateViewCallback(
Stereo::View left; std::make_shared<Stereo::Manager::CustomViewCallback>(mCustomView->mLeft, mCustomView->mRight));
Stereo::View right;
left.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); if (mEyeResolution.has_value())
left.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); sm.overrideEyeResolution(*mEyeResolution);
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");
right.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); sm.initializeStereo(graphicsContext, mMultiview, mSharedShadowMaps);
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<Stereo::Manager::CustomViewCallback>(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"));
} }
} }

View file

@ -76,15 +76,23 @@ namespace Stereo
//! @Param viewer the osg viewer whose stereo should be managed. //! @Param viewer the osg viewer whose stereo should be managed.
//! @Param enableStereo whether or not stereo should be enabled. //! @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. //! @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(); ~Manager();
//! Called during update traversal //! Called during update traversal
void update(); 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, //! 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. //! 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 //! Callback that updates stereo configuration during the update pass
void setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb); void setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb);
@ -138,6 +146,8 @@ namespace Stereo
std::shared_ptr<MultiviewFramebuffer> mMultiviewFramebuffer; std::shared_ptr<MultiviewFramebuffer> mMultiviewFramebuffer;
bool mEyeResolutionOverriden; bool mEyeResolutionOverriden;
osg::Vec2i mEyeResolutionOverride; osg::Vec2i mEyeResolutionOverride;
double mNear;
double mFar;
std::array<View, 2> mView; std::array<View, 2> mView;
std::array<osg::Matrixd, 2> mViewOffsetMatrix; std::array<osg::Matrixd, 2> mViewOffsetMatrix;
@ -153,13 +163,34 @@ namespace Stereo
osg::ref_ptr<Identifier> mIdentifierRight = new Identifier(); osg::ref_ptr<Identifier> mIdentifierRight = new Identifier();
}; };
struct CustomView
{
Stereo::View mLeft;
Stereo::View mRight;
};
struct Settings
{
bool mMultiview;
bool mAllowDisplayListsForMultiview;
bool mSharedShadowMaps;
std::optional<CustomView> mCustomView;
std::optional<osg::Vec2i> mEyeResolution;
};
//! Performs stereo-specific initialization operations. //! Performs stereo-specific initialization operations.
class InitializeStereoOperation final : public osg::GraphicsOperation class InitializeStereoOperation final : public osg::GraphicsOperation
{ {
public: public:
InitializeStereoOperation(); explicit InitializeStereoOperation(const Settings& settings);
void operator()(osg::GraphicsContext* graphicsContext) override; void operator()(osg::GraphicsContext* graphicsContext) override;
private:
bool mMultiview;
bool mSharedShadowMaps;
std::optional<CustomView> mCustomView;
std::optional<osg::Vec2i> mEyeResolution;
}; };
} }

View file

@ -31,13 +31,15 @@ This setting can either be activated in the OpenMW launcher or changed in `setti
Variables. Variables.
+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +--------------+--------------------------------------------------------------------------------------------------------+---------+---------+
| Name | Description | Type | Default | | Name | Description | Type | Default |
+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +--------------+--------------------------------------------------------------------------------------------------------+---------+---------+
| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | | 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 | | 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. Example usage.
@ -48,6 +50,7 @@ Example usage.
"soft_effect" : { "soft_effect" : {
"size": 250, "size": 250,
"falloff" : false, "falloff" : false,
"falloffDepth": 5,
} }
} }
} }

View file

@ -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. 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, 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. 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).

View file

@ -919,13 +919,26 @@
-- @function [parent=#Player] getCrimeLevel -- @function [parent=#Player] getCrimeLevel
-- @param openmw.core#GameObject player -- @param openmw.core#GameObject player
-- @return #number -- @return #number
--- ---
-- Whether the character generation for this player is finished. -- Whether the character generation for this player is finished.
-- @function [parent=#Player] isCharGenFinished -- @function [parent=#Player] isCharGenFinished
-- @param openmw.core#GameObject player -- @param openmw.core#GameObject player
-- @return #boolean -- @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. -- Returns a list containing quests @{#PlayerQuest} for the specified player, indexed by quest ID.
-- @function [parent=#Player] quests -- @function [parent=#Player] quests
@ -1868,6 +1881,9 @@
--- Functions for @{#ESM4Miscellaneous} objects --- Functions for @{#ESM4Miscellaneous} objects
-- @field [parent=#types] #ESM4Miscellaneous ESM4Miscellaneous -- @field [parent=#types] #ESM4Miscellaneous ESM4Miscellaneous
--- Functions for @{#ESM4MovableStatic} objects
-- @field [parent=#types] #ESM4MovableStatic ESM4MovableStatic
--- Functions for @{#ESM4Potion} objects --- Functions for @{#ESM4Potion} objects
-- @field [parent=#types] #ESM4Potion ESM4Potion -- @field [parent=#types] #ESM4Potion ESM4Potion

View file

@ -640,7 +640,7 @@ contrast = 1.0
# Video gamma setting. (>0.0). No effect in Linux. # Video gamma setting. (>0.0). No effect in Linux.
gamma = 1.0 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 width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200)
screenshot type = regular screenshot type = regular

View file

@ -38,6 +38,7 @@ uniform float alphaRef;
uniform sampler2D opaqueDepthTex; uniform sampler2D opaqueDepthTex;
uniform float particleSize; uniform float particleSize;
uniform bool particleFade; uniform bool particleFade;
uniform float softFalloffDepth;
#endif #endif
void main() void main()
@ -71,7 +72,8 @@ void main()
far, far,
texture2D(opaqueDepthTex, screenCoords).x, texture2D(opaqueDepthTex, screenCoords).x,
particleSize, particleSize,
particleFade particleFade,
softFalloffDepth
); );
#endif #endif

View file

@ -98,6 +98,7 @@ varying vec3 passNormal;
uniform sampler2D opaqueDepthTex; uniform sampler2D opaqueDepthTex;
uniform float particleSize; uniform float particleSize;
uniform bool particleFade; uniform bool particleFade;
uniform float softFalloffDepth;
#endif #endif
#if @particleOcclusion #if @particleOcclusion
@ -256,7 +257,8 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal);
far, far,
texture2D(opaqueDepthTex, screenCoords).x, texture2D(opaqueDepthTex, screenCoords).x,
particleSize, particleSize,
particleFade particleFade,
softFalloffDepth
); );
#endif #endif

View file

@ -19,7 +19,8 @@ float calcSoftParticleFade(
float far, float far,
float depth, float depth,
float size, float size,
bool fade bool fade,
float softFalloffDepth
) )
{ {
float euclidianDepth = length(viewPos); float euclidianDepth = length(viewPos);
@ -32,13 +33,12 @@ float calcSoftParticleFade(
float falloff = size * falloffMultiplier; float falloff = size * falloffMultiplier;
float delta = particleDepth - sceneDepth; float delta = particleDepth - sceneDepth;
const float nearMult = 300.0;
float viewBias = 1.0; float viewBias = 1.0;
if (fade) if (fade)
{ {
float VdotN = dot(viewDir, viewNormal); 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; const float shift = 0.845;