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 #8462: Crashes when resizing the window 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 #2566: Handle NAM9 records for manual cell references
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_MINOR 49)
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_VERSION_COMMITHASH "")

View File

@ -365,15 +365,15 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
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.setValue(0);
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)
continue;
@ -436,6 +436,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
}
item->setToolTip(tooltip.join('\n'));
}
progressBar.setValue(progressBar.maximum());
mSelector->sortFiles();
QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
@ -1001,7 +1002,11 @@ bool Launcher::DataFilesPage::showDeleteMessageBox(const QString& text)
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)
{
const std::lock_guard lock(mReloadCellsMutex);
@ -1013,6 +1018,7 @@ void Launcher::DataFilesPage::slotAddonDataChanged()
void Launcher::DataFilesPage::reloadCells()
{
QStringList selectedFiles;
std::unique_lock lock(mReloadCellsMutex);
while (true)
@ -1025,16 +1031,26 @@ void Launcher::DataFilesPage::reloadCells()
if (!std::exchange(mReloadCells, false))
continue;
QStringList selectedFiles = mSelectedFiles;
const QStringList newSelectedFiles = mSelectedFiles;
lock.unlock();
CellNameLoader cellNameLoader;
QSet<QString> set = cellNameLoader.getCellNames(selectedFiles);
QStringList cellNamesList(set.begin(), set.end());
std::sort(cellNamesList.begin(), cellNamesList.end());
QStringList filteredFiles;
for (const QString& v : newSelectedFiles)
if (QFile::exists(v))
filteredFiles.append(v);
emit signalLoadedCellsChanged(std::move(cellNamesList));
if (selectedFiles != filteredFiles)
{
selectedFiles = std::move(filteredFiles);
CellNameLoader cellNameLoader;
QSet<QString> set = cellNameLoader.getCellNames(selectedFiles);
QStringList cellNamesList(set.begin(), set.end());
std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(std::move(cellNamesList));
}
lock.lock();

View File

@ -465,7 +465,6 @@ namespace MWGui
void Console::findOccurrence(const SearchDirection direction)
{
if (mCurrentSearchTerm.empty())
{
return;
@ -478,17 +477,16 @@ namespace MWGui
size_t firstIndex{ 0 };
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 (direction == SearchDirection::Forward && mCurrentOccurrenceIndex > 1)
if (direction == SearchDirection::Forward)
{
firstIndex = mCurrentOccurrenceIndex + mCurrentOccurrenceLength;
}
else if (direction == SearchDirection::Reverse
&& (historyText.length() - mCurrentOccurrenceIndex) > mCurrentOccurrenceLength)
else if (direction == SearchDirection::Reverse)
{
lastIndex = mCurrentOccurrenceIndex - 1;
lastIndex = mCurrentOccurrenceIndex;
}
}
@ -523,6 +521,13 @@ namespace MWGui
void Console::findInHistoryText(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
if (lastIndex <= firstIndex)
{
mCurrentOccurrenceIndex = std::string::npos;
mCurrentOccurrenceLength = 0;
return;
}
if (mRegExSearch)
{
findWithRegex(historyText, direction, firstIndex, lastIndex);
@ -570,7 +575,7 @@ namespace MWGui
const size_t firstIndex, const size_t lastIndex)
{
// 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);
if (direction == SearchDirection::Forward)
{

View File

@ -2,6 +2,7 @@
#include <components/lua/luastate.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/misc/finitenumbers.hpp>
#include <components/settings/values.hpp>
#include "../mwbase/environment.hpp"
@ -11,11 +12,12 @@
namespace MWLua
{
using CameraMode = MWRender::Camera::Mode;
sol::table initCameraPackage(sol::state_view lua)
{
using FiniteFloat = Misc::FiniteFloat;
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager();
@ -49,26 +51,27 @@ namespace MWLua
api["getRoll"] = [camera]() { return -camera->getRoll(); };
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);
if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation();
};
api["setYaw"] = [camera](float v) {
api["setYaw"] = [camera](const FiniteFloat v) {
camera->setYaw(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation();
};
api["setRoll"] = [camera](float v) { camera->setRoll(-v); };
api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); };
api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); };
api["setExtraRoll"] = [camera](float v) { camera->setExtraRoll(-v); };
api["setRoll"] = [camera](const FiniteFloat v) { camera->setRoll(-v); };
api["setExtraPitch"] = [camera](const FiniteFloat v) { camera->setExtraPitch(-v); };
api["setExtraYaw"] = [camera](const FiniteFloat v) { camera->setExtraYaw(-v); };
api["setExtraRoll"] = [camera](const FiniteFloat v) { camera->setExtraRoll(-v); };
api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); };
api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); };
api["getExtraRoll"] = [camera]() { return -camera->getExtraRoll(); };
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["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); };
@ -76,7 +79,7 @@ namespace MWLua
api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); };
api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); };
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["getCollisionType"] = [camera]() { return camera->getCollisionType(); };
@ -86,11 +89,12 @@ namespace MWLua
api["getFieldOfView"]
= [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); };
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["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() }; };

View File

@ -100,7 +100,8 @@ namespace MWLua
stats.land(true);
stats.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();
world->moveObject(newPtr, pos);
world->rotateObject(newPtr, rot);
@ -350,8 +351,8 @@ namespace MWLua
throw std::runtime_error(
"The argument of `activateBy` must be an actor who activates the object. Got: "
+ actor.toString());
if (objPtr.getRefData().activate())
MWBase::Environment::get().getLuaManager()->objectActivated(objPtr, actorPtr);
MWBase::Environment::get().getLuaManager()->objectActivated(objPtr, actorPtr);
};
auto isEnabled = [](const ObjectT& o) { return o.ptr().getRefData().isEnabled(); };

