Merge branch 'OpenMW:master' into master

This commit is contained in:
Andy Lanzone 2025-05-31 15:29:25 -07:00 committed by GitHub
commit 6185683ca3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 227 additions and 145 deletions

View File

@ -234,6 +234,7 @@
Bug #8445: Launcher crashes on exit when cell name loading thread is still running Bug #8445: Launcher crashes on exit when cell name loading thread is still running
Bug #8462: Crashes when resizing the window on macOS Bug #8462: Crashes when resizing the window on macOS
Bug #8465: Blue screen w/ antialiasing and post-processing on macOS Bug #8465: Blue screen w/ antialiasing and post-processing on macOS
Bug #8503: Camera does not handle NaN gracefully
Feature #1415: Infinite fall failsafe Feature #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking

View File

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 73) set(OPENMW_LUA_API_REVISION 75)
set(OPENMW_POSTPROCESSING_API_REVISION 2) set(OPENMW_POSTPROCESSING_API_REVISION 2)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

View File

@ -365,15 +365,15 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
QIcon containsDataIcon(":/images/openmw-plugin.png"); QIcon containsDataIcon(":/images/openmw-plugin.png");
QProgressDialog progressBar("Adding data directories", {}, 0, directories.count(), this); QProgressDialog progressBar("Adding data directories", {}, 0, static_cast<int>(directories.size()), this);
progressBar.setWindowModality(Qt::WindowModal); progressBar.setWindowModality(Qt::WindowModal);
progressBar.setValue(0);
std::unordered_set<QString> visitedDirectories; std::unordered_set<QString> visitedDirectories;
for (const Config::SettingValue& currentDir : directories) for (qsizetype i = 0; i < directories.size(); ++i)
{ {
progressBar.setValue(progressBar.value() + 1); progressBar.setValue(static_cast<int>(i));
const Config::SettingValue& currentDir = directories.at(i);
if (!visitedDirectories.insert(currentDir.value).second) if (!visitedDirectories.insert(currentDir.value).second)
continue; continue;
@ -436,6 +436,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
} }
item->setToolTip(tooltip.join('\n')); item->setToolTip(tooltip.join('\n'));
} }
progressBar.setValue(progressBar.maximum());
mSelector->sortFiles(); mSelector->sortFiles();
QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList(); QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
@ -1001,7 +1002,11 @@ bool Launcher::DataFilesPage::showDeleteMessageBox(const QString& text)
void Launcher::DataFilesPage::slotAddonDataChanged() void Launcher::DataFilesPage::slotAddonDataChanged()
{ {
QStringList selectedFiles = selectedFilePaths(); const ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
QStringList selectedFiles;
for (const ContentSelectorModel::EsmFile* item : items)
selectedFiles.append(item->filePath());
if (mSelectedFiles != selectedFiles) if (mSelectedFiles != selectedFiles)
{ {
const std::lock_guard lock(mReloadCellsMutex); const std::lock_guard lock(mReloadCellsMutex);
@ -1013,6 +1018,7 @@ void Launcher::DataFilesPage::slotAddonDataChanged()
void Launcher::DataFilesPage::reloadCells() void Launcher::DataFilesPage::reloadCells()
{ {
QStringList selectedFiles;
std::unique_lock lock(mReloadCellsMutex); std::unique_lock lock(mReloadCellsMutex);
while (true) while (true)
@ -1025,16 +1031,26 @@ void Launcher::DataFilesPage::reloadCells()
if (!std::exchange(mReloadCells, false)) if (!std::exchange(mReloadCells, false))
continue; continue;
QStringList selectedFiles = mSelectedFiles; const QStringList newSelectedFiles = mSelectedFiles;
lock.unlock(); lock.unlock();
QStringList filteredFiles;
for (const QString& v : newSelectedFiles)
if (QFile::exists(v))
filteredFiles.append(v);
if (selectedFiles != filteredFiles)
{
selectedFiles = std::move(filteredFiles);
CellNameLoader cellNameLoader; CellNameLoader cellNameLoader;
QSet<QString> set = cellNameLoader.getCellNames(selectedFiles); QSet<QString> set = cellNameLoader.getCellNames(selectedFiles);
QStringList cellNamesList(set.begin(), set.end()); QStringList cellNamesList(set.begin(), set.end());
std::sort(cellNamesList.begin(), cellNamesList.end()); std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(std::move(cellNamesList)); emit signalLoadedCellsChanged(std::move(cellNamesList));
}
lock.lock(); lock.lock();

View File

@ -465,7 +465,6 @@ namespace MWGui
void Console::findOccurrence(const SearchDirection direction) void Console::findOccurrence(const SearchDirection direction)
{ {
if (mCurrentSearchTerm.empty()) if (mCurrentSearchTerm.empty())
{ {
return; return;
@ -478,17 +477,16 @@ namespace MWGui
size_t firstIndex{ 0 }; size_t firstIndex{ 0 };
size_t lastIndex{ historyText.length() }; size_t lastIndex{ historyText.length() };
// If search is not the first adjust the range based on the direction and previous occurrence. // If this isn't the first search, adjust the range based on the previous occurrence.
if (mCurrentOccurrenceIndex != std::string::npos) if (mCurrentOccurrenceIndex != std::string::npos)
{ {
if (direction == SearchDirection::Forward && mCurrentOccurrenceIndex > 1) if (direction == SearchDirection::Forward)
{ {
firstIndex = mCurrentOccurrenceIndex + mCurrentOccurrenceLength; firstIndex = mCurrentOccurrenceIndex + mCurrentOccurrenceLength;
} }
else if (direction == SearchDirection::Reverse else if (direction == SearchDirection::Reverse)
&& (historyText.length() - mCurrentOccurrenceIndex) > mCurrentOccurrenceLength)
{ {
lastIndex = mCurrentOccurrenceIndex - 1; lastIndex = mCurrentOccurrenceIndex;
} }
} }
@ -523,6 +521,13 @@ namespace MWGui
void Console::findInHistoryText(const std::string& historyText, const SearchDirection direction, void Console::findInHistoryText(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex) const size_t firstIndex, const size_t lastIndex)
{ {
if (lastIndex <= firstIndex)
{
mCurrentOccurrenceIndex = std::string::npos;
mCurrentOccurrenceLength = 0;
return;
}
if (mRegExSearch) if (mRegExSearch)
{ {
findWithRegex(historyText, direction, firstIndex, lastIndex); findWithRegex(historyText, direction, firstIndex, lastIndex);
@ -570,7 +575,7 @@ namespace MWGui
const size_t firstIndex, const size_t lastIndex) const size_t firstIndex, const size_t lastIndex)
{ {
// Search in given text interval for search term // Search in given text interval for search term
const size_t substringLength{ (lastIndex - firstIndex) + 1 }; const size_t substringLength = lastIndex - firstIndex;
const std::string_view historyTextView((historyText.c_str() + firstIndex), substringLength); const std::string_view historyTextView((historyText.c_str() + firstIndex), substringLength);
if (direction == SearchDirection::Forward) if (direction == SearchDirection::Forward)
{ {

View File

@ -2,6 +2,7 @@
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/utilpackage.hpp> #include <components/lua/utilpackage.hpp>
#include <components/misc/finitenumbers.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -11,11 +12,12 @@
namespace MWLua namespace MWLua
{ {
using CameraMode = MWRender::Camera::Mode; using CameraMode = MWRender::Camera::Mode;
sol::table initCameraPackage(sol::state_view lua) sol::table initCameraPackage(sol::state_view lua)
{ {
using FiniteFloat = Misc::FiniteFloat;
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager();
@ -49,26 +51,27 @@ namespace MWLua
api["getRoll"] = [camera]() { return -camera->getRoll(); }; api["getRoll"] = [camera]() { return -camera->getRoll(); };
api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); }; api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); };
api["setPitch"] = [camera](float v) { api["setPitch"] = [camera](const FiniteFloat v) {
camera->setPitch(-v, true); camera->setPitch(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson) if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation(); camera->calculateDeferredRotation();
}; };
api["setYaw"] = [camera](float v) { api["setYaw"] = [camera](const FiniteFloat v) {
camera->setYaw(-v, true); camera->setYaw(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson) if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation(); camera->calculateDeferredRotation();
}; };
api["setRoll"] = [camera](float v) { camera->setRoll(-v); }; api["setRoll"] = [camera](const FiniteFloat v) { camera->setRoll(-v); };
api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); }; api["setExtraPitch"] = [camera](const FiniteFloat v) { camera->setExtraPitch(-v); };
api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); }; api["setExtraYaw"] = [camera](const FiniteFloat v) { camera->setExtraYaw(-v); };
api["setExtraRoll"] = [camera](float v) { camera->setExtraRoll(-v); }; api["setExtraRoll"] = [camera](const FiniteFloat v) { camera->setExtraRoll(-v); };
api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); }; api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); };
api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); }; api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); };
api["getExtraRoll"] = [camera]() { return -camera->getExtraRoll(); }; api["getExtraRoll"] = [camera]() { return -camera->getExtraRoll(); };
api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); }; api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); };
api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); }; api["setPreferredThirdPersonDistance"]
= [camera](const FiniteFloat v) { camera->setPreferredCameraDistance(v); };
api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); }; api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); };
api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); }; api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); };
@ -76,7 +79,7 @@ namespace MWLua
api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); }; api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); };
api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); }; api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); };
api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); }; api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); };
api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); }; api["setFocalTransitionSpeed"] = [camera](const FiniteFloat v) { camera->setFocalPointTransitionSpeed(v); };
api["instantTransition"] = [camera]() { camera->instantTransition(); }; api["instantTransition"] = [camera]() { camera->instantTransition(); };
api["getCollisionType"] = [camera]() { return camera->getCollisionType(); }; api["getCollisionType"] = [camera]() { return camera->getCollisionType(); };
@ -86,11 +89,12 @@ namespace MWLua
api["getFieldOfView"] api["getFieldOfView"]
= [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); }; = [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); };
api["setFieldOfView"] api["setFieldOfView"]
= [renderingManager](float v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); }; = [renderingManager](const FiniteFloat v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); };
api["getBaseViewDistance"] = [] { return Settings::camera().mViewingDistance.get(); }; api["getBaseViewDistance"] = [] { return Settings::camera().mViewingDistance.get(); };
api["getViewDistance"] = [renderingManager]() { return renderingManager->getViewDistance(); }; api["getViewDistance"] = [renderingManager]() { return renderingManager->getViewDistance(); };
api["setViewDistance"] = [renderingManager](float d) { renderingManager->setViewDistance(d, true); }; api["setViewDistance"]
= [renderingManager](const FiniteFloat d) { renderingManager->setViewDistance(d, true); };
api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; };

