From 10fe3342477816e99e7d9cd12e726cd09146f98d Mon Sep 17 00:00:00 2001 From: Florian Weber Date: Fri, 2 Mar 2018 15:05:00 +0100 Subject: [PATCH 01/72] add more precise float-spinbox and use it for rotations --- apps/opencs/model/world/columnbase.hpp | 1 + apps/opencs/model/world/columnimp.hpp | 2 +- apps/opencs/model/world/refidcollection.cpp | 6 +++--- apps/opencs/view/world/util.cpp | 9 +++++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index d609a6253..df37afe60 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -88,6 +88,7 @@ namespace CSMWorld Display_UnsignedInteger8, Display_Integer, Display_Float, + Display_Double, Display_Var, Display_GmstVarType, Display_GlobalVarType, diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index f1025acb9..4ad447b0a 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1371,7 +1371,7 @@ namespace CSMWorld RotColumn (ESM::Position ESXRecordT::* position, int index, bool door) : Column ( (door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index, - ColumnBase::Display_Float), mPosition (position), mIndex (index) {} + ColumnBase::Display_Double), mPosition (position), mIndex (index) {} virtual QVariant get (const Record& record) const { diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 44a6ce07d..daebc2dcb 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double)); // Nested table mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList, diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index efba1ea82..eab37e1bf 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -233,6 +233,15 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return dsb; } + case CSMWorld::ColumnBase::Display_Double: + { + DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); + dsb->setRange(-FLT_MAX, FLT_MAX); + dsb->setSingleStep(0.01f); + dsb->setDecimals(6); + return dsb; + } + case CSMWorld::ColumnBase::Display_LongString: { QPlainTextEdit *edit = new QPlainTextEdit(parent); From da74ca5ce0242cfd107a98d7934835aba178024f Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Mon, 5 Mar 2018 21:26:59 -0600 Subject: [PATCH 02/72] Add testing options to the Settings page --- apps/launcher/settingspage.cpp | 42 ++++ apps/launcher/settingspage.hpp | 1 + files/ui/settingspage.ui | 367 ++++++++++++++++++++------------- 3 files changed, 265 insertions(+), 145 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 843b51391..906459c23 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -177,6 +177,28 @@ void Launcher::SettingsPage::on_browseButton_clicked() } } +void Launcher::SettingsPage::on_runScriptAfterStartupBrowseButton_clicked() +{ + QString scriptFile = QFileDialog::getOpenFileName( + this, + QObject::tr("Select script file"), + QDir::currentPath(), + QString(tr("Text file (*.txt)"))); + + + if (scriptFile.isEmpty()) + return; + + QFileInfo info(scriptFile); + + if (!info.exists() || !info.isReadable()) + return; + + const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); + + runScriptAfterStartupField->setText(path); +} + void Launcher::SettingsPage::wizardStarted() { mMain->hide(); // Hide the launcher @@ -260,6 +282,19 @@ void Launcher::SettingsPage::saveSettings() } else { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); } + + // Testing + int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; + if (skipMenu != mGameSettings.value("skip-menu").toInt()) + mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + + QString startCell = startDefaultCharacterAtField->text(); + if (startCell != mGameSettings.value("start")) { + mGameSettings.setValue("start", startCell); + } + QString scriptRun = runScriptAfterStartupField->text(); + if (scriptRun != mGameSettings.value("script-run")) + mGameSettings.setValue("script-run", scriptRun); } bool Launcher::SettingsPage::loadSettings() @@ -271,5 +306,12 @@ bool Launcher::SettingsPage::loadSettings() if (index != -1) languageComboBox->setCurrentIndex(index); + // Testing + if (mGameSettings.value("skip-menu").toInt() == 1) + skipMenuCheckBox->setCheckState(Qt::Checked); + + startDefaultCharacterAtField->setText(mGameSettings.value("start")); + runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + return true; } diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index ccc2061dd..12539a0fc 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -38,6 +38,7 @@ namespace Launcher void on_wizardButton_clicked(); void on_importerButton_clicked(); void on_browseButton_clicked(); + void on_runScriptAfterStartupBrowseButton_clicked(); void wizardStarted(); void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index f4f41f839..eed723adb 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -7,7 +7,7 @@ 0 0 514 - 397 + 532 @@ -15,153 +15,230 @@ - - - Morrowind Content Language + + + true - - - - - - 250 - 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 0 + 0 + 473 + 510 + + + + + + + Morrowind Content Language + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 250 + 0 + + + + + + + + + + + Morrowind Installation Wizard + + + + + + Run &Installation Wizard + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Morrowind Settings Importer + + + + + + + + File to import settings from: + + + + + + + + + + Browse... + + + + + + + + + Import add-on and plugin selection (creates a new Content List) + + + true + + + + + + + + + Run &Settings Importer + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 4 + + + false + + + + + + + + + + Testing + + + + + + Skip menu and generate default character + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Start default character at + + + + + + + default cell + + + + + + + + + Run script after startup: + + + + + + + + + + + + Browse… + + + + + + + + + + - - - - Morrowind Installation Wizard - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Run &Installation Wizard - - - - - - - - - - Morrowind Settings Importer - - - - - - - - File to import settings from: - - - - - - - - - - Browse... - - - - - - - - - Import add-on and plugin selection (creates a new Content List) - - - true - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Run &Settings Importer - - - - - - - - - 4 - - - false - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - From 082e166faefd2daa149d3af49657c98bbb67db5f Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Mon, 5 Mar 2018 21:41:29 -0600 Subject: [PATCH 03/72] Making "start default character at" field enabled or disabled by the previous checkbox --- apps/launcher/settingspage.cpp | 55 ++++++++++++++++++++-------------- apps/launcher/settingspage.hpp | 4 ++- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 906459c23..ec784c9d3 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -177,28 +177,6 @@ void Launcher::SettingsPage::on_browseButton_clicked() } } -void Launcher::SettingsPage::on_runScriptAfterStartupBrowseButton_clicked() -{ - QString scriptFile = QFileDialog::getOpenFileName( - this, - QObject::tr("Select script file"), - QDir::currentPath(), - QString(tr("Text file (*.txt)"))); - - - if (scriptFile.isEmpty()) - return; - - QFileInfo info(scriptFile); - - if (!info.exists() || !info.isReadable()) - return; - - const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); - - runScriptAfterStartupField->setText(path); -} - void Launcher::SettingsPage::wizardStarted() { mMain->hide(); // Hide the launcher @@ -269,6 +247,33 @@ void Launcher::SettingsPage::updateOkButton(const QString &text) : mProfileDialog->setOkButtonEnabled(true); } +void Launcher::SettingsPage::on_skipMenuCheckBox_stateChanged(int state) { + startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); + startDefaultCharacterAtField->setEnabled(state == Qt::Checked); +} + +void Launcher::SettingsPage::on_runScriptAfterStartupBrowseButton_clicked() +{ + QString scriptFile = QFileDialog::getOpenFileName( + this, + QObject::tr("Select script file"), + QDir::currentPath(), + QString(tr("Text file (*.txt)"))); + + + if (scriptFile.isEmpty()) + return; + + QFileInfo info(scriptFile); + + if (!info.exists() || !info.isReadable()) + return; + + const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); + + runScriptAfterStartupField->setText(path); +} + void Launcher::SettingsPage::saveSettings() { QString language(languageComboBox->currentText()); @@ -307,8 +312,12 @@ bool Launcher::SettingsPage::loadSettings() languageComboBox->setCurrentIndex(index); // Testing - if (mGameSettings.value("skip-menu").toInt() == 1) + bool skipMenu = mGameSettings.value("skip-menu").toInt() == Qt::Checked; + if (skipMenu) { skipMenuCheckBox->setCheckState(Qt::Checked); + } + startDefaultCharacterAtLabel->setEnabled(skipMenu); + startDefaultCharacterAtField->setEnabled(skipMenu); startDefaultCharacterAtField->setText(mGameSettings.value("start")); runScriptAfterStartupField->setText(mGameSettings.value("script-run")); diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 12539a0fc..a9de974ff 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -38,7 +38,6 @@ namespace Launcher void on_wizardButton_clicked(); void on_importerButton_clicked(); void on_browseButton_clicked(); - void on_runScriptAfterStartupBrowseButton_clicked(); void wizardStarted(); void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); @@ -48,6 +47,9 @@ namespace Launcher void updateOkButton(const QString &text); + void on_skipMenuCheckBox_stateChanged(int state); + void on_runScriptAfterStartupBrowseButton_clicked(); + private: Process::ProcessInvoker *mWizardInvoker; From dcc262ed911fa4c0ba3db4e7f47e0d3cb22d80dd Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Mon, 5 Mar 2018 23:10:08 -0600 Subject: [PATCH 04/72] Fixing Skip Menu checkbox not working correctly --- apps/launcher/settingspage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index ec784c9d3..986aee048 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -312,7 +312,7 @@ bool Launcher::SettingsPage::loadSettings() languageComboBox->setCurrentIndex(index); // Testing - bool skipMenu = mGameSettings.value("skip-menu").toInt() == Qt::Checked; + bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; if (skipMenu) { skipMenuCheckBox->setCheckState(Qt::Checked); } From 6931f6cadc14128df681c66ee269cee739aba31c Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Wed, 7 Mar 2018 18:37:43 -0600 Subject: [PATCH 05/72] Adding message indicating the purpose of the "Testing" block --- files/ui/settingspage.ui | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index eed723adb..f1b833c60 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -23,9 +23,9 @@ 0 - 0 + -44 473 - 510 + 567 @@ -170,6 +170,23 @@ Testing + + + + These settings are intended for testing mods and may cause issues if used for normal gameplay. + + + true + + + + + + + Qt::Horizontal + + + From f07a12af733d6cabd3ce66cb49fedba474d7a8cd Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Thu, 8 Mar 2018 18:09:18 -0600 Subject: [PATCH 06/72] Changing label "and may cause issues" to "and will cause issues" --- files/ui/settingspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index f1b833c60..d0c77f4b7 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -173,7 +173,7 @@ - These settings are intended for testing mods and may cause issues if used for normal gameplay. + These settings are intended for testing mods and will cause issues if used for normal gameplay. true From d42791e26056e7dd98ab639f24b0627b2cff83a9 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Thu, 15 Mar 2018 22:11:54 -0500 Subject: [PATCH 07/72] Moving testing options to Advanced page --- apps/launcher/advancedpage.cpp | 58 ++++- apps/launcher/advancedpage.hpp | 9 +- apps/launcher/maindialog.cpp | 2 +- apps/launcher/settingspage.cpp | 51 ----- apps/launcher/settingspage.hpp | 3 - files/ui/advancedpage.ui | 99 ++++++++- files/ui/settingspage.ui | 384 +++++++++++++-------------------- 7 files changed, 300 insertions(+), 306 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 0abefcc8f..bc3308da0 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -1,10 +1,14 @@ #include "advancedpage.hpp" -#include +#include +#include -Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) +Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, + Config::GameSettings &gameSettings, + Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) + , mGameSettings(gameSettings) , mEngineSettings(engineSettings) { setObjectName ("AdvancedPage"); @@ -13,8 +17,45 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings: loadSettings(); } +void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) { + startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); + startDefaultCharacterAtField->setEnabled(state == Qt::Checked); +} + +void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked() +{ + QString scriptFile = QFileDialog::getOpenFileName( + this, + QObject::tr("Select script file"), + QDir::currentPath(), + QString(tr("Text file (*.txt)"))); + + + if (scriptFile.isEmpty()) + return; + + QFileInfo info(scriptFile); + + if (!info.exists() || !info.isReadable()) + return; + + const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); + +} + bool Launcher::AdvancedPage::loadSettings() { + // Testing + bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; + if (skipMenu) { + skipMenuCheckBox->setCheckState(Qt::Checked); + } + startDefaultCharacterAtLabel->setEnabled(skipMenu); + startDefaultCharacterAtField->setEnabled(skipMenu); + + startDefaultCharacterAtField->setText(mGameSettings.value("start")); + runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + // Game Settings loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); @@ -53,6 +94,19 @@ void Launcher::AdvancedPage::saveSettings() // Ensure we only set the new settings if they changed. This is to avoid cluttering the // user settings file (which by definition should only contain settings the user has touched) + // Testing + int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; + if (skipMenu != mGameSettings.value("skip-menu").toInt()) + mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + + QString startCell = startDefaultCharacterAtField->text(); + if (startCell != mGameSettings.value("start")) { + mGameSettings.setValue("start", startCell); + } + QString scriptRun = runScriptAfterStartupField->text(); + if (scriptRun != mGameSettings.value("script-run")) + mGameSettings.setValue("script-run", scriptRun); + // Game Settings saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index a8361c98e..0a9957b9c 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -8,6 +8,7 @@ #include namespace Files { struct ConfigurationManager; } +namespace Config { class GameSettings; } namespace Launcher { @@ -16,13 +17,19 @@ namespace Launcher Q_OBJECT public: - AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); + AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Settings::Manager &engineSettings, QWidget *parent = 0); bool loadSettings(); void saveSettings(); + private slots: + void on_skipMenuCheckBox_stateChanged(int state); + void on_runScriptAfterStartupBrowseButton_clicked(); + private: Files::ConfigurationManager &mCfgMgr; + Config::GameSettings &mGameSettings; Settings::Manager &mEngineSettings; void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 1a210ccc5..356863d03 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -119,7 +119,7 @@ void Launcher::MainDialog::createPages() mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mAdvancedPage = new AdvancedPage(mCfgMgr, mEngineSettings, this); + mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 986aee048..843b51391 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -247,33 +247,6 @@ void Launcher::SettingsPage::updateOkButton(const QString &text) : mProfileDialog->setOkButtonEnabled(true); } -void Launcher::SettingsPage::on_skipMenuCheckBox_stateChanged(int state) { - startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); - startDefaultCharacterAtField->setEnabled(state == Qt::Checked); -} - -void Launcher::SettingsPage::on_runScriptAfterStartupBrowseButton_clicked() -{ - QString scriptFile = QFileDialog::getOpenFileName( - this, - QObject::tr("Select script file"), - QDir::currentPath(), - QString(tr("Text file (*.txt)"))); - - - if (scriptFile.isEmpty()) - return; - - QFileInfo info(scriptFile); - - if (!info.exists() || !info.isReadable()) - return; - - const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); - - runScriptAfterStartupField->setText(path); -} - void Launcher::SettingsPage::saveSettings() { QString language(languageComboBox->currentText()); @@ -287,19 +260,6 @@ void Launcher::SettingsPage::saveSettings() } else { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); } - - // Testing - int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; - if (skipMenu != mGameSettings.value("skip-menu").toInt()) - mGameSettings.setValue("skip-menu", QString::number(skipMenu)); - - QString startCell = startDefaultCharacterAtField->text(); - if (startCell != mGameSettings.value("start")) { - mGameSettings.setValue("start", startCell); - } - QString scriptRun = runScriptAfterStartupField->text(); - if (scriptRun != mGameSettings.value("script-run")) - mGameSettings.setValue("script-run", scriptRun); } bool Launcher::SettingsPage::loadSettings() @@ -311,16 +271,5 @@ bool Launcher::SettingsPage::loadSettings() if (index != -1) languageComboBox->setCurrentIndex(index); - // Testing - bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; - if (skipMenu) { - skipMenuCheckBox->setCheckState(Qt::Checked); - } - startDefaultCharacterAtLabel->setEnabled(skipMenu); - startDefaultCharacterAtField->setEnabled(skipMenu); - - startDefaultCharacterAtField->setText(mGameSettings.value("start")); - runScriptAfterStartupField->setText(mGameSettings.value("script-run")); - return true; } diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index a9de974ff..ccc2061dd 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -47,9 +47,6 @@ namespace Launcher void updateOkButton(const QString &text); - void on_skipMenuCheckBox_stateChanged(int state); - void on_runScriptAfterStartupBrowseButton_clicked(); - private: Process::ProcessInvoker *mWizardInvoker; diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 6832b86df..f2224ff13 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -11,13 +11,6 @@ - - - - <html><head/><body><p>This temporary page contains new settings that will be available in-game in a post-1.0 release of OpenMW.</p></body></html> - - - @@ -27,9 +20,9 @@ 0 - -187 + 0 630 - 510 + 746 @@ -266,6 +259,94 @@ + + + + Testing + + + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + true + + + + + + + Qt::Horizontal + + + + + + + Skip menu and generate default character + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Start default character at + + + + + + + default cell + + + + + + + + + Run script after startup: + + + + + + + + + + + + Browse… + + + + + + + + diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index d0c77f4b7..f4f41f839 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -7,7 +7,7 @@ 0 0 514 - 532 + 397 @@ -15,247 +15,153 @@ - - - true + + + Morrowind Content Language - - - - 0 - -44 - 473 - 567 - - - - - - - Morrowind Content Language - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 250 - 0 - - - - - - - - - - - Morrowind Installation Wizard - - - - - - Run &Installation Wizard - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Morrowind Settings Importer - - - - - - - - File to import settings from: - - - - - - - - - - Browse... - - - - - - - - - Import add-on and plugin selection (creates a new Content List) - - - true - - - - - - - - - Run &Settings Importer - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 4 - - - false - - - - - - - - - - Testing - - - - - - These settings are intended for testing mods and will cause issues if used for normal gameplay. - - - true - - - - - - - Qt::Horizontal - - - - - - - Skip menu and generate default character - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - - - Start default character at - - - - - - - default cell - - - - - - - - - Run script after startup: - - - - - - - - - - - - Browse… - - - - - - - - - - + + + + + + 250 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + Morrowind Installation Wizard + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run &Installation Wizard + + + + + + + + + + Morrowind Settings Importer + + + + + + + + File to import settings from: + + + + + + + + + + Browse... + + + + + + + + + Import add-on and plugin selection (creates a new Content List) + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run &Settings Importer + + + + + + + + + 4 + + + false + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + From fb27f34a32ed87ea757dd591af6526ede2604aa7 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sat, 7 Apr 2018 21:27:36 -0500 Subject: [PATCH 08/72] Add autocomplete to the "Start default character at" field --- apps/launcher/CMakeLists.txt | 3 ++ apps/launcher/advancedpage.cpp | 18 ++++++++++ apps/launcher/advancedpage.hpp | 6 ++++ apps/launcher/datafilespage.cpp | 11 ++++++ apps/launcher/datafilespage.hpp | 6 ++++ apps/launcher/maindialog.cpp | 3 ++ apps/launcher/utils/cellnameloader.cpp | 48 ++++++++++++++++++++++++++ apps/launcher/utils/cellnameloader.hpp | 41 ++++++++++++++++++++++ 8 files changed, 136 insertions(+) create mode 100644 apps/launcher/utils/cellnameloader.cpp create mode 100644 apps/launcher/utils/cellnameloader.hpp diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index aec8c2533..54d7c3ece 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -8,6 +8,7 @@ set(LAUNCHER settingspage.cpp advancedpage.cpp + utils/cellnameloader.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp @@ -24,6 +25,7 @@ set(LAUNCHER_HEADER settingspage.hpp advancedpage.hpp + utils/cellnameloader.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp @@ -39,6 +41,7 @@ set(LAUNCHER_HEADER_MOC settingspage.hpp advancedpage.hpp + utils/cellnameloader.hpp utils/textinputdialog.hpp utils/profilescombobox.hpp utils/lineedit.hpp diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bc3308da0..bd077c12f 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -1,7 +1,12 @@ #include "advancedpage.hpp" #include +#include +#include #include +#include +#include +#include Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, @@ -17,6 +22,19 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, loadSettings(); } +void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList filePaths) { + CellNameLoader cellNameLoader; + QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(filePaths)); + std::sort(cellNamesList.begin(), cellNamesList.end()); + + // Set up an auto-completer for the "Start default character at" field + auto *completer = new QCompleter(cellNamesList); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + startDefaultCharacterAtField->setCompleter(completer); + +} + void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) { startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); startDefaultCharacterAtField->setEnabled(state == Qt::Checked); diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 0a9957b9c..654214f29 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -23,6 +23,12 @@ namespace Launcher bool loadSettings(); void saveSettings(); + /** + * Load the cells associated with the given content files for use in autocomplete + * @param filePaths the file paths of the content files to be examined + */ + void loadCellsForAutocomplete(QStringList filePaths); + private slots: void on_skipMenuCheckBox_stateChanged(int state); void on_runScriptAfterStartupBrowseButton_clicked(); diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 0b0f8c75e..484c8c56b 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -142,6 +142,17 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mGameSettings.setContentList(fileNames); } +QStringList Launcher::DataFilesPage::selectedFilePaths() +{ + //retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); + QStringList filePaths; + foreach(const ContentSelectorModel::EsmFile *item, items) { + filePaths.append(item->filePath()); + } + return filePaths; +} + void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.removeContentList(profile); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index d25d20fc9..9955737d5 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -41,6 +41,12 @@ namespace Launcher void saveSettings(const QString &profile = ""); bool loadSettings(); + /** + * Returns the file paths of all selected content files + * @return the file paths of all selected content files + */ + QStringList selectedFilePaths(); + signals: void signalProfileChanged (int index); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 356863d03..9967cbddc 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -125,6 +125,9 @@ void Launcher::MainDialog::createPages() mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex()); + // Load cells for the "Start default character at" field + mAdvancedPage->loadCellsForAutocomplete(mDataFilesPage->selectedFilePaths()); + // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); pagesWidget->addWidget(mDataFilesPage); diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp new file mode 100644 index 000000000..6d1ed2f49 --- /dev/null +++ b/apps/launcher/utils/cellnameloader.cpp @@ -0,0 +1,48 @@ +#include "cellnameloader.hpp" + +#include +#include + +QSet CellNameLoader::getCellNames(QStringList &contentPaths) +{ + QSet cellNames; + ESM::ESMReader esmReader; + + // Loop through all content files + for (auto &contentPath : contentPaths) { + esmReader.open(contentPath.toStdString()); + + // Loop through all records + while(esmReader.hasMoreRecs()) + { + ESM::NAME recordName = esmReader.getRecName(); + esmReader.getRecHeader(); + + if (isCellRecord(recordName)) { + QString cellName = getCellName(esmReader); + if (!cellName.isEmpty()) { + cellNames.insert(cellName); + } + } + + // Stop loading content for this record and continue to the next + esmReader.skipRecord(); + } + } + + return cellNames; +} + +bool CellNameLoader::isCellRecord(ESM::NAME &recordName) +{ + return recordName.intval == ESM::REC_CELL; +} + +QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) +{ + ESM::Cell cell; + bool isDeleted = false; + cell.loadNameAndData(esmReader, isDeleted); + + return QString::fromStdString(cell.mName); +} \ No newline at end of file diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp new file mode 100644 index 000000000..c58d09226 --- /dev/null +++ b/apps/launcher/utils/cellnameloader.hpp @@ -0,0 +1,41 @@ +#ifndef OPENMW_CELLNAMELOADER_H +#define OPENMW_CELLNAMELOADER_H + +#include +#include +#include + +#include + +namespace ESM {class ESMReader; struct Cell;} +namespace ContentSelectorView {class ContentSelector;} + +class CellNameLoader { + +public: + + /** + * Returns the names of all cells contained within the given content files + * @param contentPaths the file paths of each content file to be examined + * @return the names of all cells + */ + QSet getCellNames(QStringList &contentPaths); + +private: + /** + * Returns whether or not the given record is of type "Cell" + * @param name The name associated with the record + * @return whether or not the given record is of type "Cell" + */ + bool isCellRecord(ESM::NAME &name); + + /** + * Returns the name of the cell + * @param esmReader the reader currently pointed to a loaded cell + * @return the name of the cell + */ + QString getCellName(ESM::ESMReader &esmReader); +}; + + +#endif //OPENMW_CELLNAMELOADER_H From d58cce9c727dd6ddccea3bcee44338457615338f Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Tue, 22 May 2018 20:50:31 -0500 Subject: [PATCH 09/72] Adding WIP code to dynamically change the autocomplete fields --- apps/launcher/advancedpage.cpp | 5 +++++ apps/launcher/advancedpage.hpp | 3 +++ apps/launcher/datafilespage.cpp | 7 +++++++ apps/launcher/datafilespage.hpp | 3 +++ apps/launcher/maindialog.cpp | 1 + components/contentselector/view/contentselector.cpp | 13 +++++++++++++ components/contentselector/view/contentselector.hpp | 2 ++ 7 files changed, 34 insertions(+) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bd077c12f..383799e2a 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -162,4 +162,9 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str bool cValue = checkbox->checkState(); if (cValue != mEngineSettings.getBool(setting, group)) mEngineSettings.setBool(setting, group, cValue); +} + +void Launcher::AdvancedPage::slotSelectedDataFilesChanged(QStringList selectedFiles) +{ + loadCellsForAutocomplete(selectedFiles); } \ No newline at end of file diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 654214f29..c40ae2da1 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -29,6 +29,9 @@ namespace Launcher */ void loadCellsForAutocomplete(QStringList filePaths); + public slots: + void slotSelectedDataFilesChanged(QStringList selectedFiles); + private slots: void on_skipMenuCheckBox_stateChanged(int state); void on_runScriptAfterStartupBrowseButton_clicked(); diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 484c8c56b..8d4d2422f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -37,6 +37,8 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); + connect(mSelector, SIGNAL(signalSelectedFilesChanged(QStringList)), + this, SLOT(slotSelectedFilesChanged(QStringList))); buildView(); loadSettings(); @@ -319,3 +321,8 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) return (msgBox.clickedButton() == deleteButton); } + +void Launcher::DataFilesPage::slotSelectedFilesChanged(QStringList selectedFilesChanged) +{ + emit signalSelectedFilesChanged(selectedFilesChanged); +} \ No newline at end of file diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 9955737d5..fb9f9e04f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -7,6 +7,7 @@ #include #include +#include class QSortFilterProxyModel; class QAbstractItemModel; @@ -49,6 +50,7 @@ namespace Launcher signals: void signalProfileChanged (int index); + void signalSelectedFilesChanged(QStringList selectedFiles); public slots: void slotProfileChanged (int index); @@ -58,6 +60,7 @@ namespace Launcher void slotProfileChangedByUser(const QString &previous, const QString ¤t); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); + void slotSelectedFilesChanged (QStringList selectedFiles); void updateOkButton(const QString &text); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 9967cbddc..f3afb2732 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -142,6 +142,7 @@ void Launcher::MainDialog::createPages() connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); + connect(mDataFilesPage, SIGNAL(signalSelectedFilesChanged(QStringList)), mAdvancedPage, SLOT(slotSelectedDataFilesChanged(QStringList))); } diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index f4da7e6ad..4798d160c 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -216,6 +216,8 @@ void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos { QPoint globalPos = ui.addonView->viewport()->mapToGlobal(pos); mContextMenu->exec(globalPos); + // TODO This is a temporary workaround to demonstrate that the selected files signal can be sent + emitSelectedFilesChanged(); } void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) @@ -240,3 +242,14 @@ void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(true); } + +void ContentSelectorView::ContentSelector::emitSelectedFilesChanged() +{ + //retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = selectedFiles(); + QStringList filePaths; + foreach(const ContentSelectorModel::EsmFile *item, items) { + filePaths.append(item->filePath()); + } + emit signalSelectedFilesChanged(filePaths); +} \ No newline at end of file diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 9f775d597..5015306ab 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -56,11 +56,13 @@ namespace ContentSelectorView void buildContextMenu(); void setGameFileSelected(int index, bool selected); void setCheckStateForMultiSelectedItems(bool checked); + void emitSelectedFilesChanged(); signals: void signalCurrentGamefileIndexChanged (int); void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright); + void signalSelectedFilesChanged(QStringList selectedFiles); private slots: From c2fff61ccdc9c8397e7cae28f8c48bea5f6ab588 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sat, 26 May 2018 20:35:28 -0500 Subject: [PATCH 10/72] Changing so that data changes happen only after the addon is checked --- apps/launcher/datafilespage.cpp | 15 +++++++++++---- apps/launcher/datafilespage.hpp | 4 ++-- .../contentselector/view/contentselector.cpp | 13 ------------- .../contentselector/view/contentselector.hpp | 2 -- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 8d4d2422f..25a09ab1b 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -37,11 +37,14 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - connect(mSelector, SIGNAL(signalSelectedFilesChanged(QStringList)), - this, SLOT(slotSelectedFilesChanged(QStringList))); buildView(); loadSettings(); + + // Connect signal and slot after the settings have been loaded. We only care about the user changing + // the addons and don't want to get signals of the system doing it during startup. + connect(mSelector, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)), + this, SLOT(slotAddonDataChanged())); } void Launcher::DataFilesPage::buildView() @@ -322,7 +325,11 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) return (msgBox.clickedButton() == deleteButton); } -void Launcher::DataFilesPage::slotSelectedFilesChanged(QStringList selectedFilesChanged) +void Launcher::DataFilesPage::slotAddonDataChanged() { - emit signalSelectedFilesChanged(selectedFilesChanged); + QStringList selectedFiles = selectedFilePaths(); + if (previousSelectedFiles != selectedFiles) { + previousSelectedFiles = selectedFiles; + emit signalSelectedFilesChanged(selectedFiles); + } } \ No newline at end of file diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index fb9f9e04f..8893412be 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -60,7 +60,7 @@ namespace Launcher void slotProfileChangedByUser(const QString &previous, const QString ¤t); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); - void slotSelectedFilesChanged (QStringList selectedFiles); + void slotAddonDataChanged (); void updateOkButton(const QString &text); @@ -81,7 +81,7 @@ namespace Launcher Config::LauncherSettings &mLauncherSettings; QString mPreviousProfile; - + QStringList previousSelectedFiles; QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 4798d160c..5e16064f4 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -216,8 +216,6 @@ void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos { QPoint globalPos = ui.addonView->viewport()->mapToGlobal(pos); mContextMenu->exec(globalPos); - // TODO This is a temporary workaround to demonstrate that the selected files signal can be sent - emitSelectedFilesChanged(); } void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) @@ -241,15 +239,4 @@ void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(true); -} - -void ContentSelectorView::ContentSelector::emitSelectedFilesChanged() -{ - //retrieve the files selected for the profile - ContentSelectorModel::ContentFileList items = selectedFiles(); - QStringList filePaths; - foreach(const ContentSelectorModel::EsmFile *item, items) { - filePaths.append(item->filePath()); - } - emit signalSelectedFilesChanged(filePaths); } \ No newline at end of file diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 5015306ab..323f926ed 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -15,7 +15,6 @@ namespace ContentSelectorView Q_OBJECT QMenu *mContextMenu; - QStringList mFilePaths; protected: @@ -56,7 +55,6 @@ namespace ContentSelectorView void buildContextMenu(); void setGameFileSelected(int index, bool selected); void setCheckStateForMultiSelectedItems(bool checked); - void emitSelectedFilesChanged(); signals: void signalCurrentGamefileIndexChanged (int); From 78234e9468c2467a90e6a0bcf9101cebee76bb32 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 27 May 2018 16:47:09 -0500 Subject: [PATCH 11/72] Moving autocomplete code to thread --- apps/launcher/advancedpage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 383799e2a..bc0378314 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -166,5 +167,8 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str void Launcher::AdvancedPage::slotSelectedDataFilesChanged(QStringList selectedFiles) { - loadCellsForAutocomplete(selectedFiles); + // Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a + // barely perceptible UI lag. Splitting into its own thread to alleviate that. + std::thread loadCellsThread(&AdvancedPage::loadCellsForAutocomplete, this, selectedFiles); + loadCellsThread.join(); } \ No newline at end of file From 26dfef797040e6e944495ab017c0c8c4e6770748 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 27 May 2018 17:15:36 -0500 Subject: [PATCH 12/72] Changing where we are loading cells to prevent Qt access issue --- apps/launcher/advancedpage.cpp | 17 ++++------------- apps/launcher/advancedpage.hpp | 2 +- apps/launcher/datafilespage.cpp | 15 ++++++++++++++- apps/launcher/datafilespage.hpp | 1 + apps/launcher/maindialog.cpp | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bc0378314..3fe7c1f89 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -2,10 +2,8 @@ #include #include -#include #include #include -#include #include #include @@ -23,13 +21,9 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, loadSettings(); } -void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList filePaths) { - CellNameLoader cellNameLoader; - QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(filePaths)); - std::sort(cellNamesList.begin(), cellNamesList.end()); - +void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) { // Set up an auto-completer for the "Start default character at" field - auto *completer = new QCompleter(cellNamesList); + auto *completer = new QCompleter(cellNames); completer->setCompletionMode(QCompleter::PopupCompletion); completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); startDefaultCharacterAtField->setCompleter(completer); @@ -165,10 +159,7 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str mEngineSettings.setBool(setting, group, cValue); } -void Launcher::AdvancedPage::slotSelectedDataFilesChanged(QStringList selectedFiles) +void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) { - // Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a - // barely perceptible UI lag. Splitting into its own thread to alleviate that. - std::thread loadCellsThread(&AdvancedPage::loadCellsForAutocomplete, this, selectedFiles); - loadCellsThread.join(); + loadCellsForAutocomplete(cellNames); } \ No newline at end of file diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index c40ae2da1..82b9c3b65 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -30,7 +30,7 @@ namespace Launcher void loadCellsForAutocomplete(QStringList filePaths); public slots: - void slotSelectedDataFilesChanged(QStringList selectedFiles); + void slotLoadedCellsChanged(QStringList cellNames); private slots: void on_skipMenuCheckBox_stateChanged(int state); diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 25a09ab1b..1f46c7156 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -7,7 +7,9 @@ #include #include #include +#include +#include #include #include @@ -330,6 +332,17 @@ void Launcher::DataFilesPage::slotAddonDataChanged() QStringList selectedFiles = selectedFilePaths(); if (previousSelectedFiles != selectedFiles) { previousSelectedFiles = selectedFiles; - emit signalSelectedFilesChanged(selectedFiles); + // Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a + // barely perceptible UI lag. Splitting into its own thread to alleviate that. + std::thread loadCellsThread(&DataFilesPage::reloadCells, this, selectedFiles); + loadCellsThread.join(); } +} + +void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) +{ + CellNameLoader cellNameLoader; + QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); + std::sort(cellNamesList.begin(), cellNamesList.end()); + emit signalSelectedFilesChanged(cellNamesList); } \ No newline at end of file diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 8893412be..d871eeee0 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -96,6 +96,7 @@ namespace Launcher void addProfile (const QString &profile, bool setAsCurrent); void checkForDefaultProfile(); void populateFileViews(const QString& contentModelName); + void reloadCells(QStringList selectedFiles); class PathIterator { diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index f3afb2732..79f3e5c67 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -142,7 +142,7 @@ void Launcher::MainDialog::createPages() connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); - connect(mDataFilesPage, SIGNAL(signalSelectedFilesChanged(QStringList)), mAdvancedPage, SLOT(slotSelectedDataFilesChanged(QStringList))); + connect(mDataFilesPage, SIGNAL(signalSelectedFilesChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList))); } From e26c67582920709344036f54bb245086195408f7 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sat, 2 Jun 2018 17:29:35 -0500 Subject: [PATCH 13/72] Changing join to detach so that the thread will not block the UI --- apps/launcher/datafilespage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 1f46c7156..bcb2966d5 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -335,7 +335,7 @@ void Launcher::DataFilesPage::slotAddonDataChanged() // Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a // barely perceptible UI lag. Splitting into its own thread to alleviate that. std::thread loadCellsThread(&DataFilesPage::reloadCells, this, selectedFiles); - loadCellsThread.join(); + loadCellsThread.detach(); } } From 103a7ac62882d196b9fd12c9afa374f17a5e856f Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 3 Jun 2018 16:32:12 -0500 Subject: [PATCH 14/72] Using a mutex lock to prevent race conditions --- apps/launcher/datafilespage.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index bcb2966d5..76ca893c4 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -339,8 +339,16 @@ void Launcher::DataFilesPage::slotAddonDataChanged() } } +// Mutex lock to run reloadCells synchronously. +std::mutex _reloadCellsMutex; + void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) { + // Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time + // Based on https://stackoverflow.com/a/5429695/531762 + std::unique_lock lock(_reloadCellsMutex); + + // The following code will run only if there is not another thread currently running it CellNameLoader cellNameLoader; QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); std::sort(cellNamesList.begin(), cellNamesList.end()); From e282ece3d14bda73938cce38412a932d74826246 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 3 Jun 2018 16:58:18 -0500 Subject: [PATCH 15/72] Fixing bug with autocomplete not loading correctly during startup --- apps/launcher/advancedpage.hpp | 11 +++++------ apps/launcher/datafilespage.cpp | 3 +++ apps/launcher/maindialog.cpp | 3 --- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 82b9c3b65..59de3d319 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -23,12 +23,6 @@ namespace Launcher bool loadSettings(); void saveSettings(); - /** - * Load the cells associated with the given content files for use in autocomplete - * @param filePaths the file paths of the content files to be examined - */ - void loadCellsForAutocomplete(QStringList filePaths); - public slots: void slotLoadedCellsChanged(QStringList cellNames); @@ -41,6 +35,11 @@ namespace Launcher Config::GameSettings &mGameSettings; Settings::Manager &mEngineSettings; + /** + * Load the cells associated with the given content files for use in autocomplete + * @param filePaths the file paths of the content files to be examined + */ + void loadCellsForAutocomplete(QStringList filePaths); void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); }; diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 76ca893c4..b3c8fb14a 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" @@ -47,6 +48,8 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: // the addons and don't want to get signals of the system doing it during startup. connect(mSelector, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)), this, SLOT(slotAddonDataChanged())); + // Call manually to indicate all changes to addon data during startup. + slotAddonDataChanged(); } void Launcher::DataFilesPage::buildView() diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 79f3e5c67..a70f6ff7f 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -125,9 +125,6 @@ void Launcher::MainDialog::createPages() mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex()); - // Load cells for the "Start default character at" field - mAdvancedPage->loadCellsForAutocomplete(mDataFilesPage->selectedFilePaths()); - // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); pagesWidget->addWidget(mDataFilesPage); From d46590934af75c265b6fd1894acbdc306b470c7d Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 3 Jun 2018 16:59:27 -0500 Subject: [PATCH 16/72] Importing mutex --- apps/launcher/datafilespage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index b3c8fb14a..6c23b3285 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include From ab433102a49057d8ae4eb7219674e75960313045 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 May 2018 10:31:53 +0400 Subject: [PATCH 17/72] Increase hit distance for player by halfExtents --- apps/openmw/mwworld/worldimp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 13970ee13..61bed46f7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1074,6 +1074,7 @@ namespace MWWorld osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); if (ptr == getPlayerPtr()) pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera @@ -1082,11 +1083,12 @@ namespace MWWorld // general case, compatible with all types of different creatures // note: we intentionally do *not* use the collision box offset here, this is required to make // some flying creatures work that have their collision box offset in the air - osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); pos.z() += halfExtents.z() * 2 * 0.75; - distance += halfExtents.y(); } + // the origin of hitbox is an actor's front, not center + distance += halfExtents.y(); + std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); if(result.first.isEmpty()) return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); From f5dc9f01620306c147c1428a1bb39eee0c286acf Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 May 2018 10:53:53 +0400 Subject: [PATCH 18/72] Use hitbox cone only as fallback --- apps/openmw/mwphysics/physicssystem.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f3c34bc4e..4830760af 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -846,6 +846,16 @@ namespace MWPhysics const osg::Quat &orient, float queryDistance, std::vector targets) { + // First of all, try to hit where you aim to + int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; + RayResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, 0xff, hitmask); + + if (result.mHit) + { + return std::make_pair(result.mHitObject, result.mHitPos); + } + + // Use cone shape as fallback const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance); From 9e5d577a71dab1fffec695121b400e9b4f675b93 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 May 2018 11:35:51 +0400 Subject: [PATCH 19/72] Aim from center of attacker to center of target --- apps/openmw/mwworld/worldimp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 61bed46f7..586fc7816 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1083,7 +1083,7 @@ namespace MWWorld // general case, compatible with all types of different creatures // note: we intentionally do *not* use the collision box offset here, this is required to make // some flying creatures work that have their collision box offset in the air - pos.z() += halfExtents.z() * 2 * 0.75; + pos.z() += halfExtents.z(); } // the origin of hitbox is an actor's front, not center @@ -3468,7 +3468,7 @@ namespace MWWorld osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); - weaponPos.z() += mPhysics->getHalfExtents(actor).z() * 2 * 0.75; + weaponPos.z() += mPhysics->getHalfExtents(actor).z(); osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } @@ -3477,7 +3477,7 @@ namespace MWWorld { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); - weaponPos.z() += halfExtents.z() * 2 * 0.75; + weaponPos.z() += halfExtents.z(); return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); } From 4666a6a0abc221806fda56c85c5d5b910ef5f51d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 May 2018 12:53:58 +0400 Subject: [PATCH 20/72] Use default hit formula as fallback --- apps/openmw/mwworld/worldimp.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 586fc7816..8423adedc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1073,21 +1073,28 @@ namespace MWWorld osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); + // the origin of hitbox is an actor's front, not center + distance += halfExtents.y(); + + // special cased for better aiming with the camera + // if we do not hit anything, will use the default approach as fallback if (ptr == getPlayerPtr()) - pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera - else { - // general case, compatible with all types of different creatures - // note: we intentionally do *not* use the collision box offset here, this is required to make - // some flying creatures work that have their collision box offset in the air - pos.z() += halfExtents.z(); + osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); + + std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); + if(!result.first.isEmpty()) + return std::make_pair(result.first, result.second); } - // the origin of hitbox is an actor's front, not center - distance += halfExtents.y(); + osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); + + // general case, compatible with all types of different creatures + // note: we intentionally do *not* use the collision box offset here, this is required to make + // some flying creatures work that have their collision box offset in the air + pos.z() += halfExtents.z(); std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); if(result.first.isEmpty()) From bde1d07d4eb756da90f31698aa154920fa102cd1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 15 May 2018 18:57:36 +0400 Subject: [PATCH 21/72] Use hitboxes and focused object for touch spells (bug #3374) --- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 94 +++++++++++++------------ 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 4830760af..625ae5573 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -848,7 +848,7 @@ namespace MWPhysics { // First of all, try to hit where you aim to int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; - RayResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, 0xff, hitmask); + RayResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, CollisionType_Actor, hitmask); if (result.mHit) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8423adedc..cbd5b9340 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2761,64 +2761,66 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // Get the target to use for "on touch" effects, using the facing direction from Head node - MWWorld::Ptr target; - float distance = getActivationDistancePlusTelekinesis(); - - osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - - osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) - * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); - - osg::Vec3f direction = orient * osg::Vec3f(0,1,0); - osg::Vec3f dest = origin + direction * distance; - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (!actor.isEmpty() && actor != MWMechanics::getPlayer()) actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); - // For actor targets, we want to use bounding boxes (physics raycast). - // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. - // For object targets, we want the detailed shapes (rendering raycast). - // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. + const float fCombatDistance = getStore().get().find("fCombatDistance")->getFloat(); - MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, targetActors, MWPhysics::CollisionType_Actor); - - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); + // for player we can take faced object first + MWWorld::Ptr target; + if (actor == MWMechanics::getPlayer()) + target = getFacedObject(); - if (result1.mHit) - dist1 = (origin - result1.mHitPos).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); + // if the faced object can not be activated, do not use it + if (!target.isEmpty() && !target.getClass().canBeActivated(target)) + target = NULL; - if (result1.mHit) - { - target = result1.mHitObject; - hitPosition = result1.mHitPos; - if (dist1 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target))) - target = NULL; - } - else if (result2.mHit) + if (target.isEmpty()) { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) - target = NULL; - } + // For actor targets, we want to use hit contact with bounding boxes. + // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. + std::pair result1 = getHitContact(actor, fCombatDistance, targetActors); - // When targeting an actor that is in combat with an "on touch" spell, - // compare against the minimum of activation distance and combat distance. + // Get the target to use for "on touch" effects, using the facing direction from Head node + osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat()) - { - distance = std::min (distance, getStore().get().find("fCombatDistance")->getFloat()); - if (distance < dist1) - target = NULL; + osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); + + osg::Vec3f direction = orient * osg::Vec3f(0,1,0); + float distance = getMaxActivationDistance(); + osg::Vec3f dest = origin + direction * distance; + + MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + + float dist1 = std::numeric_limits::max(); + float dist2 = std::numeric_limits::max(); + + if (!result1.first.isEmpty() && result1.first.getClass().isActor()) + dist1 = (origin - result1.second).length(); + if (result2.mHit) + dist2 = (origin - result2.mHitPointWorld).length(); + + if (!result1.first.isEmpty() && result1.first.getClass().isActor()) + { + target = result1.first; + hitPosition = result1.second; + if (dist1 > getMaxActivationDistance()) + target = NULL; + } + else if (result2.mHit) + { + target = result2.mHitObject; + hitPosition = result2.mHitPointWorld; + if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) + target = NULL; + } } std::string selectedSpell = stats.getSpells().getSelectedSpell(); From 05026b891ea52b06d056e080cfb4e264c71a9ce9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 7 Jun 2018 20:42:18 +0400 Subject: [PATCH 22/72] Add changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbef559d..30b2af930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ 0.45.0 ------ + Bug #3374: Touch spells not hitting kwama foragers + Bug #3591: Angled hit distance too low Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4426: RotateWorld behavior is incorrect Bug #4433: Guard behaviour is incorrect with Alarm = 0 From 1a354f88acace854cc7cbb6c04e0cf8c57cce4fc Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 7 Jun 2018 22:05:55 +0300 Subject: [PATCH 23/72] Make choices trigger goodbye if Goodbye is used (fixes #3897) --- CHANGELOG.md | 1 + apps/openmw/mwgui/dialogue.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbef559d..0fbc5092f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.45.0 ------ + Feature #3897: Have Goodbye give all choices the effects of Goodbye Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4426: RotateWorld behavior is incorrect Bug #4433: Guard behaviour is incorrect with Alarm = 0 diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 14bbe81ef..450799f29 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -635,6 +635,11 @@ namespace MWGui void DialogueWindow::onChoiceActivated(int id) { + if (mGoodbye) + { + onGoodbyeActivated(); + return; + } MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); updateTopics(); } From 7cafec9861eea5b44effdc3feb31ebdaec69e310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric?= Date: Thu, 7 Jun 2018 15:30:10 +0200 Subject: [PATCH 24/72] Add support for msvc with cmake version pre 3.9 (fixes #4429) --- cmake/OpenMWMacros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index fe2837a09..2fa86094f 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -172,7 +172,7 @@ macro (get_generator_is_multi_config VALUE) if (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) get_cmake_property(${VALUE} GENERATOR_IS_MULTI_CONFIG) else (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) - list(LENGTH "${CMAKE_CONFIGURATION_TYPES}" ${VALUE}) + list(LENGTH CMAKE_CONFIGURATION_TYPES ${VALUE}) endif (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) endif (DEFINED generator_is_multi_config_var) endmacro (get_generator_is_multi_config) From 4a9b790dbea1023290e6e31a7ee69015661b385c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric?= Date: Fri, 8 Jun 2018 06:36:11 +0200 Subject: [PATCH 25/72] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbef559d..d423e1ca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4426: RotateWorld behavior is incorrect + Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4433: Guard behaviour is incorrect with Alarm = 0 0.44.0 From b784c7873da469f457a1219bea30af772acf2dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric?= Date: Fri, 8 Jun 2018 06:36:43 +0200 Subject: [PATCH 26/72] Update authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 17f11730d..faa59670f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -37,6 +37,7 @@ Programmers Britt Mathis (galdor557) Capostrophic cc9cii + Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) From 2fada9487927be1932e0eb1a134ce69473b62a81 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Jun 2018 15:44:35 +0400 Subject: [PATCH 27/72] Improve MRK NiStringExtraData handling (bug #4419) --- CHANGELOG.md | 1 + components/nifosg/nifloader.cpp | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d88fce1d0..08c81f60a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #4221: Characters get stuck in V-shaped terrain Bug #4293: Faction members are not aware of faction ownerships in barter + Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4433: Guard behaviour is incorrect with Alarm = 0 diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0add92f29..aada21fce 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -261,7 +261,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -463,7 +463,7 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return NULL; @@ -510,7 +510,7 @@ namespace NifOsg if(sd->string == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. - skipMeshes = true; + hasMarkers = true; } } } @@ -542,7 +542,7 @@ namespace NifOsg node->setNodeMask(0x1); } - if (skipMeshes && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. + if ((skipMeshes || hasMarkers) && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. { node->setDataVariance(osg::Object::DYNAMIC); } @@ -554,13 +554,18 @@ namespace NifOsg if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) { const Nif::NiTriShape* triShape = static_cast(nifNode); - if (triShape->skin.empty()) - handleTriShape(triShape, node, composite, boundTextures, animflags); - else - handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); + const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name); + static const std::string pattern = "tri editormarker"; + if (!hasMarkers || nodeName.compare(0, pattern.size(), pattern) != 0) + { + if (triShape->skin.empty()) + handleTriShape(triShape, node, composite, boundTextures, animflags); + else + handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); - if (!nifNode->controller.empty()) - handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + if (!nifNode->controller.empty()) + handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + } } if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) @@ -598,7 +603,7 @@ namespace NifOsg for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, isAnimated, textKeys, rootNode); + handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); } } From de5a3eaae9d55c8f4046426c349253a844926c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric?= Date: Fri, 8 Jun 2018 17:08:44 +0200 Subject: [PATCH 28/72] Fix indentation issue: replace tab by spaces --- AUTHORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index faa59670f..b029140c7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -37,7 +37,7 @@ Programmers Britt Mathis (galdor557) Capostrophic cc9cii - Cédric Mocquillon + Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) From fed10e87aa10bf82b6e193c4df8eda90e10c7ac9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 21 Nov 2017 20:00:51 +0400 Subject: [PATCH 29/72] Store integer actor ID in AI packages (bug #4036) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aicombat.cpp | 7 +-- apps/openmw/mwmechanics/aicombat.hpp | 3 -- apps/openmw/mwmechanics/aiescort.cpp | 22 ++++---- apps/openmw/mwmechanics/aiescort.hpp | 6 +-- apps/openmw/mwmechanics/aifollow.cpp | 67 ++++++++++++------------ apps/openmw/mwmechanics/aifollow.hpp | 11 ++-- apps/openmw/mwmechanics/aipackage.cpp | 25 ++++++++- apps/openmw/mwmechanics/aipackage.hpp | 3 ++ apps/openmw/mwmechanics/aipursue.cpp | 4 +- apps/openmw/mwmechanics/aipursue.hpp | 4 -- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- apps/openmw/mwmechanics/summoning.cpp | 2 +- components/esm/aisequence.cpp | 4 ++ components/esm/aisequence.hpp | 2 + components/esm/savedgame.cpp | 2 +- 16 files changed, 93 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2a9acc1..62a011859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.45.0 ------ Bug #2835: Player able to slowly move when overencumbered + Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4221: Characters get stuck in V-shaped terrain Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4327: Missing animations during spell/weapon stance switching diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 83ebc67d9..06248fa10 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -103,9 +103,10 @@ namespace MWMechanics bool isFleeing(); }; - AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) - {} + AiCombat::AiCombat(const MWWorld::Ptr& actor) + { + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + } AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) { diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index e7d74248b..6e1f0c623 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -54,9 +54,6 @@ namespace MWMechanics virtual bool shouldCancelPreviousAi() const { return false; } private: - - int mTargetActorId; - /// Returns true if combat should end bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 103ef32e1..a86d13d75 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -22,28 +22,32 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = actorId; mMaxDist = 450; } - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) + : mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = actorId; mMaxDist = 450; } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) - : mActorId(escort->mTargetId), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) , mMaxDist(450) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = escort->mTargetId; + mTargetActorId = escort->mTargetActorId; // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. // The exact value of mDuration only matters for repeating packages. if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. @@ -78,7 +82,7 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); - const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; double differenceBetween[3]; @@ -119,18 +123,14 @@ namespace MWMechanics return TypeIdEscort; } - MWWorld::Ptr AiEscort::getTarget() const - { - return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); - } - void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr escort(new ESM::AiSequence::AiEscort()); escort->mData.mX = mX; escort->mData.mY = mY; escort->mData.mZ = mZ; - escort->mTargetId = mActorId; + escort->mTargetId = mTargetActorRefId; + escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 719582271..82dba960e 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -24,11 +24,11 @@ namespace MWMechanics /// Implementation of AiEscort /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ - AiEscort(const std::string &actorId,int duration, float x, float y, float z); + AiEscort(const std::string &actorId, int duration, float x, float y, float z); /// Implementation of AiEscortCell /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time \implement AiEscortCell **/ - AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); + AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z); AiEscort(const ESM::AiSequence::AiEscort* escort); @@ -38,7 +38,6 @@ namespace MWMechanics virtual int getTypeId() const; - MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } void writeState(ESM::AiSequence::AiSequence &sequence) const; @@ -46,7 +45,6 @@ namespace MWMechanics void fastForward(const MWWorld::Ptr& actor, AiState& state); private: - std::string mActorId; std::string mCellId; float mX; float mY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index fd5f9c7fe..13de01f9a 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -35,32 +35,53 @@ struct AiFollowStorage : AiTemporaryBase int AiFollow::mFollowIndexCounter = 0; -AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) +AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) +, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { + mTargetActorRefId = actorId; } -AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) +AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mActorId(-1), mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) +, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { + mTargetActorRefId = actorId; } -AiFollow::AiFollow(const std::string &actorId, bool commanded) +AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) +: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) +{ + mTargetActorRefId = actor.getCellRef().getRefId(); + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); +} + +AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z) +: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) +{ + mTargetActorRefId = actor.getCellRef().getRefId(); + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); +} + +AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) : mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) -, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) +, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { + mTargetActorRefId = actor.getCellRef().getRefId(); + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) : mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) - , mActorRefId(follow->mTargetId), mActorId(-1) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) { -// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. -// The exact value of mDuration only matters for repeating packages. + mTargetActorRefId = follow->mTargetId; + mTargetActorId = follow->mTargetActorId; + // mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. + // The exact value of mDuration only matters for repeating packages. if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. mDuration = 1; else @@ -204,7 +225,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte std::string AiFollow::getFollowedActor() { - return mActorRefId; + return mTargetActorRefId; } AiFollow *MWMechanics::AiFollow::clone() const @@ -228,7 +249,8 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; - follow->mTargetId = mActorRefId; + follow->mTargetId = mTargetActorRefId; + follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; @@ -241,29 +263,6 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const sequence.mPackages.push_back(package); } -MWWorld::Ptr AiFollow::getTarget() const -{ - if (mActorId == -2) - return MWWorld::Ptr(); - - if (mActorId == -1) - { - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorRefId, false); - if (target.isEmpty()) - { - mActorId = -2; - return target; - } - else - mActorId = target.getClass().getCreatureStats(target).getActorId(); - } - - if (mActorId != -1) - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); - else - return MWWorld::Ptr(); -} - int AiFollow::getFollowIndex() const { return mFollowIndex; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 051a4a2ce..f0d43c9a7 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -25,16 +25,17 @@ namespace MWMechanics class AiFollow : public AiPackage { public: + AiFollow(const std::string &actorId, float duration, float x, float y, float z); + AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z); /// Follow Actor for duration or until you arrive at a world position - AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); + AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z); /// Follow Actor for duration or until you arrive at a position in a cell - AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); + AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z); /// Follow Actor indefinitively - AiFollow(const std::string &ActorId, bool commanded=false); + AiFollow(const MWWorld::Ptr& actor, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); - MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } virtual bool followTargetThroughDoors() const { return true; } virtual bool shouldCancelPreviousAi() const { return !mCommanded; } @@ -66,8 +67,6 @@ namespace MWMechanics float mX; float mY; float mZ; - std::string mActorRefId; - mutable int mActorId; std::string mCellId; bool mActive; // have we spotted the target? int mFollowIndex; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 38f641b94..6a0f5b013 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -27,15 +27,36 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mTargetActorRefId(""), + mTargetActorId(-1), mRotateOnTheRunChecks(0), mIsShortcutting(false), - mShortcutProhibited(false), mShortcutFailPos() + mShortcutProhibited(false), + mShortcutFailPos() { } MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { - return MWWorld::Ptr(); + if (mTargetActorId == -2) + return MWWorld::Ptr(); + + if (mTargetActorId == -1) + { + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); + if (target.isEmpty()) + { + mTargetActorId = -2; + return target; + } + else + mTargetActorId = target.getClass().getCreatureStats(target).getActorId(); + } + + if (mTargetActorId != -1) + return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + else + return MWWorld::Ptr(); } bool MWMechanics::AiPackage::sideWithTarget() const diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 06b4adf61..b4987e954 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -128,6 +128,9 @@ namespace MWMechanics float mTimer; + std::string mTargetActorRefId; + mutable int mTargetActorId; + osg::Vec3f mLastActorPos; short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index fd8b5752a..f46594655 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -15,13 +15,13 @@ namespace MWMechanics { AiPursue::AiPursue(const MWWorld::Ptr& actor) - : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) - : mTargetActorId(pursue->mTargetActorId) { + mTargetActorId = pursue->mTargetActorId; } AiPursue *MWMechanics::AiPursue::clone() const diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index cb93e9636..455b0a2fd 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -40,10 +40,6 @@ namespace MWMechanics virtual bool canCancel() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; } - - private: - - int mTargetActorId; // The actor to pursue }; } #endif diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d71b4c8ba..f6d92726d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -547,7 +547,7 @@ namespace MWMechanics || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel()) { - MWMechanics::AiFollow package(caster.getCellRef().getRefId(), true); + MWMechanics::AiFollow package(caster, true); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index fad7bc96d..71c49f9df 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -59,7 +59,7 @@ namespace MWMechanics MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); // Make the summoned creature follow its master and help in fights - AiFollow package(mActor.getCellRef().getRefId()); + AiFollow package(mActor); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index c39ef8269..90992bed3 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -46,6 +46,7 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); } @@ -54,6 +55,7 @@ namespace AiSequence { esm.writeHNT ("DATA", mData); esm.writeHNString ("TARG", mTargetId); + esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); @@ -63,6 +65,7 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); @@ -76,6 +79,7 @@ namespace AiSequence { esm.writeHNT ("DATA", mData); esm.writeHNString("TARG", mTargetId); + esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index 52446d38f..3c4cf2406 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -89,6 +89,7 @@ namespace ESM { AiEscortData mData; + int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; @@ -101,6 +102,7 @@ namespace ESM { AiEscortData mData; + int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 3220f496e..c96261c64 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 3; +int ESM::SavedGame::sCurrentFormat = 4; void ESM::SavedGame::load (ESMReader &esm) { From 99781ab70c11c25bb92c1fadb75b7173676328ae Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 8 Jun 2018 19:15:31 +0300 Subject: [PATCH 30/72] Fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbc5092f..333ae6f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ 0.45.0 ------ - Feature #3897: Have Goodbye give all choices the effects of Goodbye + Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4426: RotateWorld behavior is incorrect Bug #4433: Guard behaviour is incorrect with Alarm = 0 From b274931165c85b0ead18f4fc89c7e2ae6b83e036 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 8 Jun 2018 19:18:53 +0300 Subject: [PATCH 31/72] Revert erroneous changes --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index da6e80e79..fb492ff3b 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -431,11 +431,8 @@ namespace MWDialogue void DialogueManager::addChoice (const std::string& text, int choice) { - if (!mGoodbye) - { - mIsInChoice = true; - mChoices.push_back(std::make_pair(text, choice)); - } + mIsInChoice = true; + mChoices.push_back(std::make_pair(text, choice)); } const std::vector >& DialogueManager::getChoices() @@ -450,8 +447,8 @@ namespace MWDialogue void DialogueManager::goodbye() { - if (!mIsInChoice) - mGoodbye = true; + mIsInChoice = false; + mGoodbye = true; } void DialogueManager::persuade(int type, ResponseCallback* callback) From 9d27eb197fceb00cf37b836bdfd649088f64635c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 28 Nov 2017 12:05:05 +0400 Subject: [PATCH 32/72] AiWander: return to initial position only after combat --- apps/openmw/mwmechanics/aiwander.cpp | 68 +++++++------------ apps/openmw/mwmechanics/aiwander.hpp | 1 - .../mwmechanics/mechanicsmanagerimp.cpp | 18 ++++- 3 files changed, 41 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8199170dc..3ad45e2c3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -59,7 +59,7 @@ namespace MWMechanics float mTargetAngleRadians; bool mTurnActorGivingGreetingToFacePlayer; float mReaction; // update some actions infrequently - + AiWander::GreetingState mSaidGreeting; int mGreetingTimer; @@ -70,7 +70,7 @@ namespace MWMechanics bool mIsWanderingManually; bool mCanWanderAlongPathGrid; - + unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors @@ -86,7 +86,7 @@ namespace MWMechanics float mDoorCheckDuration; int mStuckCount; - + AiWanderStorage(): mTargetAngleRadians(0), mTurnActorGivingGreetingToFacePlayer(false), @@ -111,7 +111,7 @@ namespace MWMechanics mIsWanderingManually = isManualWander; } }; - + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)) @@ -201,7 +201,18 @@ namespace MWMechanics stopWalking(actor, storage); currentCell = actor.getCell(); storage.mPopulateAvailableNodes = true; - mStoredInitialActorPosition = false; + } + + // Here we should reset an initial position, if a current cell was REALLY changed + // We do not store AiStorage in a savegame, so cellChange is not help us in this case + // TODO: find a more simple and fast solution, or do not store the mInitialActorPosition at all + if (mStoredInitialActorPosition) + { + int cx,cy; + MWBase::Environment::get().getWorld()->positionToIndex(mInitialActorPosition.x(),mInitialActorPosition.y(),cx,cy); + MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); + if (cell != currentCell) + mStoredInitialActorPosition = false; } mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); @@ -223,7 +234,7 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) storage.setState(Wander_Walking); } - + doPerFrameActionsForState(actor, duration, storage, pos); playIdleDialogueRandomly(actor); @@ -298,13 +309,6 @@ namespace MWMechanics if(mDistance && cellChange) mDistance = 0; - // For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere - if (mDistance == 0 && !cellChange - && (pos.asVec3() - mInitialActorPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE)) - { - returnToStartLocation(actor, storage, pos); - } - // Allow interrupting a walking actor to trigger a greeting WanderState& wanderState = storage.mState; if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) @@ -321,7 +325,7 @@ namespace MWMechanics { setPathToAnAllowedNode(actor, storage, pos); } - } + } } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { completeManualWalking(actor, storage); } @@ -330,8 +334,8 @@ namespace MWMechanics } bool AiWander::getRepeat() const - { - return mRepeat; + { + return mRepeat; } @@ -350,27 +354,6 @@ namespace MWMechanics return false; } - void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) - { - if (!mPathFinder.isPathConstructed()) - { - mDestination = mInitialActorPosition; - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); - - // actor position is already in world coordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); - - if (mPathFinder.isPathConstructed()) - { - storage.setState(Wander_Walking); - mHasDestination = true; - } - } - } - /* * Commands actor to walk to a random location near original spawn location. */ @@ -497,7 +480,7 @@ namespace MWMechanics } } - void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, + void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) { // Is there no destination or are we there yet? @@ -873,7 +856,7 @@ namespace MWMechanics state.moveIn(new AiWanderStorage()); - MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), + MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); } @@ -914,7 +897,7 @@ namespace MWMechanics // get NPC's position in local (i.e. cell) coordinates osg::Vec3f npcPos(mInitialActorPosition); CoordinateConverter(cell).toLocal(npcPos); - + // Find closest pathgrid point int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); @@ -945,7 +928,7 @@ namespace MWMechanics storage.mPopulateAvailableNodes = false; } - // When only one path grid point in wander distance, + // When only one path grid point in wander distance, // additional points for NPC to wander to are: // 1. NPC's initial location // 2. Partway along the path between the point and its connected points. @@ -969,7 +952,7 @@ namespace MWMechanics delta.normalize(); int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); - + // must not travel longer than distance between waypoints or NPC goes past waypoint distance = std::min(distance, static_cast(length)); delta *= distance; @@ -1041,4 +1024,3 @@ namespace MWMechanics init(); } } - diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6266a7708..3f69d107d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -85,7 +85,6 @@ namespace MWMechanics bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 94317bbf2..bddcf83d6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -22,6 +22,7 @@ #include "aicombat.hpp" #include "aipursue.hpp" +#include "aitravel.hpp" #include "spellcasting.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" @@ -1598,9 +1599,22 @@ namespace MWMechanics void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { - if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) + MWMechanics::AiSequence& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + + if (aiSequence.isInCombat(target)) return; - ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); + + // we should return a wandering actor back after combat + // TODO: only for stationary wander? + if (!aiSequence.isInCombat() && aiSequence.getLastRunTypeId() == MWMechanics::AiPackage::TypeIdWander) + { + osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); + + MWMechanics::AiTravel travelPackage(pos.x(), pos.y(), pos.z()); + aiSequence.stack(travelPackage, ptr); + } + + aiSequence.stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { // if guard starts combat with player, guards pursuing player should do the same From 18ff097e4a2ab38bb3d46e3164e26fa89de8317e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 28 Nov 2017 12:06:09 +0400 Subject: [PATCH 33/72] Add the parameter to AiSequence::stack() to control ability to cancel other AI packages --- apps/openmw/mwmechanics/aisequence.cpp | 4 ++-- apps/openmw/mwmechanics/aisequence.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2a2d598f5..b38111f7b 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -298,7 +298,7 @@ void AiSequence::clear() mPackages.clear(); } -void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) +void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) { if (actor == getPlayer()) throw std::runtime_error("Can't add AI packages to player"); @@ -308,7 +308,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) stopCombat(); // remove previous packages if required - if (package.shouldCancelPreviousAi()) + if (cancelOther && package.shouldCancelPreviousAi()) { for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 41d204ee2..d725409de 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -115,7 +115,7 @@ namespace MWMechanics ///< Add \a package to the front of the sequence /** Suspends current package @param actor The actor that owns this AiSequence **/ - void stack (const AiPackage& package, const MWWorld::Ptr& actor); + void stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther=true); /// Return the current active package. /** If there is no active package, it will throw an exception **/ diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index bddcf83d6..d032ed632 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1611,7 +1611,7 @@ namespace MWMechanics osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); MWMechanics::AiTravel travelPackage(pos.x(), pos.y(), pos.z()); - aiSequence.stack(travelPackage, ptr); + aiSequence.stack(travelPackage, ptr, false); } aiSequence.stack(MWMechanics::AiCombat(target), ptr); From 81f29d8dcd131bc123fbed5858f49fa049c060d9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Dec 2017 18:53:31 +0400 Subject: [PATCH 34/72] AiWander: resume moving to destination after combat --- apps/openmw/mwmechanics/aipackage.hpp | 3 +++ apps/openmw/mwmechanics/aiwander.cpp | 15 ++++++++++++--- apps/openmw/mwmechanics/aiwander.hpp | 6 ++++-- .../openmw/mwmechanics/mechanicsmanagerimp.cpp | 18 ++++++++++++++---- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 06b4adf61..d9a7fa386 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -79,6 +79,9 @@ namespace MWMechanics /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) virtual MWWorld::Ptr getTarget() const; + /// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0)) + virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); }; + /// Return true if having this AiPackage makes the actor side with the target in fights (default false) virtual bool sideWithTarget() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 3ad45e2c3..2e832dc3f 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -335,9 +335,18 @@ namespace MWMechanics bool AiWander::getRepeat() const { - return mRepeat; + return mRepeat; } + osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const + { + if (mHasDestination) + return mDestination; + + const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; + const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ); + return currentPositionVec3f; + } bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) { @@ -346,8 +355,8 @@ namespace MWMechanics // End package if duration is complete if (mRemainingDuration <= 0) { - stopWalking(actor, storage); - return true; + stopWalking(actor, storage); + return true; } } // if get here, not yet completed diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 3f69d107d..d96d93165 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -47,9 +47,11 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); - + bool getRepeat() const; - + + osg::Vec3f getDestination(const MWWorld::Ptr& actor) const; + enum GreetingState { Greet_None, Greet_InProgress, diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index d032ed632..55aace8e7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1605,12 +1605,22 @@ namespace MWMechanics return; // we should return a wandering actor back after combat - // TODO: only for stationary wander? - if (!aiSequence.isInCombat() && aiSequence.getLastRunTypeId() == MWMechanics::AiPackage::TypeIdWander) + // the same thing for actors without AI packages + if (!aiSequence.isInCombat() && aiSequence.getTypeId() <= MWMechanics::AiPackage::TypeIdWander) { - osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); + int typeId = aiSequence.getTypeId(); + osg::Vec3f dest; + if (typeId == MWMechanics::AiPackage::TypeIdNone) + { + dest = ptr.getRefData().getPosition().asVec3(); + } + else if (typeId == MWMechanics::AiPackage::TypeIdWander) + { + AiPackage* activePackage = aiSequence.getActivePackage(); + dest = activePackage->getDestination(ptr); + } - MWMechanics::AiTravel travelPackage(pos.x(), pos.y(), pos.z()); + MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z()); aiSequence.stack(travelPackage, ptr, false); } From 5105c676422fa2664d4605717a04d9aea500e157 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Dec 2017 19:58:42 +0400 Subject: [PATCH 35/72] Add mHidden field to AiTravel --- apps/openmw/mwmechanics/aipackage.hpp | 3 ++- apps/openmw/mwmechanics/aitravel.cpp | 8 ++++---- apps/openmw/mwmechanics/aitravel.hpp | 4 +++- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index d9a7fa386..829bbe898 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -48,7 +48,8 @@ namespace MWMechanics TypeIdPursue = 6, TypeIdAvoidDoor = 7, TypeIdFace = 8, - TypeIdBreathe = 9 + TypeIdBreathe = 9, + TypeIdInternalTravel = 10 }; ///Default constructor diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 36b96101f..ea14407ca 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -28,15 +28,14 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z) - : mX(x),mY(y),mZ(z) + AiTravel::AiTravel(float x, float y, float z, bool hidden) + : mX(x),mY(y),mZ(z),mHidden(hidden) { } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) { - } AiTravel *MWMechanics::AiTravel::clone() const @@ -64,7 +63,8 @@ namespace MWMechanics int AiTravel::getTypeId() const { - return TypeIdTravel; + // TODO: store mHidden in the savegame? + return mHidden ? TypeIdInternalTravel : TypeIdTravel; } void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 8c75bded1..c297771d0 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -20,7 +20,7 @@ namespace MWMechanics { public: /// Default constructor - AiTravel(float x, float y, float z); + AiTravel(float x, float y, float z, bool hidden = false); AiTravel(const ESM::AiSequence::AiTravel* travel); /// Simulates the passing of time @@ -38,6 +38,8 @@ namespace MWMechanics float mX; float mY; float mZ; + + bool mHidden; }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 55aace8e7..dc3695239 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1620,7 +1620,7 @@ namespace MWMechanics dest = activePackage->getDestination(ptr); } - MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z()); + MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true); aiSequence.stack(travelPackage, ptr, false); } From 57d686131e89ec4f18ab97bcab7fa7443d7df20e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Dec 2017 20:01:15 +0400 Subject: [PATCH 36/72] Remove redundant condition --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index dc3695239..fbf989d05 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1606,7 +1606,7 @@ namespace MWMechanics // we should return a wandering actor back after combat // the same thing for actors without AI packages - if (!aiSequence.isInCombat() && aiSequence.getTypeId() <= MWMechanics::AiPackage::TypeIdWander) + if (aiSequence.getTypeId() <= MWMechanics::AiPackage::TypeIdWander) { int typeId = aiSequence.getTypeId(); osg::Vec3f dest; From 2f5beb885371aa2daed3b999b78cda24ec3abcfc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Dec 2017 20:38:10 +0400 Subject: [PATCH 37/72] Remove unnecessary hack --- apps/openmw/mwmechanics/aiwander.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 2e832dc3f..ee680159e 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -201,18 +201,7 @@ namespace MWMechanics stopWalking(actor, storage); currentCell = actor.getCell(); storage.mPopulateAvailableNodes = true; - } - - // Here we should reset an initial position, if a current cell was REALLY changed - // We do not store AiStorage in a savegame, so cellChange is not help us in this case - // TODO: find a more simple and fast solution, or do not store the mInitialActorPosition at all - if (mStoredInitialActorPosition) - { - int cx,cy; - MWBase::Environment::get().getWorld()->positionToIndex(mInitialActorPosition.x(),mInitialActorPosition.y(),cx,cy); - MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); - if (cell != currentCell) - mStoredInitialActorPosition = false; + mStoredInitialActorPosition = false; } mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); From 3a0ee78d2b300301a16e4739cd11de14195f9d0d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 25 May 2018 19:31:31 +0400 Subject: [PATCH 38/72] AiTravel: store mHidden flag in savegame --- apps/openmw/mwmechanics/aisequence.cpp | 3 ++- apps/openmw/mwmechanics/aitravel.cpp | 4 ++-- components/esm/aisequence.cpp | 2 ++ components/esm/aisequence.hpp | 1 + components/esm/savedgame.cpp | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b38111f7b..2c48eacf8 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -184,7 +184,8 @@ bool isActualAiPackage(int packageTypeId) && packageTypeId != AiPackage::TypeIdPursue && packageTypeId != AiPackage::TypeIdAvoidDoor && packageTypeId != AiPackage::TypeIdFace - && packageTypeId != AiPackage::TypeIdBreathe); + && packageTypeId != AiPackage::TypeIdBreathe + && packageTypeId != AiPackage::TypeIdInternalTravel); } void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index ea14407ca..72e6ced19 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -34,7 +34,7 @@ namespace MWMechanics } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) - : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) + : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(travel->mHidden) { } @@ -63,7 +63,6 @@ namespace MWMechanics int AiTravel::getTypeId() const { - // TODO: store mHidden in the savegame? return mHidden ? TypeIdInternalTravel : TypeIdTravel; } @@ -83,6 +82,7 @@ namespace MWMechanics travel->mData.mX = mX; travel->mData.mY = mY; travel->mData.mZ = mZ; + travel->mHidden = mHidden; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index c39ef8269..196c1754f 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -35,11 +35,13 @@ namespace AiSequence void AiTravel::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); + esm.getHNOT (mHidden, "HIDD"); } void AiTravel::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); + esm.writeHNT ("HIDD", mHidden); } void AiEscort::load(ESMReader &esm) diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index 52446d38f..d4315062b 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -80,6 +80,7 @@ namespace ESM struct AiTravel : AiPackage { AiTravelData mData; + bool mHidden; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 3220f496e..c96261c64 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 3; +int ESM::SavedGame::sCurrentFormat = 4; void ESM::SavedGame::load (ESMReader &esm) { From 3d0631cfcc01fdb5032ac3df2be00a1beadd1e6a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 25 May 2018 20:07:08 +0400 Subject: [PATCH 39/72] Store last AI package in savegame --- apps/openmw/mwmechanics/aisequence.cpp | 6 +++++- components/esm/aisequence.cpp | 4 ++++ components/esm/aisequence.hpp | 6 +++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2c48eacf8..51c82f6ed 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -393,6 +393,8 @@ void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const { (*iter)->writeState(sequence); } + + sequence.mLastAiPackage = mLastAiPackage; } void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) @@ -404,7 +406,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) int count = 0; for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) - { + { if (isActualAiPackage(it->mType)) count++; } @@ -463,6 +465,8 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) mPackages.push_back(package.release()); } + + mLastAiPackage = sequence.mLastAiPackage; } void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state) diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 196c1754f..43712d9ea 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -156,6 +156,8 @@ namespace AiSequence break; } } + + esm.writeHNT ("LAST", mLastAiPackage); } void AiSequence::load(ESMReader &esm) @@ -223,6 +225,8 @@ namespace AiSequence return; } } + + esm.getHNOT (mLastAiPackage, "LAST"); } } } diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index d4315062b..0cbde1b8e 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -148,10 +148,14 @@ namespace ESM struct AiSequence { - AiSequence() {} + AiSequence() + { + mLastAiPackage = -1; + } ~AiSequence(); std::vector mPackages; + int mLastAiPackage; void load (ESMReader &esm); void save (ESMWriter &esm) const; From 74a2cbe6960ce178fb426f86430532c561cb0da7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Jun 2018 09:30:44 +0400 Subject: [PATCH 40/72] AI: return back after pursuit --- apps/openmw/mwmechanics/aisequence.cpp | 23 +++++++++++++++++++ .../mwmechanics/mechanicsmanagerimp.cpp | 20 ---------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 51c82f6ed..84ddf8fdf 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -308,6 +308,29 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo if (isActualAiPackage(package.getTypeId())) stopCombat(); + // we should return a wandering actor back after combat or pursuit + // the same thing for actors without AI packages + int currentTypeId = getTypeId(); + int newTypeId = package.getTypeId(); + if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander + && (newTypeId <= MWMechanics::AiPackage::TypeIdCombat + || newTypeId == MWMechanics::AiPackage::TypeIdPursue)) + { + osg::Vec3f dest; + if (currentTypeId == MWMechanics::AiPackage::TypeIdWander) + { + AiPackage* activePackage = getActivePackage(); + dest = activePackage->getDestination(actor); + } + else + { + dest = actor.getRefData().getPosition().asVec3(); + } + + MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true); + stack(travelPackage, actor, false); + } + // remove previous packages if required if (cancelOther && package.shouldCancelPreviousAi()) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fbf989d05..89e1bef06 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1604,26 +1604,6 @@ namespace MWMechanics if (aiSequence.isInCombat(target)) return; - // we should return a wandering actor back after combat - // the same thing for actors without AI packages - if (aiSequence.getTypeId() <= MWMechanics::AiPackage::TypeIdWander) - { - int typeId = aiSequence.getTypeId(); - osg::Vec3f dest; - if (typeId == MWMechanics::AiPackage::TypeIdNone) - { - dest = ptr.getRefData().getPosition().asVec3(); - } - else if (typeId == MWMechanics::AiPackage::TypeIdWander) - { - AiPackage* activePackage = aiSequence.getActivePackage(); - dest = activePackage->getDestination(ptr); - } - - MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true); - aiSequence.stack(travelPackage, ptr, false); - } - aiSequence.stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { From 6ed27732995c2a87f2e62c4966c24a729a60b158 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Jun 2018 12:05:10 +0400 Subject: [PATCH 41/72] Do not stack return packages --- apps/openmw/mwmechanics/aisequence.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 84ddf8fdf..85afbc453 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -308,11 +308,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo if (isActualAiPackage(package.getTypeId())) stopCombat(); - // we should return a wandering actor back after combat or pursuit - // the same thing for actors without AI packages + // We should return a wandering actor back after combat or pursuit. + // The same thing for actors without AI packages. + // Also there is no point to stack return packages. int currentTypeId = getTypeId(); int newTypeId = package.getTypeId(); if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander + && !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel) && (newTypeId <= MWMechanics::AiPackage::TypeIdCombat || newTypeId == MWMechanics::AiPackage::TypeIdPursue)) { From 2e6cf2a4149ae87ca932833c70f594725475c5a1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Jun 2018 20:39:59 +0400 Subject: [PATCH 42/72] Add changelog entries --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb72c7cac..82410813e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ 0.45.0 ------ + Bug #2835: Player able to slowly move when overencumbered + Bug #3997: Almalexia doesn't pace Bug #4221: Characters get stuck in V-shaped terrain + Bug #4251: Stationary NPCs do not return to their position after combat Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4327: Missing animations during spell/weapon stance switching Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 + Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 Bug #4443: Goodbye option and dialogue choices are not mutually exclusive Feature #4444: Per-group KF-animation files support From 6277f5511c4346a4038dda8401edf241b8a88b51 Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 8 Jun 2018 17:52:46 -0400 Subject: [PATCH 43/72] fix #3876 and #3993 --- components/esmterrain/storage.cpp | 34 ++++++++++++++++++++++++------- components/terrain/material.cpp | 3 +++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index dc144d119..a1b4856e1 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -430,15 +430,23 @@ namespace ESMTerrain // Second iteration - create and fill in the blend maps const int blendmapSize = (realTextureSize-1) * chunkSize + 1; + // We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla + const int imageScaleFactor = 2; + const int blendmapImageSize = blendmapSize * imageScaleFactor; + + + const bool largeImage = true; for (int i=0; i image (new osg::Image); - image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE); + if(!largeImage) + image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE); + else + image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); unsigned char* pData = image->data(); - for (int y=0; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - - if (blendIndex == i) - pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 255; + + int alpha = (blendIndex == i) ? 255 : 0; + + if(!largeImage) + pData[((blendmapSize - y - 1)*blendmapSize + x)*channels + channel] = alpha; else - pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 0; + { + int realY = (blendmapSize - y - 1)*imageScaleFactor; + int realX = x*imageScaleFactor; + if(true) + pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha; + if(realY+1 < blendmapImageSize) + pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha; + if(realX+1 < blendmapImageSize) + pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha; + if(realY+1 < blendmapImageSize && realX+1 < blendmapImageSize) + pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha; + } } } - blendmaps.push_back(image); } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 640f2932b..56ace0e5a 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -25,6 +25,9 @@ namespace Terrain matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); matrix.preMultScale(osg::Vec3f(scale, scale, 1.f)); matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + // We need to nudge the blendmap to look like vanilla. + // This causes visible seams unless the blendmap's resolution is doubled, but Vanilla also doubles the blendmap, apparently. + matrix.preMultTranslate(osg::Vec3f(1.0f/blendmapScale/4.0f, 1.0f/blendmapScale/4.0f, 0.f)); texMat = new osg::TexMat(matrix); From 62c4eb8d6abbb4b3ebf5af951801dc0ce17bbc1f Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Fri, 8 Jun 2018 19:16:24 -0500 Subject: [PATCH 44/72] Explicitly flagging loaded cells changed as queued --- apps/launcher/maindialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index a70f6ff7f..90197b157 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -139,7 +139,8 @@ void Launcher::MainDialog::createPages() connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); - connect(mDataFilesPage, SIGNAL(signalSelectedFilesChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList))); + // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread + connect(mDataFilesPage, SIGNAL(signalSelectedFilesChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); } From dfa99685658318a27fa79e59f7060aa8fbebb68c Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Fri, 8 Jun 2018 19:18:23 -0500 Subject: [PATCH 45/72] Renaming Launcher::DataFilesPage::signalSelectedFilesChanged to signalLoadedCellsChanged --- apps/launcher/datafilespage.cpp | 2 +- apps/launcher/datafilespage.hpp | 2 +- apps/launcher/maindialog.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6c23b3285..7b703a924 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -356,5 +356,5 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) CellNameLoader cellNameLoader; QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); std::sort(cellNamesList.begin(), cellNamesList.end()); - emit signalSelectedFilesChanged(cellNamesList); + emit signalLoadedCellsChanged(cellNamesList); } \ No newline at end of file diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index d871eeee0..2cbace38e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -50,7 +50,7 @@ namespace Launcher signals: void signalProfileChanged (int index); - void signalSelectedFilesChanged(QStringList selectedFiles); + void signalLoadedCellsChanged(QStringList selectedFiles); public slots: void slotProfileChanged (int index); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 90197b157..27fa2d903 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -140,7 +140,7 @@ void Launcher::MainDialog::createPages() connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread - connect(mDataFilesPage, SIGNAL(signalSelectedFilesChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); + connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); } From 4ba361fea6d1ecef2e13e2bad2267c6644df5e6a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 10 Apr 2018 07:06:44 +0400 Subject: [PATCH 46/72] Unhardcode sunset and sunrise settings (bug #1990) --- apps/openmw/mwworld/weather.cpp | 119 ++++++++++++++++++++++---------- apps/openmw/mwworld/weather.hpp | 43 +++++++++++- 2 files changed, 123 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 155e4340a..d12e633be 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -43,49 +42,67 @@ namespace } template -T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const +T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const { - // TODO: use pre/post sunset/sunrise time values in [Weather] section + WeatherSetting setting = timeSettings.getSetting(prefix); + float preSunriseTime = setting.mPreSunriseTime; + float postSunriseTime = setting.mPostSunriseTime; + float preSunsetTime = setting.mPreSunsetTime; + float postSunsetTime = setting.mPostSunsetTime; // night - if (gameHour <= timeSettings.mNightEnd || gameHour >= timeSettings.mNightStart + 1) + if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) return mNightValue; // sunrise - else if (gameHour >= timeSettings.mNightEnd && gameHour <= timeSettings.mDayStart + 1) + else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) { - if (gameHour <= timeSettings.mSunriseTime) + float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; + float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; + + if (gameHour <= middle) { // fade in - float advance = timeSettings.mSunriseTime - gameHour; - float factor = advance / 0.5f; + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunriseValue, mNightValue, factor); } else { // fade out - float advance = gameHour - timeSettings.mSunriseTime; - float factor = advance / 3.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunriseValue, mDayValue, factor); } } // day - else if (gameHour >= timeSettings.mDayStart + 1 && gameHour <= timeSettings.mDayEnd - 1) + else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) return mDayValue; // sunset - else if (gameHour >= timeSettings.mDayEnd - 1 && gameHour <= timeSettings.mNightStart + 1) + else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) { - if (gameHour <= timeSettings.mDayEnd + 1) + float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; + float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; + + if (gameHour <= middle) { // fade in - float advance = (timeSettings.mDayEnd + 1) - gameHour; - float factor = (advance / 2); + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mDayValue, factor); } else { // fade out - float advance = gameHour - (timeSettings.mDayEnd + 1); - float factor = advance / 2.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mNightValue, factor); } } @@ -539,10 +556,28 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; - mTimeSettings.mNightEnd = mSunriseTime - 0.5f; + mTimeSettings.mNightEnd = mSunriseTime; mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; mTimeSettings.mDayEnd = mSunsetTime; - mTimeSettings.mSunriseTime = mSunriseTime; + + mTimeSettings.addSetting(fallback, "Sky"); + mTimeSettings.addSetting(fallback, "Ambient"); + mTimeSettings.addSetting(fallback, "Fog"); + mTimeSettings.addSetting(fallback, "Sun"); + + // Morrowind handles stars settings differently for other ones + mTimeSettings.mStarsPostSunsetStart = fallback.getFallbackFloat("Weather_Stars_Post-Sunset_Start"); + mTimeSettings.mStarsPreSunriseFinish = fallback.getFallbackFloat("Weather_Stars_Pre-Sunrise_Finish"); + mTimeSettings.mStarsFadingDuration = fallback.getFallbackFloat("Weather_Stars_Fading_Duration"); + + WeatherSetting starSetting = { + mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsPostSunsetStart, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart + }; + + mTimeSettings.mSunriseTransitions["Stars"] = starSetting; mWeatherSettings.reserve(10); // These distant land fog factor and offset values are the defaults MGE XE provides. Should be @@ -698,10 +733,13 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, const float nightDuration = 24.f - dayDuration; double theta; - if ( !is_night ) { + if ( !is_night ) + { theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; - } else { - theta = static_cast(osg::PI) * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); + } + else + { + theta = static_cast(osg::PI) + static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( @@ -711,7 +749,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, mRendering.setSunDirection( final * -1 ); } - float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings); + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) @@ -784,7 +822,7 @@ unsigned int WeatherManager::getWeatherID() const bool WeatherManager::useTorches(float hour) const { - bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart - 1; + bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; return isDark && !mPrecipitation; } @@ -1060,20 +1098,25 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart - 1); + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); - mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings); + mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); + mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); + mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); + mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); mResult.mDLFogFactor = current.mDL.FogFactor; mResult.mDLFogOffset = current.mDL.FogOffset; - mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings); - mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings); - mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings); - mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings); - mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings); - if (gameHour >= mSunsetTime - mSunPreSunsetTime) + WeatherSetting setting = mTimeSettings.getSetting("Sun"); + float preSunsetTime = setting.mPreSunsetTime; + + if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) { - float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; + float factor = 1.f; + if (preSunsetTime > 0) + factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; factor = std::min(1.f, factor); mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because @@ -1087,15 +1130,17 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam else mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); - if (gameHour >= mSunsetTime) + if (gameHour >= mTimeSettings.mDayEnd) { - float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); + // sunset + float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); fade = fade*fade; mResult.mSunDiscColor.a() = 1.f - fade; } - else if (gameHour >= mSunriseTime && gameHour <= mSunriseTime + 1) + else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) { - mResult.mSunDiscColor.a() = gameHour - mSunriseTime; + // sunrise + mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; } else mResult.mSunDiscColor.a() = 1; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 4be0d3e20..cf6868356 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwbase/soundmanager.hpp" #include "../mwrender/sky.hpp" @@ -38,6 +40,13 @@ namespace MWWorld { class TimeStamp; + struct WeatherSetting + { + float mPreSunriseTime; + float mPostSunriseTime; + float mPreSunsetTime; + float mPostSunsetTime; + }; struct TimeOfDaySettings { @@ -45,7 +54,37 @@ namespace MWWorld float mNightEnd; float mDayStart; float mDayEnd; - float mSunriseTime; + + std::map mSunriseTransitions; + + float mStarsPostSunsetStart; + float mStarsPreSunriseFinish; + float mStarsFadingDuration; + + WeatherSetting getSetting(const std::string& type) const + { + std::map::const_iterator it = mSunriseTransitions.find(type); + if (it != mSunriseTransitions.end()) + { + return it->second; + } + else + { + return { 1.f, 1.f, 1.f, 1.f }; + } + } + + void addSetting(const Fallback::Map& fallback, const std::string& type) + { + WeatherSetting setting = { + fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunrise_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Post-Sunrise_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunset_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Post-Sunset_Time") + }; + + mSunriseTransitions[type] = setting; + } }; /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. @@ -59,7 +98,7 @@ namespace MWWorld { } - T getValue (const float gameHour, const TimeOfDaySettings& timeSettings) const; + T getValue (const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const; private: T mSunriseValue, mDayValue, mSunsetValue, mNightValue; From 5ead6353ac38cd3df0a09287034ee69da8bee554 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 7 Jun 2018 16:44:46 +0400 Subject: [PATCH 47/72] Add missing changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1acff2a..f36636571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.45.0 ------ + Bug #1990: Sunrise/sunset not set correct Bug #2835: Player able to slowly move when overencumbered Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low From ae87e0d3fcbd1ba6348f51fcbb05a693741deda3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jun 2018 15:34:08 +0400 Subject: [PATCH 48/72] Do not reset mUpperBodyState for weapon->weapon switch (regression #4446) --- apps/openmw/mwmechanics/character.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a0aad8b1b..07e5fa7d6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1214,7 +1214,6 @@ bool CharacterController::updateWeaponState() bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if (!animPlaying || complete >= 1.0f) { - mUpperBodyState = UpperCharState_Nothing; forcestateupdate = true; mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); From fba0c155df01481b8f6bf534b9c20c5ebeb903c4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jun 2018 17:07:38 +0400 Subject: [PATCH 49/72] Fix assertion fail related to NiLookAtController --- components/nif/controller.cpp | 4 ++-- components/nif/controller.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index ddfa02a09..49f591b47 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -104,13 +104,13 @@ namespace Nif void NiLookAtController::read(NIFStream *nif) { Controller::read(nif); - data.read(nif); + target.read(nif); } void NiLookAtController::post(NIFFile *nif) { Controller::post(nif); - data.post(nif); + target.post(nif); } void NiPathController::read(NIFStream *nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index ce8bff041..f22d10622 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -102,7 +102,7 @@ public: class NiLookAtController : public Controller { public: - NiKeyframeDataPtr data; + NodePtr target; void read(NIFStream *nif); void post(NIFFile *nif); From 8f45b0d53a3c398df88d83681c24a25863b910ba Mon Sep 17 00:00:00 2001 From: wareya Date: Sat, 9 Jun 2018 10:11:43 -0400 Subject: [PATCH 50/72] remove unnecessary conditions --- components/esmterrain/storage.cpp | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index a1b4856e1..fd04d90b9 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -434,19 +434,14 @@ namespace ESMTerrain const int imageScaleFactor = 2; const int blendmapImageSize = blendmapSize * imageScaleFactor; - - const bool largeImage = true; - for (int i=0; i image (new osg::Image); - if(!largeImage) - image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE); - else - image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); + image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); unsigned char* pData = image->data(); + for (int y=0; y Date: Sat, 9 Jun 2018 10:31:51 -0400 Subject: [PATCH 51/72] remove indentation from blank lines --- components/esmterrain/storage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index fd04d90b9..dadc64f57 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -433,7 +433,7 @@ namespace ESMTerrain // We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla const int imageScaleFactor = 2; const int blendmapImageSize = blendmapSize * imageScaleFactor; - + for (int i=0; i image (new osg::Image); image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); unsigned char* pData = image->data(); - + for (int y=0; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - + int alpha = (blendIndex == i) ? 255 : 0; - + int realY = (blendmapSize - y - 1)*imageScaleFactor; int realX = x*imageScaleFactor; - + pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha; pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha; pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha; From a9ca528fb8a6afdb2d88cbde023e84a862e74004 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sat, 9 Jun 2018 19:42:24 -0500 Subject: [PATCH 52/72] Adding version number of macOS build of OpenMW --- files/mac/openmw-Info.plist.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/mac/openmw-Info.plist.in b/files/mac/openmw-Info.plist.in index b4a8af480..7583b45ad 100644 --- a/files/mac/openmw-Info.plist.in +++ b/files/mac/openmw-Info.plist.in @@ -18,6 +18,8 @@ APPL CFBundleSignature ???? + CFBundleShortVersionString + ${OPENMW_VERSION} CFBundleVersion ${OPENMW_VERSION} CSResourcesFileMapped From dbc87e7c7dc9d7adebda34d0458b972a682038ec Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 10 Jun 2018 11:28:22 +0200 Subject: [PATCH 53/72] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1acff2a..a3101056e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID + Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully Bug #4221: Characters get stuck in V-shaped terrain Bug #4251: Stationary NPCs do not return to their position after combat Bug #4293: Faction members are not aware of faction ownerships in barter From 0375bedab229358b8fe04c29cd16d382623f4370 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 23 Nov 2017 19:57:36 +0400 Subject: [PATCH 54/72] Equip previous item after a bound item expires (bug #2326) --- apps/openmw/mwmechanics/actors.cpp | 106 +++++++++++++++++++++++------ apps/openmw/mwmechanics/actors.hpp | 4 ++ 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7990373a7..10e9b7afc 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr) return !stats.isDead() && !stats.getKnockedDown(); } -void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) +int getBoundItemSlot (const std::string& itemId) { - if (bound) + static std::map boundItemsMap; + if (boundItemsMap.empty()) { - if (actor.getClass().getContainerStore(actor).count(item) == 0) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor); - MWWorld::ActionEquip action(newPtr); - action.execute(actor); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - // change draw state only if the item is in player's right hand - if (actor == MWMechanics::getPlayer() - && rightHand != store.end() && newPtr == *rightHand) - { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - } - } + std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; } - else - actor.getClass().getInventoryStore(actor).remove(item, 1, actor, true); + + int slot = MWWorld::InventoryStore::Slot_CarriedRight; + std::map::iterator it = boundItemsMap.find(itemId); + if (it != boundItemsMap.end()) + slot = it->second; + + return slot; } class CheckActorCommanded : public MWMechanics::EffectSourceVisitor @@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { - const float aiProcessingDistance = 7168; const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; @@ -227,6 +235,65 @@ namespace MWMechanics } }; + void Actors::adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + if (bound) + { + if (actor.getClass().getContainerStore(actor).count(itemId) != 0) + return; + + int slot = getBoundItemSlot(itemId); + + MWWorld::Ptr prevItem = *store.getSlot(slot); + + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); + + if (actor != MWMechanics::getPlayer()) + return; + + MWWorld::Ptr newItem = *store.getSlot(slot); + + if (newItem.isEmpty() || boundPtr != newItem) + return; + + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + + mPreviousItems[slot] = std::make_pair(itemId, prevItem.isEmpty() ? "" : prevItem.getCellRef().getRefId()); + } + else + { + store.remove(itemId, 1, actor, true); + + if (actor != MWMechanics::getPlayer()) + return; + + int slot = getBoundItemSlot(itemId); + + std::pair prevItem = mPreviousItems[slot]; + + if (prevItem.first != itemId) + return; + + MWWorld::Ptr ptr = MWWorld::Ptr(); + if (prevItem.second != "") + ptr = store.search (prevItem.second); + + mPreviousItems.erase(slot); + + if (ptr.isEmpty()) + return; + + MWWorld::ActionEquip action(ptr); + action.execute(actor); + } + } + void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects @@ -1875,6 +1942,7 @@ namespace MWMechanics } mActors.clear(); mDeathCount.clear(); + mPreviousItems.clear(); } void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 15f2d3dc8..f0e157db1 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,6 +25,10 @@ namespace MWMechanics class Actors { std::map mDeathCount; + typedef std::map> PreviousItems; + PreviousItems mPreviousItems; + + void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); void updateNpc(const MWWorld::Ptr &ptr, float duration); From 9b72a6ac69ddd4b282ff3c9ed46ca4d8b7cde44c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 27 Nov 2017 13:00:14 +0400 Subject: [PATCH 55/72] Use the MWWorld::Ptr() instead of string ID --- apps/openmw/mwmechanics/actors.cpp | 23 +++++++++++++++-------- apps/openmw/mwmechanics/actors.hpp | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 10e9b7afc..76c5744b3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -264,7 +264,7 @@ namespace MWMechanics if (slot == MWWorld::InventoryStore::Slot_CarriedRight) MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - mPreviousItems[slot] = std::make_pair(itemId, prevItem.isEmpty() ? "" : prevItem.getCellRef().getRefId()); + mPreviousItems[slot] = std::make_pair(itemId, prevItem); } else { @@ -275,21 +275,28 @@ namespace MWMechanics int slot = getBoundItemSlot(itemId); - std::pair prevItem = mPreviousItems[slot]; + std::pair prevItem = mPreviousItems[slot]; if (prevItem.first != itemId) return; - MWWorld::Ptr ptr = MWWorld::Ptr(); - if (prevItem.second != "") - ptr = store.search (prevItem.second); - mPreviousItems.erase(slot); - if (ptr.isEmpty()) + if (prevItem.second.isEmpty()) + return; + + // check if the item is still in the player's inventory + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == prevItem.second) + break; + } + + if (it == store.end()) return; - MWWorld::ActionEquip action(ptr); + MWWorld::ActionEquip action(prevItem.second); action.execute(actor); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index f0e157db1..40e9e1d21 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map> PreviousItems; + typedef std::map> PreviousItems; PreviousItems mPreviousItems; void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); From d1b1cb748d94cda070c5383973329613620097d0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2017 16:26:43 +0400 Subject: [PATCH 56/72] Reequip previous item only if the expired bound item was equipped --- apps/openmw/mwmechanics/actors.cpp | 41 +++++++++++++++--------------- apps/openmw/mwmechanics/actors.hpp | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 76c5744b3..4019067e3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -238,15 +238,14 @@ namespace MWMechanics void Actors::adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + int slot = getBoundItemSlot(itemId); if (bound) { if (actor.getClass().getContainerStore(actor).count(itemId) != 0) return; - int slot = getBoundItemSlot(itemId); - - MWWorld::Ptr prevItem = *store.getSlot(slot); + MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); MWWorld::ActionEquip action(boundPtr); @@ -264,39 +263,40 @@ namespace MWMechanics if (slot == MWWorld::InventoryStore::Slot_CarriedRight) MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - mPreviousItems[slot] = std::make_pair(itemId, prevItem); + if (prevItem != store.end()) + mPreviousItems[itemId] = *prevItem; } else { + MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); + + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); + store.remove(itemId, 1, actor, true); if (actor != MWMechanics::getPlayer()) return; - int slot = getBoundItemSlot(itemId); + MWWorld::Ptr prevItem = mPreviousItems[itemId]; - std::pair prevItem = mPreviousItems[slot]; + mPreviousItems.erase(itemId); - if (prevItem.first != itemId) - return; - - mPreviousItems.erase(slot); - - if (prevItem.second.isEmpty()) + if (prevItem.isEmpty()) return; // check if the item is still in the player's inventory MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) { - if (*it == prevItem.second) + if (*it == prevItem) break; } - if (it == store.end()) + // we should equip previous item only if expired bound item was equipped. + if (it == store.end() || !wasEquipped) return; - MWWorld::ActionEquip action(prevItem.second); + MWWorld::ActionEquip action(prevItem); action.execute(actor); } } @@ -830,9 +830,15 @@ namespace MWMechanics float magnitude = effects.get(it->first).getMagnitude(); if (found != (magnitude > 0)) { + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + std::string itemGmst = it->second; std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->getString(); + if (it->first == ESM::MagicEffect::BoundGloves) { item = MWBase::Environment::get().getWorld()->getStore().get().find( @@ -844,11 +850,6 @@ namespace MWMechanics } else adjustBoundItem(item, magnitude > 0, ptr); - - if (magnitude > 0) - creatureStats.mBoundItems.insert(it->first); - else - creatureStats.mBoundItems.erase(it->first); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 40e9e1d21..e696abf01 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map> PreviousItems; + typedef std::map PreviousItems; PreviousItems mPreviousItems; void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); From 4de9d9fa778402eff3de8c9e8d56b44132aaca80 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2017 17:18:16 +0400 Subject: [PATCH 57/72] Split adjustBoundItem() --- apps/openmw/mwmechanics/actors.cpp | 96 +++++++++++++++--------------- apps/openmw/mwmechanics/actors.hpp | 3 +- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4019067e3..d1e74e576 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -235,70 +235,71 @@ namespace MWMechanics } }; - void Actors::adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor) + void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); int slot = getBoundItemSlot(itemId); - if (bound) - { - if (actor.getClass().getContainerStore(actor).count(itemId) != 0) - return; + if (actor.getClass().getContainerStore(actor).count(itemId) != 0) + return; - MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); + MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); - MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); - MWWorld::ActionEquip action(boundPtr); - action.execute(actor); + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); - if (actor != MWMechanics::getPlayer()) - return; + if (actor != MWMechanics::getPlayer()) + return; - MWWorld::Ptr newItem = *store.getSlot(slot); + MWWorld::Ptr newItem = *store.getSlot(slot); - if (newItem.isEmpty() || boundPtr != newItem) - return; + if (newItem.isEmpty() || boundPtr != newItem) + return; - // change draw state only if the item is in player's right hand - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - if (prevItem != store.end()) - mPreviousItems[itemId] = *prevItem; - } - else - { - MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); + if (prevItem != store.end()) + mPreviousItems[itemId] = *prevItem; + } - bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); + void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + int slot = getBoundItemSlot(itemId); - store.remove(itemId, 1, actor, true); + MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); - if (actor != MWMechanics::getPlayer()) - return; + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); - MWWorld::Ptr prevItem = mPreviousItems[itemId]; + store.remove(itemId, 1, actor, true); - mPreviousItems.erase(itemId); + if (actor != MWMechanics::getPlayer()) + return; - if (prevItem.isEmpty()) - return; + MWWorld::Ptr prevItem = mPreviousItems[itemId]; - // check if the item is still in the player's inventory - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) - { - if (*it == prevItem) - break; - } + mPreviousItems.erase(itemId); - // we should equip previous item only if expired bound item was equipped. - if (it == store.end() || !wasEquipped) - return; + if (prevItem.isEmpty()) + return; - MWWorld::ActionEquip action(prevItem); - action.execute(actor); + // check if the item is still in the player's inventory + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == prevItem) + break; } + + // we should equip previous item only if expired bound item was equipped. + if (it == store.end() || !wasEquipped) + return; + + MWWorld::ActionEquip action(prevItem); + action.execute(actor); } void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) @@ -839,17 +840,14 @@ namespace MWMechanics std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->getString(); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + if (it->first == ESM::MagicEffect::BoundGloves) { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundLeftGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); item = MWBase::Environment::get().getWorld()->getStore().get().find( "sMagicBoundRightGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); } - else - adjustBoundItem(item, magnitude > 0, ptr); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index e696abf01..b3e1f95db 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -28,7 +28,8 @@ namespace MWMechanics typedef std::map PreviousItems; PreviousItems mPreviousItems; - void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); + void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); + void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void updateNpc(const MWWorld::Ptr &ptr, float duration); From f977c6876ff391cc4624483647bf3197791482c5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2018 14:48:40 +0400 Subject: [PATCH 58/72] Bound items: store item ID instead of pointer --- apps/openmw/mwmechanics/actors.cpp | 28 ++++++++++++++++++---------- apps/openmw/mwmechanics/actors.hpp | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d1e74e576..8a13b492f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -262,7 +262,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); if (prevItem != store.end()) - mPreviousItems[itemId] = *prevItem; + mPreviousItems[itemId] = (*prevItem).getCellRef().getRefId(); } void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) @@ -279,26 +279,34 @@ namespace MWMechanics if (actor != MWMechanics::getPlayer()) return; - MWWorld::Ptr prevItem = mPreviousItems[itemId]; + std::string prevItemId = mPreviousItems[itemId]; mPreviousItems.erase(itemId); - if (prevItem.isEmpty()) + if (prevItemId.empty()) return; - // check if the item is still in the player's inventory - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) + // Find the item by id + MWWorld::Ptr item; + for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) { - if (*it == prevItem) - break; + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), prevItemId)) + { + if (item.isEmpty() || + // Prefer the stack with the lowest remaining uses + !item.getClass().hasItemHealth(*iter) || + iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) + { + item = *iter; + } + } } // we should equip previous item only if expired bound item was equipped. - if (it == store.end() || !wasEquipped) + if (item.isEmpty() || !wasEquipped) return; - MWWorld::ActionEquip action(prevItem); + MWWorld::ActionEquip action(item); action.execute(actor); } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index b3e1f95db..90b646f0e 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map PreviousItems; + typedef std::map PreviousItems; PreviousItems mPreviousItems; void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); From 9fd2d57b8663dee2df8ca739f15d7b39b769b3a6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 9 Mar 2018 08:56:04 +0400 Subject: [PATCH 59/72] Move previous items to player --- apps/openmw/mwmechanics/actors.cpp | 14 ++++++++------ apps/openmw/mwmechanics/actors.hpp | 3 --- apps/openmw/mwworld/player.cpp | 16 ++++++++++++++++ apps/openmw/mwworld/player.hpp | 9 +++++++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 8a13b492f..6549c169b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -262,7 +262,10 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); if (prevItem != store.end()) - mPreviousItems[itemId] = (*prevItem).getCellRef().getRefId(); + { + MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); + player->setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + } } void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) @@ -272,16 +275,16 @@ namespace MWMechanics MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); - bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); store.remove(itemId, 1, actor, true); if (actor != MWMechanics::getPlayer()) return; - std::string prevItemId = mPreviousItems[itemId]; - - mPreviousItems.erase(itemId); + MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player->getPreviousItem(itemId); + player->erasePreviousItem(itemId); if (prevItemId.empty()) return; @@ -1956,7 +1959,6 @@ namespace MWMechanics } mActors.clear(); mDeathCount.clear(); - mPreviousItems.clear(); } void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 90b646f0e..0de1f4d6c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "../mwbase/world.hpp" @@ -25,8 +24,6 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map PreviousItems; - PreviousItems mPreviousItems; void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 34c5f713d..193663098 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -287,6 +287,7 @@ namespace MWWorld mAttackingOrSpell = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; + mPreviousItems.clear(); mLastKnownExteriorPosition = osg::Vec3f(0,0,0); for (int i=0; i + #include "../mwworld/refdata.hpp" #include "../mwworld/livecellref.hpp" @@ -46,6 +48,9 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) + typedef std::map PreviousItems; // previous equipped items, needed for bound spells + PreviousItems mPreviousItems; + // Saved stats prior to becoming a werewolf MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; @@ -120,6 +125,10 @@ namespace MWWorld int getNewCrimeId(); // get new id for witnesses void recordCrimeId(); // record the paid crime id when bounty is 0 int getCrimeId() const; // get the last paid crime id + + void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId); + std::string getPreviousItem(const std::string& boundItemId); + void erasePreviousItem(const std::string& boundItemId); }; } #endif From acd3cba5fae441602389bd2dbef4579e8e70a842 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 9 Mar 2018 10:20:17 +0400 Subject: [PATCH 60/72] Store previous items in the savegame --- CHANGELOG.md | 1 + apps/openmw/mwworld/player.cpp | 4 ++++ components/esm/player.cpp | 18 ++++++++++++++++++ components/esm/player.hpp | 3 +++ components/esm/savedgame.cpp | 2 +- 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3101056e..a19b012c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.45.0 ------ + Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2835: Player able to slowly move when overencumbered Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 193663098..5439447fd 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -342,6 +342,8 @@ namespace MWWorld for (int i=0; ifirst); + esm.writeHNString ("PREV", it->second); + } + for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; StatState mSaveSkills[ESM::Skill::Length]; + typedef std::map PreviousItems; // previous equipped items, needed for bound spells + PreviousItems mPreviousItems; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index c96261c64..ea9fef4fb 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 4; +int ESM::SavedGame::sCurrentFormat = 5; void ESM::SavedGame::load (ESMReader &esm) { From ab03d238bbacb0ae3ef967d005e6bc375f99ed3b Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 10 Jun 2018 07:43:38 -0500 Subject: [PATCH 61/72] Adding Feature #4345 Implemented as part of #1623, but not added. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3101056e..8c8a7eb90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support 0.44.0 From d43766d3c9936c3875c1f2bcd0d88c68558dc9f6 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 10 Jun 2018 17:47:05 +0300 Subject: [PATCH 62/72] Make WakeUpPC interrupt waiting if it was supposed to be (fixes #3629) --- CHANGELOG.md | 1 + apps/openmw/mwgui/waitdialog.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3101056e..5a22b9d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Bug #2835: Player able to slowly move when overencumbered Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low + Bug #3629: DB assassin attack never triggers creature spawning Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 61febf315..52575e25c 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -310,8 +310,10 @@ namespace MWGui void WaitDialog::wakeUp () { mSleeping = false; - mTimeAdvancer.stop(); - stopWaiting(); + if (mInterruptAt != -1) + onWaitingInterrupted(); + else + stopWaiting(); } } From 66a46ff03cf997197dcbfd9e7494de716cee58c4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 11:22:56 +0400 Subject: [PATCH 63/72] Do not show any book text after last
tag. --- CHANGELOG.md | 1 + apps/openmw/mwgui/formatting.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3101056e..356be37a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully + Bug #4215: OpenMW shows book text after last
tag Bug #4221: Characters get stuck in V-shaped terrain Bug #4251: Stationary NPCs do not return to their position after combat Bug #4293: Faction members are not aware of faction ownerships in barter diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index a9319048e..16568e2f3 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -31,6 +31,14 @@ namespace MWGui boost::algorithm::replace_all(mText, "\r", ""); + // vanilla game does not show any text after last
tag. + const std::string lowerText = Misc::StringUtils::lowerCase(mText); + int index = lowerText.rfind("
"); + if (index == -1) + mText = ""; + else + mText = mText.substr(0, index+4); + registerTag("br", Event_BrTag); registerTag("p", Event_PTag); registerTag("img", Event_ImgTag); From 5fba1c599b761fd9e81ee2d0d3f898be49bc8abf Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 05:52:19 +0000 Subject: [PATCH 64/72] Update README.md with an important distinction. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc600fdbe..7d92879e2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ OpenMW [![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) -OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. +OpenMW is a open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. From 816a1733dcf6adc8edf17ed5ebd9c1d16daed7e6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 12 Jun 2018 15:29:28 +0200 Subject: [PATCH 65/72] Allow comma after Begin and End script instruction (Fixes #4451) --- components/compiler/fileparser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 52a9a63f1..c9e205b8a 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -119,6 +119,11 @@ namespace Compiler return false; } } + else if (code==Scanner::S_comma && (mState==NameState || mState==EndNameState)) + { + // ignoring comma (for now) + return true; + } return Parser::parseSpecial (code, loc, scanner); } From 296ad8424e998cb0215fc2e62a99c3a1bdd6f377 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 12 Jun 2018 17:06:09 +0200 Subject: [PATCH 66/72] updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e199d0827..346d2c53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning + Bug #3876: Landscape texture painting is misaligned Bug #3897: Have Goodbye give all choices the effects of Goodbye + Bug #3993: Terrain texture blending map is not upscaled Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully From 565922f9ad910473e9a8430bc860171187978782 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 12 Jun 2018 17:52:16 +0200 Subject: [PATCH 67/72] naked expressions beginning with the member operator were allowed erroneously outside of the console (Fixes issue #2971) --- components/compiler/lineparser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index c7f82a3d0..2d551348d 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -512,7 +512,7 @@ namespace Compiler return true; } - if (code==Scanner::S_member && mState==PotentialExplicitState) + if (code==Scanner::S_member && mState==PotentialExplicitState && mAllowExpression) { mState = MemberState; parseExpression (scanner, loc); From 1c8a20a54afc3958e3e622a331fe798c45e9f43d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 12 Jun 2018 19:33:41 +0300 Subject: [PATCH 68/72] Set ok button focus in settings window by default (fixes #4368) --- CHANGELOG.md | 1 + apps/openmw/mwgui/settingswindow.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 346d2c53c..f407a156e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Bug #4251: Stationary NPCs do not return to their position after combat Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4327: Missing animations during spell/weapon stance switching + Bug #4368: Settings window ok button doesn't have key focus by default Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 9bf6e4385..677ddefb3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -562,8 +562,9 @@ namespace MWGui void SettingsWindow::onOpen() { - updateControlsBox (); + updateControlsBox(); resetScrollbars(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) From 6114cff8422ee2e2dfc5b53852452a7be55bfe86 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 12 Jun 2018 19:14:32 +0200 Subject: [PATCH 69/72] updated credits file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index b029140c7..b13953824 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -63,6 +63,7 @@ Programmers Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) + Florian Weber (Florianjw) Gašper Sedej gugus/gus Hallfaer Tuilinn From 9c45cc7e48897171fc27c219bd1e7d9533b81ff9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 22:05:00 +0400 Subject: [PATCH 70/72] Use player reference instead of pointer --- apps/openmw/mwmechanics/actors.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6549c169b..de394c446 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -257,15 +257,14 @@ namespace MWMechanics if (newItem.isEmpty() || boundPtr != newItem) return; + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + // change draw state only if the item is in player's right hand if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + player.setDrawState(MWMechanics::DrawState_Weapon); if (prevItem != store.end()) - { - MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - player->setPreviousItem(itemId, prevItem->getCellRef().getRefId()); - } + player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); } void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) @@ -282,9 +281,9 @@ namespace MWMechanics if (actor != MWMechanics::getPlayer()) return; - MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - std::string prevItemId = player->getPreviousItem(itemId); - player->erasePreviousItem(itemId); + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player.getPreviousItem(itemId); + player.erasePreviousItem(itemId); if (prevItemId.empty()) return; From 058cfb553ce2a20a0150793796b4b5c32527eb9b Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Tue, 12 Jun 2018 22:18:06 -0500 Subject: [PATCH 71/72] Adding CFBundleIdentifier to OpenMW's Info.plist file for Macs --- files/mac/openmw-Info.plist.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/mac/openmw-Info.plist.in b/files/mac/openmw-Info.plist.in index 7583b45ad..20dc36afa 100644 --- a/files/mac/openmw-Info.plist.in +++ b/files/mac/openmw-Info.plist.in @@ -8,6 +8,8 @@ English CFBundleExecutable openmw-launcher + CFBundleIdentifier + org.openmw.openmw CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString From b37f3251263ec86d2c8c1622989616d1c674d64e Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Tue, 12 Jun 2018 22:20:16 -0500 Subject: [PATCH 72/72] #4324/Updating Changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f407a156e..95a947d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support