diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0d96dec1..de7df21fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,7 @@ Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name + Bug #7737: OSG stats are missing some data on loading screens Bug #7742: Governing attribute training limit should use the modified attribute Bug #7753: Editor: Actors Don't Scale According to Their Race Bug #7758: Water walking is not taken into account to compute path cost on the water @@ -230,6 +231,7 @@ Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks Feature #7932: Support two-channel normal maps Feature #7936: Scalable icons in Qt applications + Feature #7953: Allow to change SVG icons colors depending on color scheme Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 62fb29e600..b3fd60b657 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include "model/doc/messages.hpp" @@ -32,6 +33,9 @@ private: { try { + if (event->type() == QEvent::ThemeChange || event->type() == QEvent::PaletteChange) + Misc::ScalableIcon::updateAllIcons(); + return QApplication::notify(receiver, event); } catch (const std::exception& exception) diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index e6323bf1a1..c6e109355f 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -1,5 +1,7 @@ #include "startup.hpp" +#include + #include #include #include @@ -16,7 +18,7 @@ QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QStr QPushButton* button = new QPushButton(this); - button->setIcon(QIcon(icon)); + button->setIcon(Misc::ScalableIcon::load(icon)); button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); @@ -78,7 +80,7 @@ QWidget* CSVDoc::StartupDialogue::createTools() QPushButton* config = new QPushButton(widget); config->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - config->setIcon(QIcon(":startup/configure")); + config->setIcon(Misc::ScalableIcon::load(":startup/configure")); config->setToolTip("Open user settings"); layout->addWidget(config); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 88a33108c0..f6ef02ad6d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -140,13 +141,13 @@ void CSVDoc::View::setupEditMenu() mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, &QAction::changed, this, &View::undoActionChanged); - mUndo->setIcon(QIcon(QString::fromStdString(":menu-undo"))); + mUndo->setIcon(Misc::ScalableIcon::load(":menu-undo")); edit->addAction(mUndo); mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); connect(mRedo, &QAction::changed, this, &View::redoActionChanged); setupShortcut("document-edit-redo", mRedo); - mRedo->setIcon(QIcon(QString::fromStdString(":menu-redo"))); + mRedo->setIcon(Misc::ScalableIcon::load(":menu-redo")); edit->addAction(mRedo); QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences"); @@ -340,7 +341,7 @@ void CSVDoc::View::setupDebugMenu() QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); runDebug->setText(tr("Run OpenMW")); setupShortcut("document-debug-run", runDebug); - runDebug->setIcon(QIcon(QString::fromStdString(":run-openmw"))); + runDebug->setIcon(Misc::ScalableIcon::load(":run-openmw")); QAction* stopDebug = createMenuEntry("Stop OpenMW", ":stop-openmw", debug, "document-debug-shutdown"); connect(stopDebug, &QAction::triggered, this, &View::stop); @@ -374,7 +375,7 @@ QAction* CSVDoc::View::createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* setupShortcut(shortcutName, entry); const std::string iconName = CSMWorld::UniversalId(type).getIcon(); if (!iconName.empty() && iconName != ":placeholder") - entry->setIcon(QIcon(QString::fromStdString(iconName))); + entry->setIcon(Misc::ScalableIcon::load(QString::fromStdString(iconName))); menu->addAction(entry); @@ -387,7 +388,7 @@ QAction* CSVDoc::View::createMenuEntry( QAction* entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); if (!iconName.empty() && iconName != ":placeholder") - entry->setIcon(QIcon(QString::fromStdString(iconName))); + entry->setIcon(Misc::ScalableIcon::load(QString::fromStdString(iconName))); menu->addAction(entry); diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 0d7ab679b2..8076e99e63 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "../../model/prefs/shortcut.hpp" #include "../../model/world/columns.hpp" @@ -44,7 +45,7 @@ CSVFilter::EditWidget::EditWidget(CSMWorld::Data& data, QWidget* parent) mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp); - mHelpAction->setIcon(QIcon(":info")); + mHelpAction->setIcon(Misc::ScalableIcon::load(":info")); addAction(mHelpAction); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 2d79c912f6..bc821ee25a 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -40,6 +40,7 @@ #include #include +#include #include "../../model/prefs/shortcut.hpp" #include "../../model/world/commandmacro.hpp" @@ -253,8 +254,8 @@ void CSVRender::InstanceMode::getSelectionGroup(const int group) CSVRender::InstanceMode::InstanceMode( WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, - "Instance editing", parent) + : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-instance"), + Mask_Reference | Mask_Terrain, "Instance editing", parent) , mSubMode(nullptr) , mSubModeId("move") , mSelectionMode(nullptr) diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index bc999eb633..bf0ef9d181 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -5,10 +5,12 @@ #include +#include + class QWidget; CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent) - : ModeButton(QIcon(":scenetoolbar/transform-move"), + : ModeButton(Misc::ScalableIcon::load(":scenetoolbar/transform-move"), "Move selected instances" "
  • Use {scene-edit-primary} to move instances around freely
  • " "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 58523ab595..062882bd04 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -169,8 +170,8 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::Sce /// \todo replace EditMode with suitable subclasses tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); - const QIcon vertexIcon = QIcon(":scenetoolbar/editing-terrain-vertex-paint"); - const QIcon movementIcon = QIcon(":scenetoolbar/editing-terrain-movement"); + const QIcon vertexIcon = Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-vertex-paint"); + const QIcon movementIcon = Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-movement"); tool->addButton(new EditMode(this, vertexIcon, Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); tool->addButton(new EditMode(this, movementIcon, Mask_Reference, "Terrain movement"), "terrain-move"); } diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 8550195e8c..8d68ef9650 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -2,6 +2,7 @@ #include +#include #include #include "../../model/prefs/state.hpp" @@ -36,7 +37,7 @@ class QWidget; namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-pathgrid"), + : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-pathgrid"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 6e1a216816..eee1f20ec6 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -31,6 +31,7 @@ #include #include +#include #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.hpp" @@ -62,8 +63,8 @@ namespace osg CSVRender::TerrainShapeMode::TerrainShapeMode( WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent) - : EditMode( - worldspaceWidget, QIcon{ ":scenetoolbar/editing-terrain-shape" }, Mask_Terrain, "Terrain land editing", parent) + : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-shape"), Mask_Terrain, + "Terrain land editing", parent) , mParentNode(parentNode) { } diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 684958da34..d89261aa1e 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include "../widget/scenetoolbar.hpp" @@ -49,8 +50,8 @@ CSVRender::TerrainTextureMode::TerrainTextureMode( WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent) - : EditMode(worldspaceWidget, QIcon{ ":scenetoolbar/editing-terrain-texture" }, Mask_Terrain | Mask_Reference, - "Terrain texture editing", parent) + : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-texture"), + Mask_Terrain | Mask_Reference, "Terrain texture editing", parent) , mBrushTexture("L0#0") , mBrushSize(1) , mBrushShape(CSVWidget::BrushShape_Point) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 2af84fb36d..089cb01f07 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -220,7 +221,7 @@ CSVWidget::SceneToolMode* CSVRender::WorldspaceWidget::makeNavigationSelector(CS "
  • Hold {free-forward:mod} to speed up movement
  • " "