View File

@ -100,7 +100,8 @@ namespace MWLua
stats.land(true); stats.land(true);
stats.setTeleported(true); stats.setTeleported(true);
world->getPlayer().setTeleported(true); world->getPlayer().setTeleported(true);
world->changeToCell(destCell->getCell()->getId(), toPos(pos, rot), false); bool differentCell = ptr.getCell() != destCell;
world->changeToCell(destCell->getCell()->getId(), toPos(pos, rot), false, differentCell);
MWWorld::Ptr newPtr = world->getPlayerPtr(); MWWorld::Ptr newPtr = world->getPlayerPtr();
world->moveObject(newPtr, pos); world->moveObject(newPtr, pos);
world->rotateObject(newPtr, rot); world->rotateObject(newPtr, rot);
@ -350,7 +351,7 @@ namespace MWLua
throw std::runtime_error( throw std::runtime_error(
"The argument of `activateBy` must be an actor who activates the object. Got: " "The argument of `activateBy` must be an actor who activates the object. Got: "
+ actor.toString()); + actor.toString());
if (objPtr.getRefData().activate())
MWBase::Environment::get().getLuaManager()->objectActivated(objPtr, actorPtr); MWBase::Environment::get().getLuaManager()->objectActivated(objPtr, actorPtr);
}; };

