diff --git a/AUTHORS.md b/AUTHORS.md
index 17f11730d..b13953824 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)
@@ -62,6 +63,7 @@ Programmers
Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar)
Fil Krynicki (filkry)
+ Florian Weber (Florianjw)
Gašper Sedej
gugus/gus
Hallfaer Tuilinn
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1ec6e58e..f4bf94678 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,32 @@
0.45.0
------
+
+ Bug #1990: Sunrise/sunset not set correct
Bug #2222: Fatigue's effect on selling price is backwards
+ 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
+ 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
+ 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
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
+ 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 #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
0.44.0
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.
diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt
index 99e7b4daa..ec2e963d1 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 9ae83419d..2b2d7b448 100644
--- a/apps/launcher/advancedpage.cpp
+++ b/apps/launcher/advancedpage.cpp
@@ -1,10 +1,18 @@
#include "advancedpage.hpp"
-#include
-
-Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 +21,54 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings:
loadSettings();
}
+void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
+ // Set up an auto-completer for the "Start default character at" field
+ auto *completer = new QCompleter(cellNames);
+ 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);
+}
+
+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");
@@ -54,6 +108,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");
@@ -95,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::slotLoadedCellsChanged(QStringList cellNames)
+{
+ loadCellsForAutocomplete(cellNames);
}
\ No newline at end of file
diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp
index a8361c98e..59de3d319 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,15 +17,29 @@ 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();
+ public slots:
+ void slotLoadedCellsChanged(QStringList cellNames);
+
+ private slots:
+ void on_skipMenuCheckBox_stateChanged(int state);
+ void on_runScriptAfterStartupBrowseButton_clicked();
+
private:
Files::ConfigurationManager &mCfgMgr;
+ 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 0b0f8c75e..7b703a924 100644
--- a/apps/launcher/datafilespage.cpp
+++ b/apps/launcher/datafilespage.cpp
@@ -7,7 +7,10 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
@@ -16,6 +19,7 @@
#include
#include
+#include
#include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp"
@@ -40,6 +44,13 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
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()));
+ // Call manually to indicate all changes to addon data during startup.
+ slotAddonDataChanged();
}
void Launcher::DataFilesPage::buildView()
@@ -142,6 +153,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);
@@ -308,3 +330,31 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text)
return (msgBox.clickedButton() == deleteButton);
}
+
+void Launcher::DataFilesPage::slotAddonDataChanged()
+{
+ QStringList selectedFiles = selectedFilePaths();
+ if (previousSelectedFiles != selectedFiles) {
+ previousSelectedFiles = 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.detach();
+ }
+}
+
+// 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());
+ emit signalLoadedCellsChanged(cellNamesList);
+}
\ No newline at end of file
diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp
index d25d20fc9..2cbace38e 100644
--- a/apps/launcher/datafilespage.hpp
+++ b/apps/launcher/datafilespage.hpp
@@ -7,6 +7,7 @@
#include
#include
+#include
class QSortFilterProxyModel;
class QAbstractItemModel;
@@ -41,8 +42,15 @@ 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);
+ void signalLoadedCellsChanged(QStringList selectedFiles);
public slots:
void slotProfileChanged (int index);
@@ -52,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 slotAddonDataChanged ();
void updateOkButton(const QString &text);
@@ -72,7 +81,7 @@ namespace Launcher
Config::LauncherSettings &mLauncherSettings;
QString mPreviousProfile;
-
+ QStringList previousSelectedFiles;
QString mDataLocal;
void setPluginsCheckstates(Qt::CheckState state);
@@ -87,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 1a210ccc5..27fa2d903 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());
@@ -139,6 +139,8 @@ 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(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection);
}
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
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 60a513cb6..6716c7240 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);
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)
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();
}
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);
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)
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();
}
}
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index 7990373a7..de394c446 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,83 @@ namespace MWMechanics
}
};
+ void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
+ {
+ MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
+ int slot = getBoundItemSlot(itemId);
+
+ if (actor.getClass().getContainerStore(actor).count(itemId) != 0)
+ return;
+
+ MWWorld::ContainerStoreIterator 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;
+
+ 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)
+ player.setDrawState(MWMechanics::DrawState_Weapon);
+
+ if (prevItem != store.end())
+ player.setPreviousItem(itemId, prevItem->getCellRef().getRefId());
+ }
+
+ void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
+ {
+ MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
+ int slot = getBoundItemSlot(itemId);
+
+ 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;
+
+ MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
+ std::string prevItemId = player.getPreviousItem(itemId);
+ player.erasePreviousItem(itemId);
+
+ if (prevItemId.empty())
+ return;
+
+ // Find the item by id
+ MWWorld::Ptr item;
+ for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter)
+ {
+ 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 (item.isEmpty() || !wasEquipped)
+ return;
+
+ MWWorld::ActionEquip action(item);
+ action.execute(actor);
+ }
+
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{
// magic effects
@@ -756,25 +841,23 @@ 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();
+
+ 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);
-
- 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 15f2d3dc8..0de1f4d6c 100644
--- a/apps/openmw/mwmechanics/actors.hpp
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -4,7 +4,6 @@
#include
#include
#include
-#include