"); tool->addButton( - new CSVRender::OrbitCameraMode(this, QIcon(":scenetoolbar/orbiting-camera"), + new CSVRender::OrbitCameraMode(this, Misc::ScalableIcon::load(":scenetoolbar/orbiting-camera"), "Orbiting Camera" "
  • Always facing the centre point
  • " "
  • Rotate around the centre point via {orbit-up}, {orbit-left}, {orbit-down}, {orbit-right} or by moving " diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 1fa7cfb690..f11d7489a8 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -94,7 +96,7 @@ void CSVWidget::SceneToolMode::showPanel(const QPoint& position) void CSVWidget::SceneToolMode::addButton(const std::string& icon, const std::string& id, const QString& tooltip) { - ModeButton* button = new ModeButton(QIcon(icon.c_str()), tooltip, mPanel); + ModeButton* button = new ModeButton(Misc::ScalableIcon::load(icon.c_str()), tooltip, mPanel); addButton(button, id); } diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 6313f10fa9..59d5bf4d5e 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include @@ -60,7 +62,7 @@ CSVWidget::SceneToolRun::SceneToolRun( , mSelected(mProfiles.begin()) , mToolTip(toolTip) { - setIcon(QIcon(icon)); + setIcon(Misc::ScalableIcon::load(icon)); updateIcon(); adjustToolTips(); diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 0e040c2385..1430300622 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include #include #include @@ -60,10 +62,10 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge : QFrame(parent, Qt::Popup) , mDocument(document) { - mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this); - mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this); - mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this); - mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this); + mButtonPoint = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-point"), "", this); + mButtonSquare = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-square"), "", this); + mButtonCircle = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-circle"), "", this); + mButtonCustom = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-custom"), "", this); mSizeSliders = new ShapeBrushSizeControls("Brush size", this); @@ -201,25 +203,25 @@ void CSVWidget::SceneToolShapeBrush::setButtonIcon(CSVWidget::BrushShape brushSh { case BrushShape_Point: - setIcon(QIcon(":scenetoolbar/brush-point")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-point")); tooltip += mShapeBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon(QIcon(":scenetoolbar/brush-square")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-square")); tooltip += mShapeBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon(QIcon(":scenetoolbar/brush-circle")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-circle")); tooltip += mShapeBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon(QIcon(":scenetoolbar/brush-custom")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-custom")); tooltip += mShapeBrushWindow->toolTipCustom; break; } diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 2e002aaf2e..e2cb241cb3 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -31,6 +31,8 @@ #include #include +#include + #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" @@ -90,10 +92,10 @@ CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QW mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel)); } - mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this); - mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this); - mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this); - mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this); + mButtonPoint = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-point"), "", this); + mButtonSquare = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-square"), "", this); + mButtonCircle = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-circle"), "", this); + mButtonCustom = new QPushButton(Misc::ScalableIcon::load(":scenetoolbar/brush-custom"), "", this); mSizeSliders = new BrushSizeControls("Brush size", this); @@ -282,25 +284,25 @@ void CSVWidget::SceneToolTextureBrush::setButtonIcon(CSVWidget::BrushShape brush { case BrushShape_Point: - setIcon(QIcon(":scenetoolbar/brush-point")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-point")); tooltip += mTextureBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon(QIcon(":scenetoolbar/brush-square")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-square")); tooltip += mTextureBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon(QIcon(":scenetoolbar/brush-circle")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-circle")); tooltip += mTextureBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon(QIcon(":scenetoolbar/brush-custom")); + setIcon(Misc::ScalableIcon::load(":scenetoolbar/brush-custom")); tooltip += mTextureBrushWindow->toolTipCustom; break; } diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 44bffa34a6..9e1e8b11c6 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -10,6 +10,8 @@ #include +#include + #include "pushbutton.hpp" #include "scenetoolbar.hpp" @@ -50,7 +52,7 @@ void CSVWidget::SceneToolToggle2::adjustIcon() std::ostringstream stream; stream << mCompositeIcon << buttonIds; - setIcon(QIcon(QString::fromUtf8(stream.str().c_str()))); + setIcon(Misc::ScalableIcon::load(QString::fromUtf8(stream.str().c_str()))); } CSVWidget::SceneToolToggle2::SceneToolToggle2( @@ -87,8 +89,8 @@ void CSVWidget::SceneToolToggle2::addButton( std::ostringstream stream; stream << mSingleIcon << id; - PushButton* button = new PushButton( - QIcon(stream.str().c_str()), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); + PushButton* button = new PushButton(Misc::ScalableIcon::load(stream.str().c_str()), PushButton::Type_Toggle, + tooltip.isEmpty() ? name : tooltip, mPanel); button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setIconSize(QSize(mIconSize, mIconSize)); diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index fc6a2b7d80..989d1af464 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -17,6 +17,8 @@ #include #include +#include + class QModelIndex; class QObject; @@ -161,7 +163,7 @@ void CSVWorld::DataDisplayDelegateFactory::add(int enumValue, const QString& enu Icon icon; icon.mValue = enumValue; icon.mName = enumName; - icon.mIcon = QIcon(iconFilename); + icon.mIcon = Misc::ScalableIcon::load(iconFilename); for (auto it = mIcons.begin(); it != mIcons.end(); ++it) { diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index a8a3df309c..a006779ba4 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include "dragdroputils.hpp" void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTable& table, const QModelIndex& index) @@ -29,7 +31,7 @@ void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTab mime->setIndexAtDragStart(index); QDrag* drag = new QDrag(this); drag->setMimeData(mime); - drag->setPixmap(QIcon(mime->getIcon().c_str()).pixmap(QSize(16, 16))); + drag->setPixmap(Misc::ScalableIcon::load(mime->getIcon().c_str()).pixmap(QSize(16, 16))); drag->exec(Qt::CopyAction); } diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 1333a4a7da..e2655136a1 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -12,6 +12,8 @@ #include "../world/tablebottombox.hpp" +#include + #include #include #include @@ -68,12 +70,12 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW // left section mPrevButton = new QToolButton(this); - mPrevButton->setIcon(QIcon(":record-previous")); + mPrevButton->setIcon(Misc::ScalableIcon::load(":record-previous")); mPrevButton->setToolTip("Switch to previous record"); buttonsLayout->addWidget(mPrevButton, 0); mNextButton = new QToolButton(this); - mNextButton->setIcon(QIcon(":/record-next")); + mNextButton->setIcon(Misc::ScalableIcon::load(":/record-next")); mNextButton->setToolTip("Switch to next record"); buttonsLayout->addWidget(mNextButton, 1); @@ -83,7 +85,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) { QToolButton* previewButton = new QToolButton(this); - previewButton->setIcon(QIcon(":edit-preview")); + previewButton->setIcon(Misc::ScalableIcon::load(":edit-preview")); previewButton->setToolTip("Open a preview of this record"); buttonsLayout->addWidget(previewButton); connect(previewButton, &QToolButton::clicked, this, &RecordButtonBar::showPreview); @@ -92,7 +94,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton(this); - viewButton->setIcon(QIcon(":cell")); + viewButton->setIcon(Misc::ScalableIcon::load(":cell")); viewButton->setToolTip("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord); @@ -100,22 +102,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW // right section mCloneButton = new QToolButton(this); - mCloneButton->setIcon(QIcon(":edit-clone")); + mCloneButton->setIcon(Misc::ScalableIcon::load(":edit-clone")); mCloneButton->setToolTip("Clone record"); buttonsLayout->addWidget(mCloneButton); mAddButton = new QToolButton(this); - mAddButton->setIcon(QIcon(":edit-add")); + mAddButton->setIcon(Misc::ScalableIcon::load(":edit-add")); mAddButton->setToolTip("Add new record"); buttonsLayout->addWidget(mAddButton); mDeleteButton = new QToolButton(this); - mDeleteButton->setIcon(QIcon(":edit-delete")); + mDeleteButton->setIcon(Misc::ScalableIcon::load(":edit-delete")); mDeleteButton->setToolTip("Delete record"); buttonsLayout->addWidget(mDeleteButton); mRevertButton = new QToolButton(this); - mRevertButton->setIcon(QIcon(":edit-undo")); + mRevertButton->setIcon(Misc::ScalableIcon::load(":edit-undo")); mRevertButton->setToolTip("Revert record"); buttonsLayout->addWidget(mRevertButton); diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index e13648d637..c4e20adf72 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -7,6 +7,8 @@ #include +#include + #include "../../model/world/commands.hpp" #include "../../model/world/universalid.hpp" @@ -38,7 +40,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator( { CSMWorld::UniversalId id2(*iter, ""); - mType->addItem(QIcon(id2.getIcon().c_str()), id2.getTypeName().c_str(), static_cast(id2.getType())); + mType->addItem(Misc::ScalableIcon::load(id2.getIcon().c_str()), id2.getTypeName().c_str(), + static_cast(id2.getType())); } mType->model()->sort(0); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index b7be2b90c8..86a1e93bbd 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include "../../model/doc/document.hpp" @@ -323,7 +324,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mEditAction = new QAction(tr("Edit Record"), this); connect(mEditAction, &QAction::triggered, this, &Table::editRecord); - mEditAction->setIcon(QIcon(":edit-edit")); + mEditAction->setIcon(Misc::ScalableIcon::load(":edit-edit")); addAction(mEditAction); CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this); editShortcut->associateAction(mEditAction); @@ -332,14 +333,14 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo { mCreateAction = new QAction(tr("Add Record"), this); connect(mCreateAction, &QAction::triggered, this, &Table::createRequest); - mCreateAction->setIcon(QIcon(":edit-add")); + mCreateAction->setIcon(Misc::ScalableIcon::load(":edit-add")); addAction(mCreateAction); CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this); createShortcut->associateAction(mCreateAction); mCloneAction = new QAction(tr("Clone Record"), this); connect(mCloneAction, &QAction::triggered, this, &Table::cloneRecord); - mCloneAction->setIcon(QIcon(":edit-clone")); + mCloneAction->setIcon(Misc::ScalableIcon::load(":edit-clone")); addAction(mCloneAction); CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this); cloneShortcut->associateAction(mCloneAction); @@ -349,7 +350,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo { mTouchAction = new QAction(tr("Touch Record"), this); connect(mTouchAction, &QAction::triggered, this, &Table::touchRecord); - mTouchAction->setIcon(QIcon(":edit-touch")); + mTouchAction->setIcon(Misc::ScalableIcon::load(":edit-touch")); addAction(mTouchAction); CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this); touchShortcut->associateAction(mTouchAction); @@ -357,56 +358,56 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mRevertAction = new QAction(tr("Revert Record"), this); connect(mRevertAction, &QAction::triggered, mDispatcher, &CSMWorld::CommandDispatcher::executeRevert); - mRevertAction->setIcon(QIcon(":edit-undo")); + mRevertAction->setIcon(Misc::ScalableIcon::load(":edit-undo")); addAction(mRevertAction); CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this); revertShortcut->associateAction(mRevertAction); mDeleteAction = new QAction(tr("Delete Record"), this); connect(mDeleteAction, &QAction::triggered, mDispatcher, &CSMWorld::CommandDispatcher::executeDelete); - mDeleteAction->setIcon(QIcon(":edit-delete")); + mDeleteAction->setIcon(Misc::ScalableIcon::load(":edit-delete")); addAction(mDeleteAction); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this); deleteShortcut->associateAction(mDeleteAction); mMoveUpAction = new QAction(tr("Move Up"), this); connect(mMoveUpAction, &QAction::triggered, this, &Table::moveUpRecord); - mMoveUpAction->setIcon(QIcon(":record-up")); + mMoveUpAction->setIcon(Misc::ScalableIcon::load(":record-up")); addAction(mMoveUpAction); CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this); moveUpShortcut->associateAction(mMoveUpAction); mMoveDownAction = new QAction(tr("Move Down"), this); connect(mMoveDownAction, &QAction::triggered, this, &Table::moveDownRecord); - mMoveDownAction->setIcon(QIcon(":record-down")); + mMoveDownAction->setIcon(Misc::ScalableIcon::load(":record-down")); addAction(mMoveDownAction); CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this); moveDownShortcut->associateAction(mMoveDownAction); mViewAction = new QAction(tr("View"), this); connect(mViewAction, &QAction::triggered, this, &Table::viewRecord); - mViewAction->setIcon(QIcon(":cell")); + mViewAction->setIcon(Misc::ScalableIcon::load(":cell")); addAction(mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); mPreviewAction = new QAction(tr("Preview"), this); connect(mPreviewAction, &QAction::triggered, this, &Table::previewRecord); - mPreviewAction->setIcon(QIcon(":edit-preview")); + mPreviewAction->setIcon(Misc::ScalableIcon::load(":edit-preview")); addAction(mPreviewAction); CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this); previewShortcut->associateAction(mPreviewAction); mExtendedDeleteAction = new QAction(tr("Extended Delete Record"), this); connect(mExtendedDeleteAction, &QAction::triggered, this, &Table::executeExtendedDelete); - mExtendedDeleteAction->setIcon(QIcon(":edit-delete")); + mExtendedDeleteAction->setIcon(Misc::ScalableIcon::load(":edit-delete")); addAction(mExtendedDeleteAction); CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this); extendedDeleteShortcut->associateAction(mExtendedDeleteAction); mExtendedRevertAction = new QAction(tr("Extended Revert Record"), this); connect(mExtendedRevertAction, &QAction::triggered, this, &Table::executeExtendedRevert); - mExtendedRevertAction->setIcon(QIcon(":edit-undo")); + mExtendedRevertAction->setIcon(Misc::ScalableIcon::load(":edit-undo")); addAction(mExtendedRevertAction); CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this); extendedRevertShortcut->associateAction(mExtendedRevertAction); @@ -417,7 +418,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &Table::openHelp); - mHelpAction->setIcon(QIcon(":info")); + mHelpAction->setIcon(Misc::ScalableIcon::load(":info")); addAction(mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index b48eaec31d..e7701ee778 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -17,6 +17,8 @@ #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" @@ -60,7 +62,7 @@ CSVWorld::TableSubView::TableSubView( mOptions->hide(); QPushButton* opt = new QPushButton(); - opt->setIcon(QIcon(":startup/configure")); + opt->setIcon(Misc::ScalableIcon::load(":startup/configure")); opt->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); opt->setToolTip("Open additional options for this subview."); connect(opt, &QPushButton::clicked, this, &TableSubView::toggleOptions); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 179dbcdc32..cf9c45f54e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -165,6 +165,15 @@ namespace private: int mMaxTextureImageUnits = 0; }; + + void reportStats(unsigned frameNumber, osgViewer::Viewer& viewer, std::ostream& stream) + { + viewer.getViewerStats()->report(stream, frameNumber); + osgViewer::Viewer::Cameras cameras; + viewer.getCameras(cameras); + for (osg::Camera* camera : cameras) + camera->getStats()->report(stream, frameNumber); + } } void OMW::Engine::executeLocalScripts() @@ -180,10 +189,9 @@ void OMW::Engine::executeLocalScripts() } } -bool OMW::Engine::frame(float frametime) +bool OMW::Engine::frame(unsigned frameNumber, float frametime) { const osg::Timer_t frameStart = mViewer->getStartTick(); - const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); const osg::Timer* const timer = osg::Timer::instance(); osg::Stats* const stats = mViewer->getViewerStats(); @@ -340,11 +348,12 @@ bool OMW::Engine::frame(float frametime) mWorld->updateWindowManager(); } - mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now + // if there is a separate Lua thread, it starts the update now + mLuaWorker->allowUpdate(frameStart, frameNumber, *stats); mViewer->renderingTraversals(); - mLuaWorker->finishUpdate(); + mLuaWorker->finishUpdate(frameStart, frameNumber, *stats); return true; } @@ -910,7 +919,7 @@ void OMW::Engine::prepareEngine() mLuaManager->init(); // starts a separate lua thread if "lua num threads" > 0 - mLuaWorker = std::make_unique(*mLuaManager, *mViewer); + mLuaWorker = std::make_unique(*mLuaManager); } // Initialise and enter main loop. @@ -1020,7 +1029,9 @@ void OMW::Engine::go() mViewer->advance(timeManager.getRenderingSimulationTime()); - if (!frame(dt)) + const unsigned frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + + if (!frame(frameNumber, dt)) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; @@ -1034,16 +1045,16 @@ void OMW::Engine::go() if (stats) { + // The delay is required because rendering happens in parallel to the main thread and stats from there is + // available with delay. constexpr unsigned statsReportDelay = 3; - const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (frameNumber >= statsReportDelay) { - const unsigned reportFrameNumber = frameNumber - statsReportDelay; - mViewer->getViewerStats()->report(stats, reportFrameNumber); - osgViewer::Viewer::Cameras cameras; - mViewer->getCameras(cameras); - for (auto camera : cameras) - camera->getStats()->report(stats, reportFrameNumber); + // Viewer frame number can be different from frameNumber because of loading screens which render new + // frames inside a simulation frame. + const unsigned currentFrameNumber = mViewer->getFrameStamp()->getFrameNumber(); + for (unsigned i = frameNumber; i <= currentFrameNumber; ++i) + reportStats(i - statsReportDelay, *mViewer, stats); } } diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 2cd224785b..bf7bf7441b 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -188,7 +188,7 @@ namespace OMW void executeLocalScripts(); - bool frame(float dt); + bool frame(unsigned frameNumber, float dt); /// Prepare engine for game play void prepareEngine(); diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 70887c69c1..ccb4b9f43f 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -2,6 +2,7 @@ #define GAME_MWBASE_DIALOGUEMANAGER_H #include +#include #include #include #include @@ -108,11 +109,15 @@ namespace MWBase /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int diff) = 0; + /// Set faction1's opinion of faction2. virtual void setFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int absolute) = 0; /// @return faction1's opinion of faction2 virtual int getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const = 0; + /// @return all faction's opinion overrides + virtual const std::map* getFactionReactionOverrides(const ESM::RefId& faction) const = 0; + /// Removes the last added topic response for the given actor from the journal virtual void clearInfoActor(const MWWorld::Ptr& actor) const = 0; }; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 556b5b53d7..27178606be 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -738,6 +738,17 @@ namespace MWDialogue return 0; } + const std::map* DialogueManager::getFactionReactionOverrides(const ESM::RefId& faction) const + { + // Make sure the faction exists + MWBase::Environment::get().getESMStore()->get().find(faction); + + const auto found = mChangedFactionReaction.find(faction); + if (found != mChangedFactionReaction.end()) + return &found->second; + return nullptr; + } + void DialogueManager::clearInfoActor(const MWWorld::Ptr& actor) const { if (actor == mActor && !mLastTopic.empty()) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index c214106fdc..af8adfb876 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -126,6 +126,8 @@ namespace MWDialogue /// @return faction1's opinion of faction2 int getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const override; + const std::map* getFactionReactionOverrides(const ESM::RefId& faction) const override; + /// Removes the last added topic response for the given actor from the journal void clearInfoActor(const MWWorld::Ptr& actor) const override; }; diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index 83b9cfc5e8..8f85671ff5 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -4,6 +4,9 @@ #include #include +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" + #include "../mwworld/store.hpp" #include "idcollectionbindings.hpp" @@ -70,6 +73,16 @@ namespace MWLua sol::table res(lua, sol::create); for (const auto& [factionId, reaction] : rec.mReactions) res[factionId.serializeText()] = reaction; + + const auto* overrides + = MWBase::Environment::get().getDialogueManager()->getFactionReactionOverrides(rec.mId); + + if (overrides != nullptr) + { + for (const auto& [factionId, reaction] : *overrides) + res[factionId.serializeText()] = reaction; + } + return res; }); factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) { diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8fa0571afc..3b0d44a984 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -104,7 +104,27 @@ namespace MWLua }); aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); }); aiPackage["destPosition"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); }); + aiPackage["distance"] = sol::readonly_property([](const AiPackage& p) { return p.getDistance(); }); + aiPackage["duration"] = sol::readonly_property([](const AiPackage& p) { return p.getDuration(); }); + aiPackage["idle"] = sol::readonly_property([context](const AiPackage& p) -> sol::optional { + if (p.getTypeId() == MWMechanics::AiPackageTypeId::Wander) + { + sol::table idles(context.mLua->sol(), sol::create); + const std::vector& idle = static_cast(p).getIdle(); + if (!idle.empty()) + { + for (size_t i = 0; i < idle.size(); ++i) + { + std::string_view groupName = MWMechanics::AiWander::getIdleGroupName(i); + idles[groupName] = idle[i]; + } + return idles; + } + } + return sol::nullopt; + }); + aiPackage["isRepeat"] = sol::readonly_property([](const AiPackage& p) { return p.getRepeat(); }); selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional> { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); @@ -132,13 +152,25 @@ namespace MWLua MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiPursue(target.ptr()), ptr, cancelOther); }; - selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target, bool cancelOther) { + selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target, sol::optional cell, + float duration, const osg::Vec3f& dest, bool repeat, bool cancelOther) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - ai.stack(MWMechanics::AiFollow(target.ptr()), ptr, cancelOther); + if (cell) + { + ai.stack(MWMechanics::AiFollow(target.ptr().getCellRef().getRefId(), + cell->mStore->getCell()->getNameId(), duration, dest.x(), dest.y(), dest.z(), repeat), + ptr, cancelOther); + } + else + { + ai.stack(MWMechanics::AiFollow( + target.ptr().getCellRef().getRefId(), duration, dest.x(), dest.y(), dest.z(), repeat), + ptr, cancelOther); + } }; selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration, - const osg::Vec3f& dest, bool cancelOther) { + const osg::Vec3f& dest, bool repeat, bool cancelOther) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); // TODO: change AiEscort implementation to accept ptr instead of a non-unique refId. @@ -146,23 +178,27 @@ namespace MWLua int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); auto* esmCell = cell.mStore->getCell(); if (esmCell->isExterior()) - ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr, + ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), ptr, cancelOther); else ai.stack(MWMechanics::AiEscort( - refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), false), + refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), ptr, cancelOther); }; - selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration, bool cancelOther) { + selfAPI["_startAiWander"] + = [](SelfObject& self, int distance, int duration, sol::table luaIdle, bool repeat, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + std::vector idle; + // Lua index starts at 1 + for (size_t i = 1; i <= luaIdle.size(); i++) + idle.emplace_back(luaIdle.get(i)); + ai.stack(MWMechanics::AiWander(distance, duration, 0, idle, repeat), ptr, cancelOther); + }; + selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target, bool repeat, bool cancelOther) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); - ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr, cancelOther); - }; - selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target, bool cancelOther) { - const MWWorld::Ptr& ptr = self.ptr(); - MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr, cancelOther); + ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), repeat), ptr, cancelOther); }; selfAPI["_enableLuaAnimations"] = [](SelfObject& self, bool enable) { const MWWorld::Ptr& ptr = self.ptr(); diff --git a/apps/openmw/mwlua/worker.cpp b/apps/openmw/mwlua/worker.cpp index 193d340208..5639cc89ed 100644 --- a/apps/openmw/mwlua/worker.cpp +++ b/apps/openmw/mwlua/worker.cpp @@ -7,13 +7,12 @@ #include #include -#include +#include namespace MWLua { - Worker::Worker(LuaManager& manager, osgViewer::Viewer& viewer) + Worker::Worker(LuaManager& manager) : mManager(manager) - , mViewer(viewer) { if (Settings::lua().mLuaNumThreads > 0) mThread = std::thread([this] { run(); }); @@ -29,26 +28,26 @@ namespace MWLua } } - void Worker::allowUpdate() + void Worker::allowUpdate(osg::Timer_t frameStart, unsigned frameNumber, osg::Stats& stats) { if (!mThread) return; { std::lock_guard lk(mMutex); - mUpdateRequest = true; + mUpdateRequest = UpdateRequest{ .mFrameStart = frameStart, .mFrameNumber = frameNumber, .mStats = &stats }; } mCV.notify_one(); } - void Worker::finishUpdate() + void Worker::finishUpdate(osg::Timer_t frameStart, unsigned frameNumber, osg::Stats& stats) { if (mThread) { std::unique_lock lk(mMutex); - mCV.wait(lk, [&] { return !mUpdateRequest; }); + mCV.wait(lk, [&] { return !mUpdateRequest.has_value(); }); } else - update(); + update(frameStart, frameNumber, stats); } void Worker::join() @@ -64,12 +63,10 @@ namespace MWLua } } - void Worker::update() + void Worker::update(osg::Timer_t frameStart, unsigned frameNumber, osg::Stats& stats) { - const osg::Timer_t frameStart = mViewer.getStartTick(); - const unsigned int frameNumber = mViewer.getFrameStamp()->getFrameNumber(); - OMW::ScopedProfile profile( - frameStart, frameNumber, *osg::Timer::instance(), *mViewer.getViewerStats()); + const osg::Timer* const timer = osg::Timer::instance(); + OMW::ScopedProfile profile(frameStart, frameNumber, *timer, stats); mManager.update(); } @@ -79,20 +76,22 @@ namespace MWLua while (true) { std::unique_lock lk(mMutex); - mCV.wait(lk, [&] { return mUpdateRequest || mJoinRequest; }); + mCV.wait(lk, [&] { return mUpdateRequest.has_value() || mJoinRequest; }); if (mJoinRequest) break; + assert(mUpdateRequest.has_value()); + try { - update(); + update(mUpdateRequest->mFrameStart, mUpdateRequest->mFrameNumber, *mUpdateRequest->mStats); } - catch (std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Failed to update LuaManager: " << e.what(); } - mUpdateRequest = false; + mUpdateRequest.reset(); lk.unlock(); mCV.notify_one(); } diff --git a/apps/openmw/mwlua/worker.hpp b/apps/openmw/mwlua/worker.hpp index fed625e1f1..58d69afe71 100644 --- a/apps/openmw/mwlua/worker.hpp +++ b/apps/openmw/mwlua/worker.hpp @@ -1,14 +1,17 @@ #ifndef OPENMW_MWLUA_WORKER_H #define OPENMW_MWLUA_WORKER_H +#include +#include + #include #include #include #include -namespace osgViewer +namespace osg { - class Viewer; + class Stats; } namespace MWLua @@ -18,26 +21,32 @@ namespace MWLua class Worker { public: - explicit Worker(LuaManager& manager, osgViewer::Viewer& viewer); + explicit Worker(LuaManager& manager); ~Worker(); - void allowUpdate(); + void allowUpdate(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - void finishUpdate(); + void finishUpdate(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void join(); private: - void update(); + struct UpdateRequest + { + osg::Timer_t mFrameStart; + unsigned mFrameNumber; + osg::ref_ptr mStats; + }; + + void update(osg::Timer_t frameStart, unsigned frameNumber, osg::Stats& stats); void run() noexcept; LuaManager& mManager; - osgViewer::Viewer& mViewer; std::mutex mMutex; std::condition_variable mCV; - bool mUpdateRequest = false; + std::optional mUpdateRequest; bool mJoinRequest = false; std::optional mThread; }; diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 709b2bee59..d88ecac6a5 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -51,6 +51,8 @@ namespace MWMechanics osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } + std::optional getDuration() const override { return mDuration; } + private: const std::string mCellId; const float mX; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 29a9f9c9ad..ca33f5dc90 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -110,6 +110,10 @@ namespace MWMechanics virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } + virtual std::optional getDistance() const { return std::nullopt; } + + virtual std::optional getDuration() const { return std::nullopt; } + /// Return true if any loaded actor with this AI package must be active. bool alwaysActive() const { return mOptions.mAlwaysActive; } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6d5bd7f8cd..aed7214f4d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -113,6 +113,14 @@ namespace MWMechanics bool isStationary() const { return mDistance == 0; } + std::optional getDistance() const override { return mDistance; } + + std::optional getDuration() const override { return static_cast(mDuration); } + + const std::vector& getIdle() const { return mIdle; } + + static std::string_view getIdleGroupName(size_t index) { return sIdleSelectToGroupName[index]; } + private: void stopWalking(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 4cff658011..2d2031b13f 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -142,45 +142,28 @@ namespace MWRender struct AnimState { std::shared_ptr mSource; - float mStartTime; - float mLoopStartTime; - float mLoopStopTime; - float mStopTime; + float mStartTime = 0; + float mLoopStartTime = 0; + float mLoopStopTime = 0; + float mStopTime = 0; - typedef std::shared_ptr TimePtr; - TimePtr mTime; - float mSpeedMult; + std::shared_ptr mTime = std::make_shared(0); + float mSpeedMult = 1; - bool mPlaying; - bool mLoopingEnabled; - uint32_t mLoopCount; + bool mPlaying = false; + bool mLoopingEnabled = true; + uint32_t mLoopCount = 0; - AnimPriority mPriority; - int mBlendMask; - bool mAutoDisable; - - AnimState() - : mStartTime(0.0f) - , mLoopStartTime(0.0f) - , mLoopStopTime(0.0f) - , mStopTime(0.0f) - , mTime(new float) - , mSpeedMult(1.0f) - , mPlaying(false) - , mLoopingEnabled(true) - , mLoopCount(0) - , mPriority(0) - , mBlendMask(0) - , mAutoDisable(true) - { - } - ~AnimState() = default; + AnimPriority mPriority{ 0 }; + int mBlendMask = 0; + bool mAutoDisable = true; float getTime() const { return *mTime; } void setTime(float time) { *mTime = time; } bool shouldLoop() const { return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; } }; + typedef std::map> AnimStateMap; AnimStateMap mStates; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index cfdeb8c658..ce0bd9673b 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -704,16 +704,17 @@ namespace MWScript if (!ptr.getClass().isActor()) return; + MWWorld::InventoryStore* invStorePtr = nullptr; if (ptr.getClass().hasInventoryStore(ptr)) { + invStorePtr = &ptr.getClass().getInventoryStore(ptr); // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping // them. - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - int numNotEquipped = store.count(item); + int numNotEquipped = invStorePtr->count(item); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - MWWorld::ConstContainerStoreIterator it = store.getSlot(slot); - if (it != store.end() && it->getCellRef().getRefId() == item) + MWWorld::ConstContainerStoreIterator it = invStorePtr->getSlot(slot); + if (it != invStorePtr->end() && it->getCellRef().getRefId() == item) { numNotEquipped -= it->getCellRef().getCount(); } @@ -721,29 +722,30 @@ namespace MWScript for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) { - MWWorld::ContainerStoreIterator it = store.getSlot(slot); - if (it != store.end() && it->getCellRef().getRefId() == item) + MWWorld::ContainerStoreIterator it = invStorePtr->getSlot(slot); + if (it != invStorePtr->end() && it->getCellRef().getRefId() == item) { int numToRemove = std::min(amount - numNotEquipped, it->getCellRef().getCount()); - store.unequipItemQuantity(*it, numToRemove); + invStorePtr->unequipItemQuantity(*it, numToRemove); numNotEquipped += numToRemove; } } + } - for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) + { + if (iter->getCellRef().getRefId() == item && (!invStorePtr || !invStorePtr->isEquipped(*iter))) { - if (iter->getCellRef().getRefId() == item && !store.isEquipped(*iter)) - { - int removed = store.remove(*iter, amount); - MWWorld::Ptr dropped - = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); - dropped.getCellRef().setOwner(ESM::RefId()); + int removed = store.remove(*iter, amount); + MWWorld::Ptr dropped + = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); + dropped.getCellRef().setOwner(ESM::RefId()); - amount -= removed; + amount -= removed; - if (amount <= 0) - break; - } + if (amount <= 0) + break; } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 7157e67d82..e08d576835 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -225,8 +225,6 @@ namespace MWWorld , mTerrain(terrain) , mLandManager(landManager) , mExpiryDelay(0.0) - , mMinCacheSize(0) - , mMaxCacheSize(0) , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) , mLoadedTerrainTimestamp(0.0) @@ -361,26 +359,11 @@ namespace MWWorld mExpiryDelay = expiryDelay; } - void CellPreloader::setMinCacheSize(unsigned int num) - { - mMinCacheSize = num; - } - - void CellPreloader::setMaxCacheSize(unsigned int num) - { - mMaxCacheSize = num; - } - void CellPreloader::setPreloadInstances(bool preload) { mPreloadInstances = preload; } - unsigned int CellPreloader::getMaxCacheSize() const - { - return mMaxCacheSize; - } - void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) { mWorkQueue = workQueue; diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index ce5d5e7a0f..aa8f2c73be 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -65,15 +65,17 @@ namespace MWWorld void setExpiryDelay(double expiryDelay); /// The minimum number of preloaded cells before unused cells get thrown out. - void setMinCacheSize(unsigned int num); + void setMinCacheSize(std::size_t value) { mMinCacheSize = value; } /// The maximum number of preloaded cells. - void setMaxCacheSize(unsigned int num); + void setMaxCacheSize(std::size_t value) { mMaxCacheSize = value; } /// Enables the creation of instances in the preloading thread. void setPreloadInstances(bool preload); - unsigned int getMaxCacheSize() const; + std::size_t getMaxCacheSize() const { return mMaxCacheSize; } + + std::size_t getCacheSize() const { return mPreloadCells.size(); } void setWorkQueue(osg::ref_ptr workQueue); @@ -96,8 +98,8 @@ namespace MWWorld MWRender::LandManager* mLandManager; osg::ref_ptr mWorkQueue; double mExpiryDelay; - unsigned int mMinCacheSize; - unsigned int mMaxCacheSize; + std::size_t mMinCacheSize = 0; + std::size_t mMaxCacheSize = 0; bool mPreloadInstances; double mLastResourceCacheUpdate; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index beb519b9e6..b373bf416e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -270,6 +270,29 @@ namespace pagedRefs.erase(it); return true; } + + template + void iterateOverCellsAround(int cellX, int cellY, int range, Function&& f) + { + for (int x = cellX - range, lastX = cellX + range; x <= lastX; ++x) + for (int y = cellY - range, lastY = cellY + range; y <= lastY; ++y) + f(x, y); + } + + void sortCellsToLoad(int centerX, int centerY, std::vector>& cells) + { + const auto getDistanceToPlayerCell = [&](const std::pair& cellPosition) { + return std::abs(cellPosition.first - centerX) + std::abs(cellPosition.second - centerY); + }; + + const auto getCellPositionPriority = [&](const std::pair& cellPosition) { + return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition)); + }; + + std::sort(cells.begin(), cells.end(), [&](const std::pair& lhs, const std::pair& rhs) { + return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); + }); + } } namespace MWWorld @@ -585,44 +608,24 @@ namespace MWWorld mPagedRefs.clear(); mRendering.getPagedRefnums(newGrid, mPagedRefs); - std::size_t refsToLoad = 0; - const auto cellsToLoad = [&](CellStoreCollection& collection, int range) -> std::vector> { - std::vector> cellsPositionsToLoad; - for (int x = playerCellX - range; x <= playerCellX + range; ++x) - { - for (int y = playerCellY - range; y <= playerCellY + range; ++y) - { - if (!isCellInCollection(ESM::ExteriorCellLocation(x, y, playerCellIndex.mWorldspace), collection)) - { - refsToLoad += mWorld.getWorldModel().getExterior(playerCellIndex).count(); - cellsPositionsToLoad.emplace_back(x, y); - } - } - } - return cellsPositionsToLoad; - }; - addPostponedPhysicsObjects(); - auto cellsPositionsToLoad = cellsToLoad(mActiveCells, mHalfGridSize); + std::size_t refsToLoad = 0; + std::vector> cellsPositionsToLoad; + iterateOverCellsAround(playerCellX, playerCellY, mHalfGridSize, [&](int x, int y) { + const ESM::ExteriorCellLocation location(x, y, playerCellIndex.mWorldspace); + if (isCellInCollection(location, mActiveCells)) + return; + refsToLoad += mWorld.getWorldModel().getExterior(location).count(); + cellsPositionsToLoad.emplace_back(x, y); + }); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); loadingListener->setLabel("#{OMWEngine:LoadingExterior}"); loadingListener->setProgressRange(refsToLoad); - const auto getDistanceToPlayerCell = [&](const std::pair& cellPosition) { - return std::abs(cellPosition.first - playerCellX) + std::abs(cellPosition.second - playerCellY); - }; - - const auto getCellPositionPriority = [&](const std::pair& cellPosition) { - return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition)); - }; - - std::sort(cellsPositionsToLoad.begin(), cellsPositionsToLoad.end(), - [&](const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); + sortCellsToLoad(playerCellX, playerCellY, cellsPositionsToLoad); for (const auto& [x, y] : cellsPositionsToLoad) { @@ -1136,7 +1139,7 @@ namespace MWWorld { try { - preloadCell(mWorld.getWorldModel().getCell(door.getCellRef().getDestCell())); + preloadCellWithSurroundings(mWorld.getWorldModel().getCell(door.getCellRef().getDestCell())); } catch (std::exception&) { @@ -1183,27 +1186,47 @@ namespace MWWorld } } - void Scene::preloadCell(CellStore& cell, bool preloadSurrounding) + void Scene::preloadCellWithSurroundings(CellStore& cell) { - if (preloadSurrounding && cell.isExterior()) + if (!cell.isExterior()) { - int x = cell.getCell()->getGridX(); - int y = cell.getCell()->getGridY(); - unsigned int numpreloaded = 0; - for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx) - { - for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) - { - mPreloader->preload(mWorld.getWorldModel().getExterior( - ESM::ExteriorCellLocation(x + dx, y + dy, cell.getCell()->getWorldSpace())), - mRendering.getReferenceTime()); - if (++numpreloaded >= mPreloader->getMaxCacheSize()) - break; - } - } - } - else mPreloader->preload(cell, mRendering.getReferenceTime()); + return; + } + + const int cellX = cell.getCell()->getGridX(); + const int cellY = cell.getCell()->getGridY(); + + std::vector> cells; + const std::size_t gridSize = static_cast(2 * mHalfGridSize + 1); + cells.reserve(gridSize * gridSize); + + iterateOverCellsAround(cellX, cellY, mHalfGridSize, [&](int x, int y) { cells.emplace_back(x, y); }); + + sortCellsToLoad(cellX, cellY, cells); + + const std::size_t leftCapacity = mPreloader->getMaxCacheSize() - mPreloader->getCacheSize(); + if (cells.size() > leftCapacity) + { + static bool logged = [&] { + Log(Debug::Warning) << "Not enough cell preloader cache capacity to preload exterior cells, consider " + "increasing \"preload cell cache max\" up to " + << (mPreloader->getCacheSize() + cells.size()); + return true; + }(); + (void)logged; + cells.resize(leftCapacity); + } + + const ESM::RefId worldspace = cell.getCell()->getWorldSpace(); + for (const auto& [x, y] : cells) + mPreloader->preload(mWorld.getWorldModel().getExterior(ESM::ExteriorCellLocation(x, y, worldspace)), + mRendering.getReferenceTime()); + } + + void Scene::preloadCell(CellStore& cell) + { + mPreloader->preload(cell, mRendering.getReferenceTime()); } void Scene::preloadTerrain(const osg::Vec3f& pos, ESM::RefId worldspace, bool sync) @@ -1281,7 +1304,7 @@ namespace MWWorld osg::Vec3f pos = dest.mPos.asVec3(); const ESM::ExteriorCellLocation cellIndex = ESM::positionToExteriorCellLocation(pos.x(), pos.y(), extWorldspace); - preloadCell(mWorld.getWorldModel().getExterior(cellIndex), true); + preloadCellWithSurroundings(mWorld.getWorldModel().getExterior(cellIndex)); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 6c915d4f92..96050bad32 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -145,7 +145,8 @@ namespace MWWorld ~Scene(); - void preloadCell(MWWorld::CellStore& cell, bool preloadSurrounding = false); + void preloadCellWithSurroundings(MWWorld::CellStore& cell); + void preloadCell(MWWorld::CellStore& cell); void preloadTerrain(const osg::Vec3f& pos, ESM::RefId worldspace, bool sync = false); void reloadTerrain(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c72a9993d6..24731055ad 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -523,7 +523,7 @@ namespace MWWorld if (getPlayerPtr().getCell()->isExterior()) mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3(), getPlayerPtr().getCell()->getCell()->getWorldSpace()); - mWorldScene->preloadCell(*getPlayerPtr().getCell(), true); + mWorldScene->preloadCellWithSurroundings(*getPlayerPtr().getCell()); } break; default: diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1235072de5..0926f738af 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -534,7 +534,7 @@ if (USE_QT) ) add_component_qt_dir (misc - helpviewer utf8qtextstream hash + helpviewer utf8qtextstream hash scalableicon ) add_component_qt_dir (files diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 8e7b77d308..e1973c7506 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -602,7 +602,8 @@ void ContentSelectorModel::ContentModel::sortFiles() emit layoutAboutToBeChanged(); int firstModifiable = 0; - while (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile()) + while (firstModifiable < mFiles.size() + && (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile())) ++firstModifiable; // Dependency sort diff --git a/components/misc/scalableicon.cpp b/components/misc/scalableicon.cpp new file mode 100644 index 0000000000..0b30eaf2ab --- /dev/null +++ b/components/misc/scalableicon.cpp @@ -0,0 +1,105 @@ +#include "scalableicon.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Misc +{ + Q_GLOBAL_STATIC(QSet, ScalableIconInstances) + + ScalableIcon::ScalableIcon(const QByteArray& svgContent) + : mTemplate(svgContent) + { + update(); + ScalableIconInstances->insert(this); + } + + ScalableIcon::~ScalableIcon() + { + if (!ScalableIconInstances.isDestroyed()) + { + ScalableIconInstances->remove(this); + } + } + + QIcon Misc::ScalableIcon::load(const QString& fileName) + { + if (fileName.isEmpty()) + return QIcon(); + + QFile iconFile(fileName); + iconFile.open(QIODevice::ReadOnly); + auto content = iconFile.readAll(); + if (!content.startsWith("update(); + } + } + + void ScalableIcon::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) + { + Q_UNUSED(mode); + Q_UNUSED(state); + + QSvgRenderer renderer(mContent); + renderer.render(painter, rect); + } + + QIconEngine* ScalableIcon::clone() const + { + return new ScalableIcon(*this); + } + + QPixmap ScalableIcon::pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) + { + QPixmap pix = QPixmap(size); + pix.fill(Qt::transparent); + + QPainter painter(&pix); + QRect r(QPoint(0.0, 0.0), size); + this->paint(&painter, r, mode, state); + + if (mode != QIcon::Disabled) + return pix; + + // For disabled icons use grayscale icons with 50% transparency + QImage img = pix.toImage(); + + for (int x = 0; x < img.width(); x++) + { + for (int y = 0; y < img.height(); y++) + { + QColor n = img.pixelColor(x, y); + int gray = qGray(n.red(), n.green(), n.blue()); + img.setPixelColor(x, y, QColor(gray, gray, gray, n.alpha() / 2)); + } + } + + return QPixmap::fromImage(img, Qt::NoFormatConversion); + } +} diff --git a/components/misc/scalableicon.hpp b/components/misc/scalableicon.hpp new file mode 100644 index 0000000000..06cac9e39d --- /dev/null +++ b/components/misc/scalableicon.hpp @@ -0,0 +1,36 @@ +#ifndef SCALABLEICON_HPP +#define SCALABLEICON_HPP + +#include +#include +#include +#include + +namespace Misc +{ + class ScalableIcon : public QIconEngine + { + public: + QIconEngine* clone() const override; + + QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override; + + void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override; + + static void updateAllIcons(); + + virtual ~ScalableIcon(); + + static QIcon load(const QString& fileName); + + private: + explicit ScalableIcon(const QByteArray& svgContent); + + void update(); + + QByteArray mTemplate; + QByteArray mContent; + }; +} + +#endif // SCALABLEICON_HPP diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index d1553cc8d8..9fd435f40d 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -558,11 +558,9 @@ MWShadowTechnique::ShadowData::ShadowData(MWShadowTechnique::ViewDependentData* _texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR); _texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); - // the shadow comparison should fail if object is outside the texture - _texture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::CLAMP_TO_BORDER); - _texture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::CLAMP_TO_BORDER); - _texture->setBorderColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); - //_texture->setBorderColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); + // the shader clips sampled coordinates, so no need for border + _texture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::CLAMP_TO_EDGE); + _texture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::CLAMP_TO_EDGE); // set up the camera _camera = new osg::Camera; diff --git a/docs/source/reference/lua-scripting/aipackages.rst b/docs/source/reference/lua-scripting/aipackages.rst index 4ef3149582..7a23d156f5 100644 --- a/docs/source/reference/lua-scripting/aipackages.rst +++ b/docs/source/reference/lua-scripting/aipackages.rst @@ -99,6 +99,18 @@ Follow another actor. * - target - `GameObject `_ [required] - the actor to follow + * - destCell + - Cell [optional] + - the destination cell + * - duration + - number [optional] + - duration in game time (will be rounded up to the next hour) + * - destPosition + - `3d vector `_ [optional] + - the destination point + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) Escort ------ @@ -126,6 +138,9 @@ Escort another actor to the given location. * - duration - number [optional] - duration in game time (will be rounded up to the next hour) + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) **Example** @@ -136,6 +151,7 @@ Escort another actor to the given location. target = object.self, destPosition = util.vector3(x, y, z), duration = 3 * time.hour, + isRepeat = true }) Wander @@ -158,6 +174,34 @@ Wander nearby current position. * - duration - number [optional] - duration in game time (will be rounded up to the next hour) + * - idle + - table [optional] + - Idle chance values, up to 8 + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) + +**Example** + +.. code-block:: Lua + + local idleTable = { + idle2 = 60, + idle3 = 50, + idle4 = 40, + idle5 = 30, + idle6 = 20, + idle7 = 10, + idle8 = 0, + idle9 = 25 + } + actor:sendEvent('StartAIPackage', { + type = 'Wander', + distance = 5000, + duration = 5 * time.hour, + idle = idleTable, + isRepeat = true + }) Travel ------ @@ -176,4 +220,6 @@ Go to given location. * - destPosition - `3d vector `_ [required] - the point to travel to - + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) diff --git a/files/data/scripts/omw/ai.lua b/files/data/scripts/omw/ai.lua index 0bf9e1bbec..f90b92fd87 100644 --- a/files/data/scripts/omw/ai.lua +++ b/files/data/scripts/omw/ai.lua @@ -1,5 +1,6 @@ local self = require('openmw.self') local interfaces = require('openmw.interfaces') +local util = require('openmw.util') local function startPackage(args) local cancelOther = args.cancelOther @@ -12,16 +13,34 @@ local function startPackage(args) self:_startAiPursue(args.target, cancelOther) elseif args.type == 'Follow' then if not args.target then error("target required") end - self:_startAiFollow(args.target, cancelOther) + self:_startAiFollow(args.target, args.cellId, args.duration or 0, args.destPosition or util.vector3(0, 0, 0), args.isRepeat or false, cancelOther) elseif args.type == 'Escort' then if not args.target then error("target required") end if not args.destPosition then error("destPosition required") end self:_startAiEscort(args.target, args.destCell or self.cell, args.duration or 0, args.destPosition, cancelOther) elseif args.type == 'Wander' then - self:_startAiWander(args.distance or 0, args.duration or 0, cancelOther) + local key = "idle" + local idle = {} + local duration = 0 + for i = 2, 9 do + local val = args.idle[key .. i] + if val == nil then + idle[i-1] = 0 + else + local v = tonumber(val) or 0 + if v < 0 or v > 100 then + error("idle values cannot exceed 100") + end + idle[i-1] = v + end + end + if args.duration then + duration = args.duration / 3600 + end + self:_startAiWander(args.distance or 0, duration, idle, args.isRepeat or false, cancelOther) elseif args.type == 'Travel' then if not args.destPosition then error("destPosition required") end - self:_startAiTravel(args.destPosition, cancelOther) + self:_startAiTravel(args.destPosition, args.isRepeat or false, cancelOther) else error('Unsupported AI Package: ' .. args.type) end @@ -47,6 +66,10 @@ return { -- @field openmw.core#GameObject target Target (usually an actor) of the AI package (can be nil). -- @field #boolean sideWithTarget Whether to help the target in combat (true or false). -- @field openmw.util#Vector3 destPosition Destination point of the AI package. + -- @field #number distance Distance value (can be nil). + -- @field #number duration Duration value (can be nil). + -- @field #table idle Idle value (can be nil). + -- @field #boolean isRepeat Should this package be repeated (true or false). --- Return the currently active AI package (or `nil` if there are no AI packages). -- @function [parent=#AI] getActivePackage diff --git a/files/lang/components_en.ts b/files/lang/components_en.ts new file mode 100644 index 0000000000..fdc5b37d36 --- /dev/null +++ b/files/lang/components_en.ts @@ -0,0 +1,93 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + + + + Dependent file needs to be active: %1 + + + + This file needs to load after %1 + + + + + ContentSelectorModel::EsmFile + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + + + + ContentSelectorView::ContentSelector + + <No game file> + + + + &Check Selected + + + + &Uncheck Selected + + + + &Copy Path(s) to Clipboard + + + + + Process::ProcessInvoker + + Error starting executable + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + + + + Error running executable + + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + + + + +Arguments: + + + + + diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts new file mode 100644 index 0000000000..2d312ef8db --- /dev/null +++ b/files/lang/launcher_en.ts @@ -0,0 +1,1475 @@ + + + + + DataFilesPage + + Content Files + + + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Data Directories + + + + Scan directories for likely data directories and append them at the end of the list. + + + + Append + + + + Scan directories for likely data directories and insert them above the selected position + + + + Insert Above + + + + Move selected directory one position up + + + + Move Up + + + + Move selected directory one position down + + + + Move Down + + + + Remove selected directory + + + + Remove + + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Archive Files + + + + Move selected archive one position up + + + + Move selected archive one position down + + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Navigation Mesh Cache + + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + + + + Update + + + + Cancel navigation mesh generation. Already processed data will be saved. + + + + Cancel + + + + Remove Unused Tiles + + + + Max Size + + + + MiB + + + + Content List + + + + Select a content list + + + + New Content List + + + + &New Content List + + + + Clone Content List + + + + Delete Content List + + + + Ctrl+N + + + + Ctrl+G + + + + Ctrl+D + + + + Check Selection + + + + Uncheck Selection + + + + Refresh Data Files + + + + Ctrl+R + + + + + GraphicsPage + + Screen + + + + Window Mode + + + + × + + + + Custom: + + + + Standard: + + + + 0 + + + + 2 + + + + 4 + + + + 8 + + + + 16 + + + + Framerate Limit + + + + Window Border + + + + Disabled + + + + Enabled + + + + Adaptive + + + + Fullscreen + + + + Windowed Fullscreen + + + + Windowed + + + + Resolution + + + + FPS + + + + Anti-Aliasing + + + + Vertical Synchronization + + + + + ImportPage + + Form + + + + Morrowind Installation Wizard + + + + Run &Installation Wizard + + + + Morrowind Settings Importer + + + + File to Import Settings From: + + + + Browse... + + + + Import Add-on and Plugin Selection + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + + + + Import Bitmap Fonts + + + + Run &Settings Importer + + + + + Launcher::DataFilesPage + + English + + + + French + + + + German + + + + Italian + + + + Polish + + + + Russian + + + + Spanish + + + + New Content List + + + + Content List name: + + + + Clone Content List + + + + &Check Selected + + + + &Uncheck Selected + + + + Resolved as %1 + + + + Will be added to the current profile + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + Contains content file(s) + + + + This archive is enabled in an openmw.cfg other than the user one + + + + Select Directory + + + + Delete Content List + + + + Are you sure you want to delete <b>%1</b>? + + + + Delete + + + + + Launcher::GraphicsPage + + Error receiving number of screens + + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + + + + Screen + + + + Error receiving resolutions + + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + Morrowind configuration file (*.ini) + + + + Importer finished + + + + Failed to import settings from INI file. + + + + + Launcher::MainDialog + + Close + + + + Launch OpenMW + + + + Help + + + + Error creating OpenMW configuration directory: code %0 + + + + <br><b>Could not create directory %0</b><br><br>%1<br> + + + + First run + + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + + + + Run &Installation Wizard + + + + Skip + + + + OpenMW %1 release + + + + OpenMW development (%1) + + + + Compiled on %1 %2 + + + + Error opening OpenMW configuration file + + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + + + + Error detecting Morrowind installation + + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + + + + Run &Installation Wizard... + + + + Error reading OpenMW configuration files + + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + + + + Error writing OpenMW configuration file + + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + + + + Error writing user settings file + + + + Error writing Launcher configuration file + + + + No game file selected + + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + + + + + Launcher::SettingsPage + + Text file (*.txt) + + + + + MainWindow + + OpenMW Launcher + + + + OpenMW version + + + + toolBar + + + + Data Files + + + + Allows to setup data files and directories + + + + Display + + + + Allows to change display settings + + + + Settings + + + + Allows to tweak engine settings + + + + Import + + + + Allows to import data from original engine + + + + + QObject + + Select configuration file + + + + Select script file + + + + + SelectSubdirs + + Select directories you wish to add + + + + + SettingsPage + + Gameplay + + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + + + + Uncapped Damage Fatigue + + + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + + + + Always Allow Actors to Follow over Water + + + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + + Permanent Barter Disposition Changes + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + + Racial Variation in Speed Fix + + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + + + + Classic Calm Spells Behavior + + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + + + + NPCs Avoid Collisions + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + + + + Soulgem Values Rebalance + + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + + + + Day Night Switch Nodes + + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + + + + Followers Defend Immediately + + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + + + + Use Navigation Mesh for Pathfinding + + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + + + + Only Magical Ammo Bypass Resistance + + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + + + Graphic Herbalism + + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + + + + Swim Upward Correction + + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + + Enchanted Weapons Are Magical + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + + Merchant Equipping Fix + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + + Trainers Choose Offered Skills by Base Value + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + + + + Can Loot During Death Animation + + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + + + + Steal from Knocked out Actors in Combat + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + + + + Classic Reflected Absorb Spells Behavior + + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + + + + Unarmed Creature Attacks Damage Armor + + + + Factor Strength into Hand-to-Hand Combat + + + + Off + + + + Affect Werewolves + + + + Do Not Affect Werewolves + + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + + + + Background Physics Threads + + + + Actor Collision Shape Type + + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + + + + Axis-Aligned Bounding Box + + + + Rotating Box + + + + Cylinder + + + + Visuals + + + + Animations + + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + + + + Smooth Movement + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + + Use Additional Animation Sources + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + + + + Turn to Movement Direction + + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + + + + Weapon Sheathing + + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + + + + Shield Sheathing + + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + + Player Movement Ignores Animation + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + + Use Magic Item Animation + + + + Shaders + + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + + + + Auto Use Object Normal Maps + + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + + + + Soft Particles + + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + + + + Auto Use Object Specular Maps + + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + + + + Auto Use Terrain Normal Maps + + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + + + + Auto Use Terrain Specular Maps + + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + + + + Adjust Coverage for Alpha Test + + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + + + + Use Anti-Aliased Alpha Testing + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + + + + Bump/Reflect Map Local Lighting + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + + Weather Particle Occlusion + + + + Fog + + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + + + + Exponential Fog + + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + + + + Radial Fog + + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + + + + Sky Blending Start + + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + + + + Sky Blending + + + + Terrain + + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + + Object Paging Min Size + + + + Viewing Distance + + + + cells + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + + Distant Land + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + + Active Grid Object Paging + + + + Post Processing + + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + + + + Transparent Postpass + + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + + + Auto Exposure Speed + + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + + + + Enable Post Processing + + + + Shadows + + + + Bounds + + + + Primitives + + + + None + + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + + + + Shadow Planes Computation Method + + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + + + + unit(s) + + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + + + + Enable Actor Shadows + + + + 512 + + + + 1024 + + + + 2048 + + + + 4096 + + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + + + + Fade Start Multiplier + + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + + + + Enable Player Shadows + + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + + + + Shadow Map Resolution + + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + + + + Shadow Distance Limit: + + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + + + + Enable Object Shadows + + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + + + Enable Indoor Shadows + + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + + + + Enable Terrain Shadows + + + + Lighting + + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + + + + Maximum Light Distance + + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + + + + Max Lights + + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + + + + Lighting Method + + + + Legacy + + + + Shaders (compatibility) + + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + + + + Bounding Sphere Multiplier + + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + + + + Minimum Interior Brightness + + + + Audio + + + + Select your preferred audio device. + + + + Audio Device + + + + Default + + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + + + + HRTF + + + + Automatic + + + + On + + + + Select your preferred HRTF profile. + + + + HRTF Profile + + + + In third-person view, use the camera as the sound listener instead of the player character. + + + + Use the Camera as the Sound Listener + + + + Interface + + + + Tooltip + + + + Crosshair + + + + Tooltip and Crosshair + + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + + + + GUI Scaling Factor + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + + Show Effect Duration + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + + Change Dialogue Topic Color + + + + Size of characters in game texts. + + + + Font Size + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + + + Can Zoom on Maps + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show Projectile Damage + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show Melee Info + + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + + + + Stretch Menu Background + + + + Show Owned Objects + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + + Show Enchant Chance + + + + Miscellaneous + + + + Saves + + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + + + + Add "Time Played" to Saves + + + + Maximum Quicksaves + + + + Screenshots + + + + Screenshot Format + + + + JPG + + + + PNG + + + + TGA + + + + Notify on Saved Screenshot + + + + Testing + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + + Grab Cursor + + + + Skip Menu and Generate Default Character + + + + Start Default Character at + + + + Default Cell + + + + Run Script After Startup: + + + + Browse… + + + + diff --git a/files/lang/wizard_en.ts b/files/lang/wizard_en.ts new file mode 100644 index 0000000000..790d8e0589 --- /dev/null +++ b/files/lang/wizard_en.ts @@ -0,0 +1,656 @@ + + + + + ComponentSelectionPage + + WizardPage + + + + Select Components + + + + Which components should be installed? + + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + + + + Selected components: + + + + + ConclusionPage + + WizardPage + + + + Completing the OpenMW Wizard + + + + Placeholder + + + + + ExistingInstallationPage + + WizardPage + + + + Select Existing Installation + + + + Select an existing installation for OpenMW to use or modify. + + + + Detected installations: + + + + Browse... + + + + + ImportPage + + WizardPage + + + + Import Settings + + + + Import settings from the Morrowind installation. + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + + + + Import Settings From Morrowind.ini + + + + Import Add-on and Plugin Selection + + + + Import Bitmap Fonts Setup From Morrowind.ini + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + + + + + InstallationPage + + WizardPage + + + + Installing + + + + Please wait while Morrowind is installed on your computer. + + + + + InstallationTargetPage + + WizardPage + + + + Select Installation Destination + + + + Where should Morrowind be installed? + + + + Morrowind will be installed to the following location. + + + + Browse... + + + + + IntroPage + + WizardPage + + + + Welcome to the OpenMW Wizard + + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + + + + + LanguageSelectionPage + + WizardPage + + + + Select Morrowind Language + + + + What is the language of the Morrowind installation? + + + + Select the language of the Morrowind installation. + + + + + MethodSelectionPage + + WizardPage + + + + Select Installation Method + + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + + Retail CD/DVD + + + + Install from a retail disc to a new location. + + + + Existing Installation + + + + Select an existing installation. + + + + Don't have a copy? + + + + Buy the game + + + + + QObject + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + + + + B&rowse... + + + + Select configuration file + + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + + + + Most recent Morrowind not detected + + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + + + + + Wizard::ComponentSelectionPage + + &Install + + + + &Skip + + + + Morrowind (installed) + + + + Morrowind + + + + Tribunal (installed) + + + + Tribunal + + + + Bloodmoon (installed) + + + + Bloodmoon + + + + About to install Tribunal after Bloodmoon + + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + + + + Re-install &Bloodmoon + + + + + Wizard::ConclusionPage + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + + + + + Wizard::ExistingInstallationPage + + No existing installations detected + + + + Error detecting Morrowind configuration + + + + Morrowind configuration file (*.ini) + + + + Select Morrowind.esm (located in Data Files) + + + + Morrowind master file (Morrowind.esm) + + + + Error detecting Morrowind files + + + + + Wizard::InstallationPage + + <p>Attempting to install component %1.</p> + + + + Attempting to install component %1. + + + + %1 Installation + + + + Select %1 installation media + + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + + + + <p>Detected old version of component Morrowind.</p> + + + + Detected old version of component Morrowind. + + + + Morrowind Installation + + + + Installation finished + + + + Installation completed successfully! + + + + Installation failed! + + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + + + + <p><span style="color:red;"><b>%1</b></p> + + + + An error occurred + + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + + + + + Wizard::InstallationTargetPage + + Error creating destination + + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + + + + Insufficient permissions + + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + + + + Destination not empty + + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + + + + Select where to install Morrowind + + + + + Wizard::LanguageSelectionPage + + English + + + + French + + + + German + + + + Italian + + + + Polish + + + + Russian + + + + Spanish + + + + + Wizard::MainWizard + + OpenMW Wizard + + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + Error opening Wizard log file + + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + Error opening OpenMW configuration file + + + + Quit Wizard + + + + Are you sure you want to exit the Wizard? + + + + Error creating OpenMW configuration directory + + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + Error writing OpenMW configuration file + + + + + Wizard::UnshieldWorker + + Failed to open Morrowind configuration file! + + + + Opening %1 failed: %2. + + + + Failed to write Morrowind configuration file! + + + + Writing to %1 failed: %2. + + + + Installing: %1 + + + + Installing: %1 directory + + + + Installation finished! + + + + Component parameter is invalid! + + + + An invalid component parameter was supplied. + + + + Failed to find a valid archive containing %1.bsa! Retrying. + + + + Installing %1 + + + + Installation media path not set! + + + + The source path for %1 was not set. + + + + Cannot create temporary directory! + + + + Failed to create %1. + + + + Cannot move into temporary directory! + + + + Failed to move into %1. + + + + Moving installation files + + + + Could not install directory! + + + + Installing %1 to %2 failed. + + + + Could not install translation file! + + + + Failed to install *%1 files. + + + + Could not install Morrowind data file! + + + + Failed to install %1. + + + + Could not install Morrowind configuration file! + + + + Installing: Sound directory + + + + Could not find Tribunal data file! + + + + Failed to find %1. + + + + Could not find Tribunal patch file! + + + + Could not find Bloodmoon data file! + + + + Updating Morrowind configuration file + + + + %1 installation finished! + + + + Extracting: %1 + + + + Failed to open InstallShield Cabinet File. + + + + Opening %1 failed. + + + + Failed to extract %1. + + + + Complete path: %1 + + + + diff --git a/files/opencs/brush-circle.svg b/files/opencs/brush-circle.svg index 8bb2491173..9a7334055d 100644 --- a/files/opencs/brush-circle.svg +++ b/files/opencs/brush-circle.svg @@ -41,7 +41,7 @@ style="fill:#4d4d4d;fill-opacity:1;stroke:#808080;stroke-width:8.21086;stroke-dasharray:none;stroke-opacity:1" /> @@ -43,12 +43,12 @@ style="display:inline" transform="matrix(1.3333345,0,0,1.333334,-63.852829,-127.35283)"> + + + + + + @@ -25,12 +49,12 @@ style="display:inline" transform="matrix(1.3333345,0,0,1.333334,-21.519464,-127.35283)"> levelled-creature.svg levelled-item.svg light.svg - list-base.svg list-added.svg + list-base.svg list-modified.svg list-removed.svg lockpick.svg diff --git a/files/opencs/scene-exterior-arrows.svg b/files/opencs/scene-exterior-arrows.svg index 1f923c91bf..c99eb6fc8a 100644 --- a/files/opencs/scene-exterior-arrows.svg +++ b/files/opencs/scene-exterior-arrows.svg @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ id="mask-powermask-path-effect3119"> @@ -95,7 +95,7 @@ id="mask-powermask-path-effect1105"> diff --git a/files/opencs/scene-exterior-borders.svg b/files/opencs/scene-exterior-borders.svg index 4a9c2ea4fa..3c63b135bc 100644 --- a/files/opencs/scene-exterior-borders.svg +++ b/files/opencs/scene-exterior-borders.svg @@ -14,7 +14,7 @@ @@ -30,7 +30,7 @@ id="mask-powermask-path-effect3119"> @@ -94,7 +94,7 @@ id="mask-powermask-path-effect1105"> diff --git a/files/opencs/scene-exterior-markers.svg b/files/opencs/scene-exterior-markers.svg index 63900d752f..5203fed48d 100644 --- a/files/opencs/scene-exterior-markers.svg +++ b/files/opencs/scene-exterior-markers.svg @@ -14,7 +14,7 @@ @@ -30,7 +30,7 @@ id="mask-powermask-path-effect3119"> @@ -94,7 +94,7 @@ id="mask-powermask-path-effect1105"> @@ -31,7 +31,7 @@ id="mask-powermask-path-effect3119"> @@ -95,7 +95,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + - - + + diff --git a/files/opencs/scene-exterior-status-1.svg b/files/opencs/scene-exterior-status-1.svg index f699d767c2..cabf1f75e0 100644 --- a/files/opencs/scene-exterior-status-1.svg +++ b/files/opencs/scene-exterior-status-1.svg @@ -16,7 +16,7 @@ id="Main" gradientTransform="translate(31.749998)"> @@ -32,7 +32,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +96,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + - - + + diff --git a/files/opencs/scene-exterior-status-2.svg b/files/opencs/scene-exterior-status-2.svg index 2d1ab9bbf8..2a1234e667 100644 --- a/files/opencs/scene-exterior-status-2.svg +++ b/files/opencs/scene-exterior-status-2.svg @@ -16,7 +16,7 @@ id="Main" gradientTransform="translate(95.249992)"> @@ -32,7 +32,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +96,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + - - + + diff --git a/files/opencs/scene-exterior-status-3.svg b/files/opencs/scene-exterior-status-3.svg index 401f19a5fa..b76cf0dac0 100644 --- a/files/opencs/scene-exterior-status-3.svg +++ b/files/opencs/scene-exterior-status-3.svg @@ -16,7 +16,7 @@ id="Main" gradientTransform="translate(190.49998)"> @@ -32,7 +32,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +96,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + + + - - diff --git a/files/opencs/scene-exterior-status-4.svg b/files/opencs/scene-exterior-status-4.svg index aebc14b932..afe0215302 100644 --- a/files/opencs/scene-exterior-status-4.svg +++ b/files/opencs/scene-exterior-status-4.svg @@ -16,7 +16,7 @@ id="Main" gradientTransform="translate(317.49996)"> @@ -32,7 +32,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +96,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + - - + + diff --git a/files/opencs/scene-exterior-status-5.svg b/files/opencs/scene-exterior-status-5.svg index 3fb8b98a5f..fc3a7eb7f3 100644 --- a/files/opencs/scene-exterior-status-5.svg +++ b/files/opencs/scene-exterior-status-5.svg @@ -16,7 +16,7 @@ id="Main" gradientTransform="translate(476.24994)"> @@ -32,7 +32,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +96,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + + - - - + + + diff --git a/files/opencs/scene-exterior-status-6.svg b/files/opencs/scene-exterior-status-6.svg index 93c905935a..2a73b15bb1 100644 --- a/files/opencs/scene-exterior-status-6.svg +++ b/files/opencs/scene-exterior-status-6.svg @@ -16,7 +16,7 @@ id="Main" gradientTransform="translate(666.74992)"> @@ -32,7 +32,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +96,7 @@ id="mask-powermask-path-effect1105"> + + + + + + + + + - - - + + + diff --git a/files/opencs/scene-exterior-status-7.svg b/files/opencs/scene-exterior-status-7.svg index 89b97866d8..221c69050b 100644 --- a/files/opencs/scene-exterior-status-7.svg +++ b/files/opencs/scene-exterior-status-7.svg @@ -13,10 +13,19 @@ + + + @@ -32,7 +41,7 @@ id="mask-powermask-path-effect3119"> @@ -96,7 +105,7 @@ id="mask-powermask-path-effect1105"> diff --git a/files/opencs/scene-view-instance.svg b/files/opencs/scene-view-instance.svg index 771432bebf..8fe27a1bab 100644 --- a/files/opencs/scene-view-instance.svg +++ b/files/opencs/scene-view-instance.svg @@ -145,7 +145,7 @@ cy="116.15208" r="1.5875" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/opencs/scene-view-status-1.svg b/files/opencs/scene-view-status-1.svg index 3dbfa1622a..cfcc7203c2 100644 --- a/files/opencs/scene-view-status-1.svg +++ b/files/opencs/scene-view-status-1.svg @@ -144,7 +144,7 @@ cy="116.15208" r="1.5875" /> + transform="translate(158.81609,-0.529153)"> - - - - + + + + + - + transform="translate(127.06613,15.345815)"> diff --git a/files/opencs/scene-view-status-13.svg b/files/opencs/scene-view-status-13.svg index 1f2b3ab398..e83dded670 100644 --- a/files/opencs/scene-view-status-13.svg +++ b/files/opencs/scene-view-status-13.svg @@ -117,7 +117,7 @@ + transform="matrix(1.0000013,0,0,1,190.56606,-0.529153)"> + transform="translate(206.44103,-0.529153)"> diff --git a/files/opencs/scene-view-status-9.svg b/files/opencs/scene-view-status-9.svg index 5d101ad1f4..598b408732 100644 --- a/files/opencs/scene-view-status-9.svg +++ b/files/opencs/scene-view-status-9.svg @@ -117,7 +117,7 @@ + transform="matrix(0.99999702,0,0,0.99997478,142.87492,0.00291574)"> diff --git a/files/opencs/selection-mode-cube-corner.svg b/files/opencs/selection-mode-cube-corner.svg index 63d5f73c56..21c28e1fc3 100644 --- a/files/opencs/selection-mode-cube-corner.svg +++ b/files/opencs/selection-mode-cube-corner.svg @@ -29,34 +29,34 @@ + transform="matrix(0.95633709,0,0,0.97689208,-70.343751,-63.444673)"> diff --git a/files/opencs/selection-mode-cube.svg b/files/opencs/selection-mode-cube.svg index e03138bafe..d237a7746a 100644 --- a/files/opencs/selection-mode-cube.svg +++ b/files/opencs/selection-mode-cube.svg @@ -28,30 +28,30 @@ + style="display:inline;stroke-width:0.183665;stroke-dasharray:none" + transform="matrix(0.94403189,0,0,0.96988505,-69.504358,-62.928849)"> diff --git a/files/opencs/selection-mode-sphere.svg b/files/opencs/selection-mode-sphere.svg index 9f8ed79340..64bfbb0916 100644 --- a/files/opencs/selection-mode-sphere.svg +++ b/files/opencs/selection-mode-sphere.svg @@ -31,15 +31,13 @@ style="display:inline" transform="matrix(0.95633709,0,0,0.97689208,-70.52014,-63.444673)"> + style="display:inline;fill:none;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.18246;stroke-dasharray:none;stroke-opacity:1" + d="m 76.860371,76.010117 8.085388,-1.488233 3.370361,3.890874 -8.140511,1.599832 z" + id="path6-9" /> + style="display:inline;fill:none;stroke:#4d4d4d;stroke-width:0.18246;stroke-dasharray:none;stroke-opacity:1" + d="m 84.948261,67.211494 0.0099,7.346147" + id="path9" /> + rx="3.5277777" + ry="3.5277779" /> diff --git a/files/opencs/transform-move.svg b/files/opencs/transform-move.svg index 1b9490f001..0cbe1c89f4 100644 --- a/files/opencs/transform-move.svg +++ b/files/opencs/transform-move.svg @@ -26,7 +26,7 @@ gradientTransform="matrix(0.26458333,0,0,0.26458333,-13.80713,103.48169)" osb:paint="solid"> diff --git a/files/opencs/transform-rotate.svg b/files/opencs/transform-rotate.svg index 29bc3fdad5..83f2ca19a7 100644 --- a/files/opencs/transform-rotate.svg +++ b/files/opencs/transform-rotate.svg @@ -18,7 +18,7 @@ gradientTransform="matrix(0.26458333,0,0,0.26458333,-13.80713,103.48169)" osb:paint="solid"> diff --git a/files/opencs/transform-scale.svg b/files/opencs/transform-scale.svg index e4d1d16422..14a8062daf 100644 --- a/files/opencs/transform-scale.svg +++ b/files/opencs/transform-scale.svg @@ -18,7 +18,7 @@ gradientTransform="matrix(0.26458333,0,0,0.26458333,-13.80713,103.48169)" osb:paint="solid"> diff --git a/files/shaders/lib/light/lighting_util.glsl b/files/shaders/lib/light/lighting_util.glsl index afa38af86b..059f024b4a 100644 --- a/files/shaders/lib/light/lighting_util.glsl +++ b/files/shaders/lib/light/lighting_util.glsl @@ -59,6 +59,39 @@ uniform int PointLightCount; #endif +float lcalcConstantAttenuation(int lightIndex) +{ +#if @lightingMethodPerObjectUniform + return @getLight[lightIndex][0].w; +#elif @lightingMethodUBO + return @getLight[lightIndex].attenuation.x; +#else + return @getLight[lightIndex].constantAttenuation; +#endif +} + +float lcalcLinearAttenuation(int lightIndex) +{ +#if @lightingMethodPerObjectUniform + return @getLight[lightIndex][1].w; +#elif @lightingMethodUBO + return @getLight[lightIndex].attenuation.y; +#else + return @getLight[lightIndex].linearAttenuation; +#endif +} + +float lcalcQuadraticAttenuation(int lightIndex) +{ +#if @lightingMethodPerObjectUniform + return @getLight[lightIndex][2].w; +#elif @lightingMethodUBO + return @getLight[lightIndex].attenuation.z; +#else + return @getLight[lightIndex].quadraticAttenuation; +#endif +} + #if !@lightingMethodFFP float lcalcRadius(int lightIndex) { @@ -70,17 +103,16 @@ float lcalcRadius(int lightIndex) } #endif -float lcalcIllumination(int lightIndex, float lightDistance) +float lcalcIllumination(int lightIndex, float dist) { -#if @lightingMethodPerObjectUniform - float illumination = clamp(1.0 / (@getLight[lightIndex][0].w + @getLight[lightIndex][1].w * lightDistance + @getLight[lightIndex][2].w * lightDistance * lightDistance), 0.0, 1.0); - return (illumination * (1.0 - quickstep((lightDistance / lcalcRadius(lightIndex)) - 1.0))); -#elif @lightingMethodUBO - float illumination = clamp(1.0 / (@getLight[lightIndex].attenuation.x + @getLight[lightIndex].attenuation.y * lightDistance + @getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); - return (illumination * (1.0 - quickstep((lightDistance / lcalcRadius(lightIndex)) - 1.0))); -#else - return clamp(1.0 / (@getLight[lightIndex].constantAttenuation + @getLight[lightIndex].linearAttenuation * lightDistance + @getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); + float illumination = 1.0 / (lcalcConstantAttenuation(lightIndex) + lcalcLinearAttenuation(lightIndex) * dist + lcalcQuadraticAttenuation(lightIndex) * dist * dist); + // FIXME: FFP doesn't do this + illumination = clamp(illumination, 0.0, 1.0); +#if @lightingMethodPerObjectUniform || @lightingMethodUBO + // Fade illumination between the radius and the radius doubled to diminish pop-in + illumination *= 1.0 - quickstep((dist / lcalcRadius(lightIndex)) - 1.0); #endif + return illumination; } vec3 lcalcPosition(int lightIndex)