View File

@ -160,26 +160,31 @@ namespace MWLua
else else
return LuaUtil::toLuaIndex(index); return LuaUtil::toLuaIndex(index);
}; };
layersTable["insertAfter"] = [context]( layersTable["insertAfter"] = [context](std::string afterName, std::string_view name, const sol::object& opt) {
std::string_view afterName, std::string_view name, const sol::object& opt) {
LuaUi::Layer::Options options; LuaUi::Layer::Options options;
options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true);
context.mLuaManager->addAction(
[=]() {
size_t index = LuaUi::Layer::indexOf(afterName); size_t index = LuaUi::Layer::indexOf(afterName);
if (index == LuaUi::Layer::count()) if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found")); throw std::logic_error(
index++; Misc::StringUtils::format("Couldn't insert after non-existent layer %s", afterName));
context.mLuaManager->addAction( LuaUi::Layer::insert(index + 1, name, options);
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); },
"Insert after UI layer");
}; };
layersTable["insertBefore"] = [context]( layersTable["insertBefore"] = [context](std::string beforeName, std::string_view name, const sol::object& opt) {
std::string_view beforename, std::string_view name, const sol::object& opt) {
LuaUi::Layer::Options options; LuaUi::Layer::Options options;
options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true);
size_t index = LuaUi::Layer::indexOf(beforename);
if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found"));
context.mLuaManager->addAction( context.mLuaManager->addAction(
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); [=]() {
size_t index = LuaUi::Layer::indexOf(beforeName);
if (index == LuaUi::Layer::count())
throw std::logic_error(
Misc::StringUtils::format("Couldn't insert before non-existent layer %s", beforeName));
LuaUi::Layer::insert(index, name, options);
},
"Insert before UI layer");
}; };
sol::table layers = LuaUtil::makeReadOnly(layersTable); sol::table layers = LuaUtil::makeReadOnly(layersTable);
sol::table layersMeta = layers[sol::metatable_key]; sol::table layersMeta = layers[sol::metatable_key];