View File

@ -160,26 +160,31 @@ namespace MWLua
else
return LuaUtil::toLuaIndex(index);
};
layersTable["insertAfter"] = [context](
std::string_view afterName, std::string_view name, const sol::object& opt) {
layersTable["insertAfter"] = [context](std::string afterName, std::string_view name, const sol::object& opt) {
LuaUi::Layer::Options options;
options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true);
size_t index = LuaUi::Layer::indexOf(afterName);
if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found"));
index++;
context.mLuaManager->addAction(
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
[=]() {
size_t index = LuaUi::Layer::indexOf(afterName);
if (index == LuaUi::Layer::count())
throw std::logic_error(
Misc::StringUtils::format("Couldn't insert after non-existent layer %s", afterName));
LuaUi::Layer::insert(index + 1, name, options);
},
"Insert after UI layer");
};
layersTable["insertBefore"] = [context](
std::string_view beforename, std::string_view name, const sol::object& opt) {
layersTable["insertBefore"] = [context](std::string beforeName, std::string_view name, const sol::object& opt) {
LuaUi::Layer::Options options;
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(
[=, 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 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
weapgroup = getWeaponAnimation(mWeaponType);
int unequipMask = MWRender::BlendMask_All;
mUpperBodyState = UpperBodyState::Unequipping;
bool useShieldAnims = mAnimation->useShieldAnimations();
if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell
&& !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell))
@ -1402,7 +1403,6 @@ namespace MWMechanics
mAnimation->disable(weapgroup);
playBlendedAnimation(
weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperBodyState::Unequipping;
mAnimation->detachArrow();
@ -1447,6 +1447,7 @@ namespace MWMechanics
{
mAnimation->showWeapons(false);
int equipMask = MWRender::BlendMask_All;
mUpperBodyState = UpperBodyState::Equipping;
if (useShieldAnims && weaptype != ESM::Weapon::Spell)
{
equipMask = equipMask | ~MWRender::BlendMask_LeftArm;
@ -1459,7 +1460,6 @@ namespace MWMechanics
playBlendedAnimation(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start",
"equip stop", 0.0f, 0);
}
mUpperBodyState = UpperBodyState::Equipping;
// If we do not have the "equip attach" key, show weapon manually.
if (weaptype != ESM::Weapon::Spell

View File

@ -38,19 +38,19 @@ void Config::GameSettings::validatePaths()
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.setValue(0);
for (const auto& dataDir : paths)
{
progressBar.setValue(progressBar.value() + 1);
if (QDir(dataDir.value).exists())
{
SettingValue copy = dataDir;
copy.value = QDir(dataDir.value).canonicalPath();
mDataDirs.append(copy);
}
progressBar.setValue(progressBar.value() + 1);
}
// Do the same for data-local

View File

@ -13,6 +13,7 @@
#include <QDirIterator>
#include <QFont>
#include <QIODevice>
#include <QProgressDialog>
#include <components/esm/format.hpp>
#include <components/esm3/esmreader.hpp>
@ -116,37 +117,26 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index
if (file == mGameFile)
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
// ... special case, addon with no dependency can be used with any gamefile.
bool gamefileChecked = false;
bool noGameFiles = true;
for (const QString& fileName : file->gameFiles())
// Show the file if the game it is for is enabled.
// NB: The file may theoretically depend on multiple games.
// Early exit means that a file is visible only if its earliest found game dependency is enabled.
// This can be counterintuitive, but it is okay for non-bizarre content setups. And also faster.
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
// 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;
if (!depFile->builtIn() && !depFile->fromAnotherConfigFile() && !mCheckedFiles.contains(depFile))
break;
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
}
}
if (gamefileChecked || noGameFiles)
{
returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
}
return returnFlags;
return Qt::NoItemFlags;
}
QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int role) const
@ -278,7 +268,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
if (success)
{
success = setCheckState(file->filePath(), value.toBool());
success = setCheckState(file, value.toBool());
emit dataChanged(index, index);
}
}
@ -305,7 +295,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
if (setState)
{
setCheckState(file->filePath(), success);
setCheckState(file, success);
emit dataChanged(index, index);
}
else
@ -707,14 +697,18 @@ bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) c
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;
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
// as necessary, move plug-ins in visible list to match sequence of supplied filelist
const EsmFile* file = item(filepath);
int filePosition = indexFromItem(file).row();
if (filePosition < previousPosition)
{
@ -725,8 +719,11 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
previousPosition = filePosition;
}
}
progressDialog.setValue(static_cast<int>(i + 1));
}
emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
refreshModel();
}
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())
return false;
@ -810,7 +802,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
else
mCheckedFiles.erase(file);
emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath)));
emit dataChanged(indexFromItem(file), indexFromItem(file));
if (file->isGameFile())
refreshModel();
@ -835,10 +827,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
{
for (const EsmFile* downstreamFile : mFiles)
{
QFileInfo fileInfo(filepath);
QString filename = fileInfo.fileName();
if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive))
if (downstreamFile->gameFiles().contains(file->fileName(), Qt::CaseInsensitive))
{
mCheckedFiles.erase(downstreamFile);
@ -878,5 +867,5 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
void ContentSelectorModel::ContentModel::uncheckAll()
{
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);
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;
void setNew(const QString& filepath, bool isChecked);
void setNonUserContent(const QStringList& fileList);
@ -67,7 +67,7 @@ namespace ContentSelectorModel
ContentFileList checkedItems() const;
void uncheckAll();
void refreshModel();
void refreshModel(std::initializer_list<int> roles = {});
private:
void addFile(EsmFile* file);

View File

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

View File

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

View File

@ -7,6 +7,7 @@
#include <QClipboard>
#include <QMenu>
#include <QModelIndex>
#include <QProgressDialog>
#include <QSortFilterProxyModel>
ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool showOMWScripts)
@ -156,7 +157,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename)
index = ui->gameFileView->findText(file->fileName());
// 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?
return;
@ -292,27 +293,33 @@ void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos
mContextMenu->exec(globalPos);
}
void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked)
void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(Qt::CheckState checkState)
{
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes())
const QModelIndexList selectedIndexes = 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)
{
mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole);
}
progressDialog.setValue(static_cast<int>(i + 1));
}
}
void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(false);
setCheckStateForMultiSelectedItems(Qt::Unchecked);
}
void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(true);
setCheckStateForMultiSelectedItems(Qt::Checked);
}
void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths()

View File

@ -69,7 +69,7 @@ namespace ContentSelectorView
void buildAddonView();
void buildContextMenu();
void setGameFileSelected(int index, bool selected);
void setCheckStateForMultiSelectedItems(bool checked);
void setCheckStateForMultiSelectedItems(Qt::CheckState checkState);
signals:
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(),
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,

View File

@ -95,7 +95,7 @@ namespace Terrain
if (!terrainNode)
return; // no terrain defined
TerrainGrid::World::loadCell(x, y);
Terrain::World::loadCell(x, y);
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.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
| player.
* - :ref:`Settings <Interface Settings>`
- by player and global scripts
- by player, menu, and global scripts
- Save, display and track changes of setting values.
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- by player and menu scripts
- Morrowind-style UI templates.
* - :ref:`UI <Interface UI>`
- by player scripts

View File

@ -1,43 +1,48 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description |
+============================================================+====================+===============================================================+
|:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other 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.ambient <Package openmw.ambient>` | by player and menu | | Controls background sounds for given player. |
| | scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.animation <Package openmw.animation>` | everywhere | | Animation controls |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|: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.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.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. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+