View File

@ -1388,6 +1388,7 @@ namespace MWMechanics
// Note: we do not disable unequipping animation automatically to avoid body desync // Note: we do not disable unequipping animation automatically to avoid body desync
weapgroup = getWeaponAnimation(mWeaponType); weapgroup = getWeaponAnimation(mWeaponType);
int unequipMask = MWRender::BlendMask_All; int unequipMask = MWRender::BlendMask_All;
mUpperBodyState = UpperBodyState::Unequipping;
bool useShieldAnims = mAnimation->useShieldAnimations(); bool useShieldAnims = mAnimation->useShieldAnimations();
if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell
&& !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell)) && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell))
@ -1402,7 +1403,6 @@ namespace MWMechanics
mAnimation->disable(weapgroup); mAnimation->disable(weapgroup);
playBlendedAnimation( playBlendedAnimation(
weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperBodyState::Unequipping;
mAnimation->detachArrow(); mAnimation->detachArrow();
@ -1447,6 +1447,7 @@ namespace MWMechanics
{ {
mAnimation->showWeapons(false); mAnimation->showWeapons(false);
int equipMask = MWRender::BlendMask_All; int equipMask = MWRender::BlendMask_All;
mUpperBodyState = UpperBodyState::Equipping;
if (useShieldAnims && weaptype != ESM::Weapon::Spell) if (useShieldAnims && weaptype != ESM::Weapon::Spell)
{ {
equipMask = equipMask | ~MWRender::BlendMask_LeftArm; equipMask = equipMask | ~MWRender::BlendMask_LeftArm;
@ -1459,7 +1460,6 @@ namespace MWMechanics
playBlendedAnimation(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", playBlendedAnimation(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start",
"equip stop", 0.0f, 0); "equip stop", 0.0f, 0);
} }
mUpperBodyState = UpperBodyState::Equipping;
// If we do not have the "equip attach" key, show weapon manually. // If we do not have the "equip attach" key, show weapon manually.
if (weaptype != ESM::Weapon::Spell if (weaptype != ESM::Weapon::Spell

View File

@ -38,19 +38,19 @@ void Config::GameSettings::validatePaths()
mDataDirs.clear(); mDataDirs.clear();
QProgressDialog progressBar("Validating paths", {}, 0, paths.count() + 1); QProgressDialog progressBar("Validating paths", {}, 0, static_cast<int>(paths.size() + 1));
progressBar.setWindowModality(Qt::WindowModal); progressBar.setWindowModality(Qt::WindowModal);
progressBar.setValue(0); progressBar.setValue(0);
for (const auto& dataDir : paths) for (const auto& dataDir : paths)
{ {
progressBar.setValue(progressBar.value() + 1);
if (QDir(dataDir.value).exists()) if (QDir(dataDir.value).exists())
{ {
SettingValue copy = dataDir; SettingValue copy = dataDir;
copy.value = QDir(dataDir.value).canonicalPath(); copy.value = QDir(dataDir.value).canonicalPath();
mDataDirs.append(copy); mDataDirs.append(copy);
} }
progressBar.setValue(progressBar.value() + 1);
} }
// Do the same for data-local // Do the same for data-local

View File

@ -13,6 +13,7 @@
#include <QDirIterator> #include <QDirIterator>
#include <QFont> #include <QFont>
#include <QIODevice> #include <QIODevice>
#include <QProgressDialog>
#include <components/esm/format.hpp> #include <components/esm/format.hpp>
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
@ -116,37 +117,26 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index
if (file == mGameFile) if (file == mGameFile)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
Qt::ItemFlags returnFlags; // files with no dependencies can always be checked
if (file->gameFiles().empty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
// addon can be checked if its gamefile is // Show the file if the game it is for is enabled.
// ... special case, addon with no dependency can be used with any gamefile. // NB: The file may theoretically depend on multiple games.
bool gamefileChecked = false; // Early exit means that a file is visible only if its earliest found game dependency is enabled.
bool noGameFiles = true; // This can be counterintuitive, but it is okay for non-bizarre content setups. And also faster.
for (const QString& fileName : file->gameFiles()) for (const EsmFile* depFile : mFiles)
{ {
for (QListIterator<EsmFile*> dependencyIter(mFiles); dependencyIter.hasNext(); dependencyIter.next()) if (depFile->isGameFile() && file->gameFiles().contains(depFile->fileName(), Qt::CaseInsensitive))
{ {
// compare filenames only. Multiple instances if (!depFile->builtIn() && !depFile->fromAnotherConfigFile() && !mCheckedFiles.contains(depFile))
// of the filename (with different paths) is not relevant here.
EsmFile* depFile = dependencyIter.peekNext();
if (!depFile->isGameFile() || depFile->fileName().compare(fileName, Qt::CaseInsensitive) != 0)
continue;
noGameFiles = false;
if (depFile->builtIn() || depFile->fromAnotherConfigFile() || mCheckedFiles.contains(depFile))
{
gamefileChecked = true;
break; break;
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
} }
} }
if (gamefileChecked || noGameFiles) return Qt::NoItemFlags;
{
returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
}
return returnFlags;
} }
QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int role) const QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int role) const
@ -278,7 +268,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
if (success) if (success)
{ {
success = setCheckState(file->filePath(), value.toBool()); success = setCheckState(file, value.toBool());
emit dataChanged(index, index); emit dataChanged(index, index);
} }
} }
@ -305,7 +295,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
if (setState) if (setState)
{ {
setCheckState(file->filePath(), success); setCheckState(file, success);
emit dataChanged(index, index); emit dataChanged(index, index);
} }
else else
@ -707,14 +697,18 @@ bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) c
void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList) void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList)
{ {
QProgressDialog progressDialog("Setting content list", {}, 0, static_cast<int>(fileList.size()));
progressDialog.setWindowModality(Qt::WindowModal);
progressDialog.setValue(0);
int previousPosition = -1; int previousPosition = -1;
for (const QString& filepath : fileList) for (qsizetype i = 0, n = fileList.size(); i < n; ++i)
{ {
if (setCheckState(filepath, true)) const EsmFile* file = item(fileList[i]);
if (setCheckState(file, true))
{ {
// setCheckState already gracefully handles builtIn and fromAnotherConfigFile // setCheckState already gracefully handles builtIn and fromAnotherConfigFile
// as necessary, move plug-ins in visible list to match sequence of supplied filelist // as necessary, move plug-ins in visible list to match sequence of supplied filelist
const EsmFile* file = item(filepath);
int filePosition = indexFromItem(file).row(); int filePosition = indexFromItem(file).row();
if (filePosition < previousPosition) if (filePosition < previousPosition)
{ {
@ -725,8 +719,11 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
previousPosition = filePosition; previousPosition = filePosition;
} }
} }
progressDialog.setValue(static_cast<int>(i + 1));
} }
emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
refreshModel();
} }
QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors( QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors(
@ -790,18 +787,13 @@ QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
} }
} }
void ContentSelectorModel::ContentModel::refreshModel() void ContentSelectorModel::ContentModel::refreshModel(std::initializer_list<int> roles)
{ {
emit dataChanged(index(0, 0), index(rowCount() - 1, 0)); emit dataChanged(index(0, 0), index(rowCount() - 1, 0), roles);
} }
bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, bool checkState) bool ContentSelectorModel::ContentModel::setCheckState(const EsmFile* file, bool checkState)
{ {
if (filepath.isEmpty())
return false;
const EsmFile* file = item(filepath);
if (!file || file->builtIn() || file->fromAnotherConfigFile()) if (!file || file->builtIn() || file->fromAnotherConfigFile())
return false; return false;
@ -810,7 +802,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
else else
mCheckedFiles.erase(file); mCheckedFiles.erase(file);
emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); emit dataChanged(indexFromItem(file), indexFromItem(file));
if (file->isGameFile()) if (file->isGameFile())
refreshModel(); refreshModel();
@ -835,10 +827,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
{ {
for (const EsmFile* downstreamFile : mFiles) for (const EsmFile* downstreamFile : mFiles)
{ {
QFileInfo fileInfo(filepath); if (downstreamFile->gameFiles().contains(file->fileName(), Qt::CaseInsensitive))
QString filename = fileInfo.fileName();
if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive))
{ {
mCheckedFiles.erase(downstreamFile); mCheckedFiles.erase(downstreamFile);
@ -878,5 +867,5 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
void ContentSelectorModel::ContentModel::uncheckAll() void ContentSelectorModel::ContentModel::uncheckAll()
{ {
mCheckedFiles.clear(); mCheckedFiles.clear();
emit dataChanged(index(0, 0), index(rowCount(), columnCount()), { Qt::CheckStateRole, Qt::UserRole + 1 }); refreshModel({ Qt::CheckStateRole, Qt::UserRole + 1 });
} }

View File

@ -59,7 +59,7 @@ namespace ContentSelectorModel
void setCurrentGameFile(const EsmFile* file); void setCurrentGameFile(const EsmFile* file);
bool isEnabled(const QModelIndex& index) const; bool isEnabled(const QModelIndex& index) const;
bool setCheckState(const QString& filepath, bool isChecked); bool setCheckState(const EsmFile* file, bool isChecked);
bool isNew(const QString& filepath) const; bool isNew(const QString& filepath) const;
void setNew(const QString& filepath, bool isChecked); void setNew(const QString& filepath, bool isChecked);
void setNonUserContent(const QStringList& fileList); void setNonUserContent(const QStringList& fileList);
@ -67,7 +67,7 @@ namespace ContentSelectorModel
ContentFileList checkedItems() const; ContentFileList checkedItems() const;
void uncheckAll(); void uncheckAll();
void refreshModel(); void refreshModel(std::initializer_list<int> roles = {});
private: private:
void addFile(EsmFile* file); void addFile(EsmFile* file);

View File

@ -2,13 +2,15 @@
ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent) ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent)
: ModelItem(parent) : ModelItem(parent)
, mFileName(fileName)
{ {
setFileName(fileName);
} }
void ContentSelectorModel::EsmFile::setFileName(const QString& fileName) void ContentSelectorModel::EsmFile::setFileName(const QString& fileName)
{ {
mFileName = fileName; mFileName = fileName;
mHasGameExtension = (mFileName.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)
|| mFileName.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive));
} }
void ContentSelectorModel::EsmFile::setAuthor(const QString& author) void ContentSelectorModel::EsmFile::setAuthor(const QString& author)
@ -53,9 +55,7 @@ void ContentSelectorModel::EsmFile::setFromAnotherConfigFile(bool fromAnotherCon
bool ContentSelectorModel::EsmFile::isGameFile() const bool ContentSelectorModel::EsmFile::isGameFile() const
{ {
return (mGameFiles.size() == 0) return mHasGameExtension && mGameFiles.empty();
&& (mFileName.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)
|| mFileName.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive));
} }
QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) const QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) const
@ -108,7 +108,7 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con
switch (prop) switch (prop)
{ {
case FileProperty_FileName: case FileProperty_FileName:
mFileName = value; setFileName(value);
break; break;
case FileProperty_Author: case FileProperty_Author:

View File

@ -102,6 +102,7 @@ namespace ContentSelectorModel
QString mToolTip; QString mToolTip;
bool mBuiltIn = false; bool mBuiltIn = false;
bool mFromAnotherConfigFile = false; bool mFromAnotherConfigFile = false;
bool mHasGameExtension = false;
}; };
} }

View File

@ -7,6 +7,7 @@
#include <QClipboard> #include <QClipboard>
#include <QMenu> #include <QMenu>
#include <QModelIndex> #include <QModelIndex>
#include <QProgressDialog>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool showOMWScripts) ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool showOMWScripts)
@ -156,7 +157,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename)
index = ui->gameFileView->findText(file->fileName()); index = ui->gameFileView->findText(file->fileName());
// verify that the current index is also checked in the model // verify that the current index is also checked in the model
if (!mContentModel->setCheckState(filename, true)) if (!mContentModel->setCheckState(file, true))
{ {
// throw error in case file not found? // throw error in case file not found?
return; return;
@ -292,27 +293,33 @@ void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos
mContextMenu->exec(globalPos); mContextMenu->exec(globalPos);
} }
void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(Qt::CheckState checkState)
{ {
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; const QModelIndexList selectedIndexes = ui->addonView->selectionModel()->selectedIndexes();
for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes())
QProgressDialog progressDialog("Updating content selection", {}, 0, static_cast<int>(selectedIndexes.size()));
progressDialog.setWindowModality(Qt::WindowModal);
progressDialog.setValue(0);
for (qsizetype i = 0, n = selectedIndexes.size(); i < n; ++i)
{ {
QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); const QModelIndex sourceIndex = mAddonProxyModel->mapToSource(selectedIndexes[i]);
if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState) if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState)
{
mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole); mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole);
}
progressDialog.setValue(static_cast<int>(i + 1));
} }
} }
void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems()
{ {
setCheckStateForMultiSelectedItems(false); setCheckStateForMultiSelectedItems(Qt::Unchecked);
} }
void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems()
{ {
setCheckStateForMultiSelectedItems(true); setCheckStateForMultiSelectedItems(Qt::Checked);
} }
void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths()

View File

@ -69,7 +69,7 @@ namespace ContentSelectorView
void buildAddonView(); void buildAddonView();
void buildContextMenu(); void buildContextMenu();
void setGameFileSelected(int index, bool selected); void setGameFileSelected(int index, bool selected);
void setCheckStateForMultiSelectedItems(bool checked); void setCheckStateForMultiSelectedItems(Qt::CheckState checkState);
signals: signals:
void signalCurrentGamefileIndexChanged(int); void signalCurrentGamefileIndexChanged(int);

View File

@ -0,0 +1,45 @@
#ifndef OPENMW_COMPONENTS_MISC_FINITENUMBERS_HPP
#define OPENMW_COMPONENTS_MISC_FINITENUMBERS_HPP
#include <components/lua/luastate.hpp>
#include <cmath>
#include <stdexcept>
namespace Misc
{
struct FiniteFloat
{
float mValue;
FiniteFloat(float v)
{
if (!std::isfinite(v))
throw std::invalid_argument("Value must be a finite number");
mValue = v;
}
operator float() const { return mValue; }
};
}
namespace sol
{
using FiniteFloat = Misc::FiniteFloat;
template <typename Handler>
bool sol_lua_check(
sol::types<FiniteFloat>, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking)
{
bool success = sol::stack::check<float>(L, lua_absindex(L, index), handler);
tracking.use(1);
return success;
}
static FiniteFloat sol_lua_get(sol::types<FiniteFloat>, lua_State* L, int index, sol::stack::record& tracking)
{
float val = sol::stack::get<float>(L, lua_absindex(L, index));
tracking.use(1);
return FiniteFloat(val);
}
}
#endif

View File

@ -104,6 +104,8 @@ namespace SceneUtil
("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(),
static_cast<int>(i))); static_cast<int>(i)));
} }
stateset.addUniform(new osg::Uniform("maximumShadowMapDistance", 0.00001f));
stateset.addUniform(new osg::Uniform("shadowFadeStart", 0.0f));
} }
ShadowManager::ShadowManager(osg::ref_ptr<osg::Group> sceneRoot, osg::ref_ptr<osg::Group> rootNode, ShadowManager::ShadowManager(osg::ref_ptr<osg::Group> sceneRoot, osg::ref_ptr<osg::Group> rootNode,

View File

@ -95,7 +95,7 @@ namespace Terrain
if (!terrainNode) if (!terrainNode)
return; // no terrain defined return; // no terrain defined
TerrainGrid::World::loadCell(x, y); Terrain::World::loadCell(x, y);
mTerrainRoot->addChild(terrainNode); mTerrainRoot->addChild(terrainNode);

View File

@ -7,5 +7,6 @@
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw_aux.time <Package openmw_aux.time>` | everywhere | | Timers and game time utils | |:ref:`openmw_aux.time <Package openmw_aux.time>` | everywhere | | Timers and game time utils |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw_aux.ui <Package openmw_aux.ui>` | by player scripts | | User interface utils | |:ref:`openmw_aux.ui <Package openmw_aux.ui>` | by player and menu | | User interface utils |
| | scripts | |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +---------------------------------------------------------+--------------------+---------------------------------------------------------------+

View File

@ -34,10 +34,10 @@
- | Control, extend, and override skill progression of the - | Control, extend, and override skill progression of the
| player. | player.
* - :ref:`Settings <Interface Settings>` * - :ref:`Settings <Interface Settings>`
- by player and global scripts - by player, menu, and global scripts
- Save, display and track changes of setting values. - Save, display and track changes of setting values.
* - :ref:`MWUI <Interface MWUI>` * - :ref:`MWUI <Interface MWUI>`
- by player scripts - by player and menu scripts
- Morrowind-style UI templates. - Morrowind-style UI templates.
* - :ref:`UI <Interface UI>` * - :ref:`UI <Interface UI>`
- by player scripts - by player scripts

View File

@ -1,43 +1,48 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description | | Package | Can be used | Description |
+============================================================+====================+===============================================================+ +============================================================+====================+===============================================================+
|:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. | |:ref:`openmw.ambient <Package openmw.ambient>` | by player and menu | | Controls background sounds for given player. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ | | scripts | |
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
| | | | that don't depend on the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data |
| | | | between game sessions. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.animation <Package openmw.animation>` | everywhere | | Animation controls | |:ref:`openmw.animation <Package openmw.animation>` | everywhere | | Animation controls |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers and callbacks. | |:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers and callbacks. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.vfs <Package openmw.vfs>` | everywhere | | Read-only access to data directories via VFS. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.markup <Package openmw.markup>` | everywhere | | API to work with markup languages. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ambient <Package openmw.ambient>` | by player scripts | | Controls background sounds for given player. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>`. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.menu <Package openmw.menu>` | by menu scripts | | Main menu functionality, such as managing game saves |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. | |:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls post-process shaders. | |:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.debug <Package openmw.debug>` | by player scripts | | Collection of debug utils. | |:ref:`openmw.debug <Package openmw.debug>` | by player scripts | | Collection of debug utils. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player and menu | | User input. |
| | scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.markup <Package openmw.markup>` | everywhere | | API to work with markup languages. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.menu <Package openmw.menu>` | by menu scripts | | Main menu functionality, such as managing game saves |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.nearby <Package openmw.nearby>` | by local and | | Read-only access to the nearest area of the game world. |
| | player scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls post-process shaders. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.self <Package openmw.self>` | by local and | | Full access to the object the script is attached to. |
| | player scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data |
| | | | between game sessions. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player and menu | | Controls :ref:`user interface <User interface reference>`. |
| | scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
| | | | that don't depend on the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.vfs <Package openmw.vfs>` | everywhere | | Read-only access to data directories via VFS. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+